add: markdown permission setting
improve: template
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
// src/AuthProvider.js
|
||||
import React, { createContext, useContext, useEffect, useMemo, useState } from "react";
|
||||
import { UserManager } from "oidc-client-ts";
|
||||
import { ConfigContext } from "./ConfigProvider";
|
||||
|
||||
@@ -74,7 +74,6 @@ const ParametersManager = ({ parameters, onChange }) => {
|
||||
updated[index].type = newType;
|
||||
setParameters(updated);
|
||||
onChange(updated);
|
||||
console.log("updated", updated);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,43 +1,59 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useMarkdownTemplates} from "../../utils/queries/markdown-template-queries";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useMarkdownTemplates } from "../../utils/queries/markdown-template-queries";
|
||||
|
||||
const TemplateSelector = ({template, onChange}) => {
|
||||
const {data:templates, isFetching: templatesAreFetching} = useMarkdownTemplates();
|
||||
const [_template, setTemplate] = useState(templates?.find(t => t.id === template.id) || {
|
||||
title: "",
|
||||
parameters: [],
|
||||
layout: ""
|
||||
});
|
||||
useEffect(() => {
|
||||
setTemplate(templates?.find(t => t.id === template.id) || {
|
||||
const TemplateSelector = ({ template, onChange }) => {
|
||||
const { data: templates, isFetching: templatesAreFetching } = useMarkdownTemplates();
|
||||
const [_template, setTemplate] = useState(
|
||||
templates?.find((t) => t.id === template?.id) || {
|
||||
title: "",
|
||||
parameters: [],
|
||||
layout: ""
|
||||
});
|
||||
}, [template]);
|
||||
if(templatesAreFetching) {
|
||||
return <p>Loading...</p>
|
||||
layout: "",
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setTemplate(
|
||||
templates?.find((t) => t.id === template?.id) || {
|
||||
title: "",
|
||||
parameters: [],
|
||||
layout: "",
|
||||
}
|
||||
);
|
||||
}, [template, templates]);
|
||||
|
||||
if (templatesAreFetching) {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<select
|
||||
value={template.id}
|
||||
onChange={(e) => {
|
||||
const templateId = parseInt(e.target.value, 10);
|
||||
onChange(templates.find(t => t.id === templateId) || {
|
||||
title: "",
|
||||
parameters: [],
|
||||
layout: ""
|
||||
});
|
||||
}}>
|
||||
<option value="">(None)</option>
|
||||
{
|
||||
templates.map((tmpl, index) => (
|
||||
<option key={index} value={tmpl.id} >{tmpl.title}</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
<div className="field">
|
||||
<label className="label">Select Template</label>
|
||||
<div className="control">
|
||||
<div className="select is-fullwidth is-primary">
|
||||
<select
|
||||
value={template?.id || ""}
|
||||
onChange={(e) => {
|
||||
const id = parseInt(e.target.value, 10);
|
||||
onChange(
|
||||
templates.find((t) => t.id === id) || {
|
||||
title: "",
|
||||
parameters: [],
|
||||
layout: "",
|
||||
}
|
||||
);
|
||||
}}
|
||||
>
|
||||
<option value="">(None)</option>
|
||||
{templates.map((tmpl) => (
|
||||
<option key={tmpl.id} value={tmpl.id}>
|
||||
{tmpl.title}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TemplateSelector;
|
||||
export default TemplateSelector;
|
||||
|
||||
@@ -4,91 +4,116 @@ import TemplateSelector from './TemplateSelector';
|
||||
|
||||
const TypeEditor = ({ type, onChange }) => {
|
||||
const [_type, setType] = React.useState(type || {});
|
||||
|
||||
const updateType = (updated) => {
|
||||
setType(updated);
|
||||
onChange(updated);
|
||||
};
|
||||
|
||||
const renderExtraFields = () => {
|
||||
switch (_type.base_type) {
|
||||
case 'enum':
|
||||
return <EnumsEditor
|
||||
enums={_type.definition.enums}
|
||||
onChange={(newEnums) => {
|
||||
const updated = {
|
||||
..._type,
|
||||
definition: {
|
||||
..._type.definition,
|
||||
enums: newEnums
|
||||
}
|
||||
};
|
||||
setType(updated);
|
||||
onChange(updated);
|
||||
}}
|
||||
/>;
|
||||
case 'list':
|
||||
return (
|
||||
<div>
|
||||
<TypeEditor
|
||||
extendType={_type.extend_type}
|
||||
onChange={(extendType) => {
|
||||
const updated = {..._type, extend_type: extendType};
|
||||
setType(updated);
|
||||
onChange(updated);
|
||||
}}
|
||||
/>
|
||||
<textarea
|
||||
className="textarea"
|
||||
value={_type.definition.iter_layout}
|
||||
onChange={(e) => {
|
||||
const updated = {
|
||||
..._type,
|
||||
definition: {
|
||||
..._type.definition,
|
||||
iter_layout: e.target.value
|
||||
}
|
||||
};
|
||||
setType(updated);
|
||||
onChange(updated);
|
||||
}}
|
||||
/>
|
||||
<div className="field">
|
||||
<label className="label">Enums</label>
|
||||
<div className="control">
|
||||
<EnumsEditor
|
||||
enums={_type.definition.enums}
|
||||
onChange={(newEnums) => {
|
||||
updateType({
|
||||
..._type,
|
||||
definition: { ..._type.definition, enums: newEnums },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'list':
|
||||
return (
|
||||
<div className="box">
|
||||
<div className="field">
|
||||
<label className="label">Extend Type</label>
|
||||
<div className="control">
|
||||
<TypeEditor
|
||||
type={_type.extend_type}
|
||||
onChange={(extendType) => {
|
||||
updateType({ ..._type, extend_type: extendType });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label className="label">Iter Layout</label>
|
||||
<div className="control">
|
||||
<textarea
|
||||
className="textarea"
|
||||
value={_type.definition.iter_layout || ''}
|
||||
onChange={(e) => {
|
||||
updateType({
|
||||
..._type,
|
||||
definition: {
|
||||
..._type.definition,
|
||||
iter_layout: e.target.value,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'template':
|
||||
return <TemplateSelector
|
||||
template={_type.definition.template}
|
||||
onChange={(newTemplate) => {
|
||||
return (
|
||||
<div className="field">
|
||||
<label className="label">Template</label>
|
||||
<div className="control">
|
||||
<TemplateSelector
|
||||
template={_type.definition.template}
|
||||
onChange={(newTemplate) => {
|
||||
updateType({
|
||||
..._type,
|
||||
definition: {
|
||||
..._type.definition,
|
||||
template: newTemplate,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const updated = {
|
||||
..._type,
|
||||
definition: {
|
||||
..._type.definition,
|
||||
template: newTemplate
|
||||
}
|
||||
};
|
||||
setType(updated);
|
||||
onChange(updated);
|
||||
|
||||
|
||||
}}
|
||||
/>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label>type:</label>
|
||||
<select value={_type.base_type} onChange={(e) => {
|
||||
const updated = {
|
||||
base_type: e.target.value,
|
||||
definition: {}
|
||||
};
|
||||
setType(updated);
|
||||
onChange(updated);
|
||||
}}>
|
||||
<option value="string">string</option>
|
||||
<option value="markdown">markdown</option>
|
||||
<option value="enum">enum</option>
|
||||
<option value="list">list</option>
|
||||
<option value="template">template</option>
|
||||
</select>
|
||||
<div className="box">
|
||||
<div className="field">
|
||||
<label className="label">Type</label>
|
||||
<div className="control">
|
||||
<div className="select is-fullwidth">
|
||||
<select
|
||||
value={_type.base_type || ''}
|
||||
onChange={(e) => {
|
||||
const updated = { base_type: e.target.value, definition: {} };
|
||||
updateType(updated);
|
||||
}}
|
||||
>
|
||||
<option value="string">string</option>
|
||||
<option value="markdown">markdown</option>
|
||||
<option value="enum">enum</option>
|
||||
<option value="list">list</option>
|
||||
<option value="template">template</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{renderExtraFields()}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -36,6 +36,17 @@ const MarkdownContent = () => {
|
||||
return <div>Error: {error.message || "Failed to load content"}</div>;
|
||||
}
|
||||
|
||||
if (markdown.isMessage) {
|
||||
return (
|
||||
<div className="markdown-content-container">
|
||||
<div className="notification is-info">
|
||||
<h4 className="title is-4">{markdown.title}</h4>
|
||||
<p>{markdown.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="markdown-content-container">
|
||||
<div className="field has-addons markdown-content-container-header">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
.markdown-editor-container {
|
||||
max-width: 800px;
|
||||
max-width: 90vw;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f8f9fa;
|
||||
@@ -92,4 +92,19 @@ pre {
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
.raw-editor {
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
tab-size: 2;
|
||||
}
|
||||
|
||||
.editor-toggle-button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.json-error {
|
||||
color: red;
|
||||
margin-top: 5px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
@@ -6,10 +6,11 @@ import "./MarkdownEditor.css";
|
||||
import PathManager from "../PathManager";
|
||||
import MarkdownView from "./MarkdownView";
|
||||
import { useMarkdown, useSaveMarkdown } from "../../utils/queries/markdown-queries";
|
||||
import {useMarkdownSetting} from "../../utils/queries/markdown-setting-queries";
|
||||
import {useMarkdownTemplate} from "../../utils/queries/markdown-template-queries";
|
||||
import {useMarkdownSetting, useUpdateMarkdownSetting} from "../../utils/queries/markdown-setting-queries";
|
||||
import {useMarkdownTemplate, useMarkdownTemplates} from "../../utils/queries/markdown-template-queries";
|
||||
import TemplatedEditor from "./TemplatedEditor";
|
||||
import {useMarkdownTemplateSetting} from "../../utils/queries/markdown-template-setting-queries";
|
||||
import {useMarkdownTemplateSetting, useUpdateMarkdownTemplateSetting, useCreateMarkdownTemplateSetting} from "../../utils/queries/markdown-template-setting-queries";
|
||||
import TemplateSelector from "../MarkdownTemplate/TemplateSelector";
|
||||
|
||||
const MarkdownEditor = () => {
|
||||
const { roles } = useContext(AuthContext);
|
||||
@@ -19,23 +20,48 @@ const MarkdownEditor = () => {
|
||||
const [content, setContent] = useState({});
|
||||
const [shortcut, setShortcut] = useState("");
|
||||
const [pathId, setPathId] = useState(1);
|
||||
const [isRawMode, setIsRawMode] = useState(false);
|
||||
const [rawContent, setRawContent] = useState("");
|
||||
const [jsonError, setJsonError] = useState("");
|
||||
const {data: markdown, isLoading, error} = useMarkdown(id);
|
||||
const saveMarkdown = useSaveMarkdown();
|
||||
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);
|
||||
const updateTemplateSetting = useUpdateMarkdownTemplateSetting();
|
||||
const createTemplateSetting = useCreateMarkdownTemplateSetting();
|
||||
const updateSetting = useUpdateMarkdownSetting();
|
||||
const {data: templates} = useMarkdownTemplates();
|
||||
|
||||
const notReady = isLoading || isTemplateFetching || isSettingFetching || isTemplateSettingFetching;
|
||||
useEffect(() => {
|
||||
if(markdown){
|
||||
setTitle(markdown.title);
|
||||
setContent(JSON.parse(markdown.content));
|
||||
setShortcut(markdown.shortcut);
|
||||
setPathId(markdown.path_id);
|
||||
if (markdown.isMessage) {
|
||||
navigate("/");
|
||||
alert(markdown.content || "Cannot edit this markdown");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const parsedContent = JSON.parse(markdown.content);
|
||||
setContent(parsedContent);
|
||||
setRawContent(JSON.stringify(parsedContent, null, 2));
|
||||
setShortcut(markdown.shortcut);
|
||||
setPathId(markdown.path_id);
|
||||
} catch (e) {
|
||||
console.error("Error parsing markdown content:", e);
|
||||
alert("Error parsing markdown content");
|
||||
navigate("/");
|
||||
}
|
||||
}
|
||||
}, [markdown]);
|
||||
}, [markdown, navigate]);
|
||||
|
||||
const handleSave = () => {
|
||||
if (isRawMode && jsonError) {
|
||||
alert("Please fix the JSON errors before saving");
|
||||
return;
|
||||
}
|
||||
|
||||
saveMarkdown.mutate(
|
||||
{id, data: {title, content: JSON.stringify(content), path_id: pathId, shortcut}},
|
||||
{
|
||||
@@ -48,6 +74,83 @@ const MarkdownEditor = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const toggleEditMode = () => {
|
||||
if (isRawMode) {
|
||||
try {
|
||||
const parsed = JSON.parse(rawContent);
|
||||
setContent(parsed);
|
||||
setJsonError("");
|
||||
setIsRawMode(false);
|
||||
} catch (e) {
|
||||
setJsonError("Invalid JSON: " + e.message);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
setRawContent(JSON.stringify(content, null, 2));
|
||||
setIsRawMode(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRawContentChange = (e) => {
|
||||
const newRawContent = e.target.value;
|
||||
setRawContent(newRawContent);
|
||||
try {
|
||||
const parsed = JSON.parse(newRawContent);
|
||||
setContent(parsed);
|
||||
setJsonError("");
|
||||
} catch (e) {
|
||||
setJsonError("Invalid JSON: " + e.message);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTemplateChange = (newTemplate) => {
|
||||
if (!newTemplate) return;
|
||||
|
||||
if (templateSetting) {
|
||||
updateTemplateSetting.mutate(
|
||||
{
|
||||
id: templateSetting.id,
|
||||
data: { 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");
|
||||
if (!hasPermission)
|
||||
@@ -99,19 +202,55 @@ const MarkdownEditor = () => {
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label className="label">Content</label>
|
||||
<div className="control">
|
||||
<TemplatedEditor
|
||||
style={{height: "70vh"}}
|
||||
content={content}
|
||||
<TemplateSelector
|
||||
template={template}
|
||||
onContentChanged={(k, v) => setContent(
|
||||
prev => ({...prev, [k]: v})
|
||||
)}
|
||||
onChange={handleTemplateChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<div className="is-flex is-justify-content-space-between is-align-items-center mb-2">
|
||||
<label className="label mb-0">Content</label>
|
||||
<button
|
||||
type="button"
|
||||
className={`button is-small editor-toggle-button ${isRawMode ? 'is-info' : 'is-light'}`}
|
||||
onClick={toggleEditMode}
|
||||
>
|
||||
{isRawMode ? 'Switch to Template Editor' : 'Switch to Raw Editor'}
|
||||
</button>
|
||||
</div>
|
||||
<div className="control">
|
||||
{isRawMode ? (
|
||||
<div>
|
||||
<p className="help mb-2">
|
||||
Edit the JSON directly. Make sure it's valid JSON before saving.
|
||||
</p>
|
||||
<textarea
|
||||
className={`textarea raw-editor ${jsonError ? 'is-danger' : ''}`}
|
||||
style={{height: "70vh"}}
|
||||
value={rawContent}
|
||||
onChange={handleRawContentChange}
|
||||
placeholder="Enter JSON content here"
|
||||
/>
|
||||
{jsonError && (
|
||||
<p className="help is-danger json-error">{jsonError}</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<TemplatedEditor
|
||||
style={{height: "70vh"}}
|
||||
content={content}
|
||||
template={template}
|
||||
onContentChanged={(k, v) => setContent(
|
||||
prev => ({...prev, [k]: v})
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<div className="control">
|
||||
<button
|
||||
@@ -137,4 +276,3 @@ const MarkdownEditor = () => {
|
||||
};
|
||||
|
||||
export default MarkdownEditor;
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@ const Translate = ({variable, value}) => {
|
||||
if (!variable.type.extend_type)
|
||||
return [];
|
||||
|
||||
return value.map((item, index) => Translate({
|
||||
return (value || []).map((item, index) => Translate({
|
||||
variable: {name: index, type: variable.type.extend_type},
|
||||
value: item,
|
||||
})).map((item) => variable.type.definition.iter_layout.replaceAll('<item/>', item));
|
||||
})).map((item) => variable.type.definition.iter_layout.replaceAll('<item/>', item)).join("");
|
||||
}
|
||||
if(variable.type.base_type === "template"){
|
||||
return ParseTemplate({
|
||||
|
||||
@@ -1,172 +1,177 @@
|
||||
import React, {useState} from "react";
|
||||
import {useMarkdownTemplate} from "../../utils/queries/markdown-template-queries";
|
||||
|
||||
const TemplatedEditorComponent = ({ variable, value={}, namespace, onContentChanged }) => {
|
||||
if(variable.type.base_type === "string") {
|
||||
return (
|
||||
<div>
|
||||
<label>{namespace}{variable.name} - {variable.type.base_type}</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input"
|
||||
value={value ?? ""}
|
||||
onChange={(e) => onContentChanged(variable.name, e.target.value)}
|
||||
/>
|
||||
<hr/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (variable.type.base_type === 'markdown') {
|
||||
return(
|
||||
<div>
|
||||
<label>{namespace}{variable.name} - {variable.type.base_type}</label>
|
||||
<textarea
|
||||
className="textarea"
|
||||
value={value}
|
||||
onChange={(e) => onContentChanged(variable.name, e.target.value)}
|
||||
/>
|
||||
<hr/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const TemplatedEditorComponent = ({ variable, value, namespace, onContentChanged }) => {
|
||||
console.log("variable", variable);
|
||||
const __namespace = `${variable.name}(${variable.type.base_type})`;
|
||||
|
||||
if(variable.type.base_type === "enum"){
|
||||
return (
|
||||
<div>
|
||||
<label>{namespace}{variable.name} - {variable.type.base_type}</label>
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) => onContentChanged(variable.name, e.target.value)}
|
||||
>
|
||||
{variable.type.definition.enums.map((item) => (
|
||||
<option key={item} value={item}>{item}</option>
|
||||
))}
|
||||
</select>
|
||||
<hr/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if(variable.type.base_type === "list"){
|
||||
const [cache, setCache] = useState(value);
|
||||
const defaultValue = variable.type.definition.default || undefined;
|
||||
const addItem = () => {
|
||||
setCache(prev => {
|
||||
const newCache = [...prev, defaultValue];
|
||||
onContentChanged(variable.name, newCache);
|
||||
return newCache;
|
||||
});
|
||||
};
|
||||
const removeItem = (index) => {
|
||||
setCache(prev => {
|
||||
const newCache = prev.filter((_, i) => i!==index);
|
||||
onContentChanged(variable.name, newCache);
|
||||
return newCache;
|
||||
})
|
||||
};
|
||||
const _onContentChanged = (index, value) => {
|
||||
setCache(prev => {
|
||||
const newCache = [...prev];
|
||||
newCache[index] = value;
|
||||
onContentChanged(variable.name, newCache);
|
||||
return newCache;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label>{namespace}{variable.name} - {variable.type.base_type}[{variable.type.extend_type.base_type}]</label>
|
||||
{cache.map((item, index) =>
|
||||
(<div key={index}>
|
||||
<TemplatedEditorComponent
|
||||
variable={{name: index, type: variable.type.extend_type}}
|
||||
value={item}
|
||||
namespace={namespace}
|
||||
onContentChanged={_onContentChanged}
|
||||
/>
|
||||
<button
|
||||
className="button is-danger"
|
||||
type="button"
|
||||
onClick={() => removeItem(index)}
|
||||
>
|
||||
DELETE
|
||||
</button>
|
||||
</div>)
|
||||
)}
|
||||
<button className="button is-warning" type="button" onClick={addItem}>
|
||||
ADD
|
||||
</button>
|
||||
<hr/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if(variable.type.base_type === 'template'){
|
||||
const {data: _template, isFetching: _templateIsFetching} = useMarkdownTemplate(variable.type.definition.template.id);
|
||||
if(_templateIsFetching){
|
||||
return(<p>Loading...</p>);
|
||||
}
|
||||
const _parameters = _template.parameters;
|
||||
const _namespace = namespace + _template.title+ "::"
|
||||
const _onSubChanged = (subKey, subValue) => {
|
||||
const updated = {
|
||||
...value,
|
||||
[subKey]: subValue
|
||||
};
|
||||
onContentChanged(variable.name, updated);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<label>{namespace}{variable.name} - {variable.type.base_type}</label>
|
||||
{
|
||||
_parameters.map((_variable, index) => (
|
||||
<div key={index}>
|
||||
<TemplatedEditorComponent
|
||||
variable={_variable}
|
||||
value={value[_variable.name]}
|
||||
namespace={_namespace}
|
||||
onContentChanged={_onSubChanged}
|
||||
const renderField = () => {
|
||||
switch (variable.type.base_type) {
|
||||
case "string":
|
||||
return (
|
||||
<div className="box has-background-danger-soft">
|
||||
<label className="label">{__namespace}</label>
|
||||
<div className="control">
|
||||
<input
|
||||
type="text"
|
||||
className="input"
|
||||
value={value ?? ""}
|
||||
onChange={(e) => onContentChanged(variable.name, e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
<hr/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
case "markdown":
|
||||
return (
|
||||
<div className="box has-background-primary-soft">
|
||||
<label className="label">{__namespace}</label>
|
||||
<div className="control">
|
||||
<textarea
|
||||
className="textarea"
|
||||
value={value}
|
||||
onChange={(e) => onContentChanged(variable.name, e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case "enum":
|
||||
return (
|
||||
<div className="box has-background-info-soft">
|
||||
<label className="label">{__namespace}</label>
|
||||
<div className="control">
|
||||
<div className="select is-fullwidth">
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) => onContentChanged(variable.name, e.target.value)}
|
||||
>
|
||||
{variable.type.definition.enums.map((item) => (
|
||||
<option key={item} value={item}>{item}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case "list": {
|
||||
const [cache, setCache] = useState(value || []);
|
||||
const defaultValue = variable.type.definition.default;
|
||||
|
||||
const addItem = () => {
|
||||
const newCache = [...cache, defaultValue];
|
||||
setCache(newCache);
|
||||
onContentChanged(variable.name, newCache);
|
||||
};
|
||||
|
||||
const removeItem = (index) => {
|
||||
const newCache = cache.filter((_, i) => i !== index);
|
||||
setCache(newCache);
|
||||
onContentChanged(variable.name, newCache);
|
||||
};
|
||||
|
||||
const onItemChange = (index, val) => {
|
||||
const newCache = [...cache];
|
||||
newCache[index] = val;
|
||||
setCache(newCache);
|
||||
onContentChanged(variable.name, newCache);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="box has-background-white-soft">
|
||||
<label className="label">{__namespace}</label>
|
||||
{cache.map((item, idx) => (
|
||||
<div className="field is-grouped" key={idx}>
|
||||
<div className="control is-expanded">
|
||||
<TemplatedEditorComponent
|
||||
variable={{ name: idx, type: variable.type.extend_type }}
|
||||
value={item}
|
||||
namespace={__namespace}
|
||||
onContentChanged={(subKey, subVal) => onItemChange(idx, subVal)}
|
||||
/>
|
||||
</div>
|
||||
<div className="control">
|
||||
<button
|
||||
className="button is-danger"
|
||||
type="button"
|
||||
onClick={() => removeItem(idx)}
|
||||
>
|
||||
DELETE
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="field">
|
||||
<div className="control">
|
||||
<button
|
||||
className="button is-warning"
|
||||
type="button"
|
||||
onClick={addItem}
|
||||
>
|
||||
ADD
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
case "template": {
|
||||
const { data: _template, isFetching: loading } = useMarkdownTemplate(variable.type.definition.template.id);
|
||||
if (loading) return <p>Loading...</p>;
|
||||
const _parameters = _template.parameters;
|
||||
|
||||
const handleSubChange = (key, val) => {
|
||||
const updated = { ...(value || {}), [key]: val };
|
||||
onContentChanged(variable.name, updated);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="box has-background-grey-light">
|
||||
<label className="label">{__namespace}</label>
|
||||
{_parameters.map((param, i) => (
|
||||
<div className="field" key={i}>
|
||||
<TemplatedEditorComponent
|
||||
variable={param}
|
||||
value={(value || {})[param.name]}
|
||||
namespace={__namespace}
|
||||
onContentChanged={handleSubChange}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return <>{renderField()}</>;
|
||||
};
|
||||
|
||||
const TemplatedEditor = ({content, template, onContentChanged}) => {
|
||||
if(!template){
|
||||
template = {
|
||||
parameters: [
|
||||
{
|
||||
name: "markdown",
|
||||
type: {
|
||||
base_type: "markdown",
|
||||
definition: {}
|
||||
}
|
||||
}
|
||||
],
|
||||
layout: "<markdown/>",
|
||||
title: "default"
|
||||
};
|
||||
}
|
||||
return(
|
||||
<div>
|
||||
{
|
||||
template.parameters.map((variable, index) => (
|
||||
<div key={index}>
|
||||
<TemplatedEditorComponent
|
||||
variable={variable}
|
||||
value={content[variable.name]}
|
||||
namespace={template.title+"::"}
|
||||
onContentChanged={onContentChanged}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
const TemplatedEditor = ({ content, template, onContentChanged }) => {
|
||||
const tpl = template || {
|
||||
parameters: [{ name: "markdown", type: { base_type: "markdown", definition: {} } }],
|
||||
layout: "<markdown/>",
|
||||
title: "default",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="box">
|
||||
{tpl.parameters.map((variable, idx) => (
|
||||
<TemplatedEditorComponent
|
||||
key={idx}
|
||||
variable={variable}
|
||||
value={content[variable.name]}
|
||||
namespace={tpl.title}
|
||||
onContentChanged={onContentChanged}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TemplatedEditor;
|
||||
@@ -2,6 +2,7 @@ import {useCreateMarkdownSetting, useMarkdownSetting} from "../../utils/queries/
|
||||
import {useSaveMarkdown} from "../../utils/queries/markdown-queries";
|
||||
import React, {useState} from "react";
|
||||
import MarkdownTemplateSettingPanel from "../Settings/MarkdownSettings/MarkdownTemplateSettingPanel";
|
||||
import MarkdownPermissionSettingPanel from "../Settings/MarkdownSettings/MarkdownPermissionSettingPanel";
|
||||
|
||||
const MarkdownSettingModal = ({isOpen, markdown, onClose}) => {
|
||||
const {data: markdownSetting, isFetching: markdownSettingIsFetching} = useMarkdownSetting(markdown?.setting_id || 0);
|
||||
@@ -47,6 +48,9 @@ const MarkdownSettingModal = ({isOpen, markdown, onClose}) => {
|
||||
<li className={activeTab==="template" ? "is-active" : ""}>
|
||||
<a onClick={() => setActiveTab("template")}>Template</a>
|
||||
</li>
|
||||
<li className={activeTab==="permission" ? "is-active" : ""}>
|
||||
<a onClick={() => setActiveTab("permission")}>Permission</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{activeTab === "template" && (
|
||||
@@ -55,6 +59,12 @@ const MarkdownSettingModal = ({isOpen, markdown, onClose}) => {
|
||||
onClose={onClose}
|
||||
/>
|
||||
)}
|
||||
{activeTab === "permission" && (
|
||||
<MarkdownPermissionSettingPanel
|
||||
markdownSetting={markdownSetting}
|
||||
onClose={onClose}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
) : (
|
||||
<section className="modal-card-body">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
background-color: #f9f9f9;
|
||||
height: 90vh;
|
||||
overflow-y: hidden;
|
||||
min-width: 25vw;
|
||||
min-width: 15vw;
|
||||
}
|
||||
|
||||
.menu-label {
|
||||
@@ -58,4 +58,25 @@
|
||||
}
|
||||
.markdown-node {
|
||||
background-color: #fffbe6 !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.tabs ul {
|
||||
display: flex;
|
||||
margin-bottom: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.tabs li {
|
||||
flex: 1;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.side-nav {
|
||||
min-width: 15vw;
|
||||
border-right: 1px solid #ddd;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -1,95 +1,50 @@
|
||||
import PermissionGuard from "../PermissionGuard";
|
||||
import PathNode from "./PathNode";
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import "./SideNavigation.css";
|
||||
import {useDeletePath, usePaths, useUpdatePath} from "../../utils/queries/path-queries";
|
||||
import React from 'react';
|
||||
import {useTree} from "../../utils/queries/tree-queries";
|
||||
import TreeTab from "./SideTabs/TreeTab";
|
||||
import TemplateTab from "./SideTabs/TemplateTab";
|
||||
import { AuthContext } from "../../AuthProvider";
|
||||
|
||||
const SideNavigation = () => {
|
||||
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 { roles } = useContext(AuthContext);
|
||||
const [selectedTab, setSelectedTab] = React.useState("tree");
|
||||
|
||||
const allTabs = [
|
||||
{ id: "tree", label: "Tree", component: <TreeTab /> },
|
||||
{ id: "templates", label: "Templates", component: <TemplateTab /> },
|
||||
];
|
||||
|
||||
const visibleTabs = roles.includes("admin")
|
||||
? allTabs
|
||||
: allTabs.filter(tab => tab.id === "tree");
|
||||
|
||||
useEffect(() => {
|
||||
if (!visibleTabs.find(tab => tab.id === selectedTab)) {
|
||||
setSelectedTab(visibleTabs[0]?.id || "");
|
||||
}
|
||||
};
|
||||
}, [visibleTabs, selectedTab]);
|
||||
|
||||
|
||||
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);
|
||||
const current = visibleTabs.find(t => t.id === selectedTab);
|
||||
|
||||
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 <aside className="menu"><p>Loading...</p></aside>;
|
||||
if (error) return <aside className="menu"><p>Error loading tree</p></aside>;
|
||||
return (
|
||||
<aside className="menu">
|
||||
<div className="control is-expanded">
|
||||
<input
|
||||
className="input is-small"
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
/>
|
||||
<aside className="side-nav">
|
||||
<div className="tabs is-small">
|
||||
<ul>
|
||||
{visibleTabs.map(tab => (
|
||||
<li
|
||||
key={tab.id}
|
||||
className={tab.id === selectedTab ? "is-active" : ""}
|
||||
>
|
||||
<a onClick={() => setSelectedTab(tab.id)}>
|
||||
{tab.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="tab-content">
|
||||
{current?.component}
|
||||
</div>
|
||||
<PermissionGuard rolesRequired={["admin", "creator"]}>
|
||||
<a
|
||||
href="/markdown/create"
|
||||
className="button is-primary is-small"
|
||||
>
|
||||
Create New Markdown
|
||||
</a>
|
||||
<a
|
||||
href="/template/create"
|
||||
className="button is-primary is-small"
|
||||
>
|
||||
Create New Template
|
||||
</a>
|
||||
</PermissionGuard>
|
||||
{!filteredTree || filteredTree.length === 0 ?
|
||||
<p>No Result</p> :
|
||||
<PathNode
|
||||
key={1}
|
||||
path={filteredTree}
|
||||
isRoot={true}
|
||||
onSave={handleSave}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
}
|
||||
|
||||
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
62
src/components/Navigations/SideTabs/TemplateTab.js
Normal file
62
src/components/Navigations/SideTabs/TemplateTab.js
Normal file
@@ -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 <p>Loading...</p>;
|
||||
if (error) return <p>Error loading templates</p>;
|
||||
|
||||
return (
|
||||
<aside className="menu">
|
||||
<div className="control is-expanded">
|
||||
<input
|
||||
className="input is-small"
|
||||
type="text"
|
||||
placeholder="Search templates..."
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<PermissionGuard rolesRequired={["admin", "creator"]}>
|
||||
<a
|
||||
href="/template/create"
|
||||
className="button is-primary is-small is-fullwidth"
|
||||
style={{ marginBottom: "10px" }}
|
||||
>
|
||||
Create New Template
|
||||
</a>
|
||||
</PermissionGuard>
|
||||
|
||||
{!filteredTemplates || filteredTemplates.length === 0 ? (
|
||||
<p>No templates found</p>
|
||||
) : (
|
||||
<div className="template-list">
|
||||
{filteredTemplates.map(template => (
|
||||
<button
|
||||
key={template.id}
|
||||
className="button is-light is-fullwidth template-button"
|
||||
onClick={() => handleTemplateClick(template.id)}
|
||||
style={{ marginBottom: "5px", textAlign: "left", justifyContent: "flex-start" }}
|
||||
>
|
||||
{template.title}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
export default TemplateTab;
|
||||
88
src/components/Navigations/SideTabs/TreeTab.js
Normal file
88
src/components/Navigations/SideTabs/TreeTab.js
Normal file
@@ -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 <p>Loading...</p>;
|
||||
if (error) return <p>Error loading tree</p>;
|
||||
return (
|
||||
<aside className="menu">
|
||||
|
||||
<div className="control is-expanded">
|
||||
<input
|
||||
className="input is-small"
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<PermissionGuard rolesRequired={["admin", "creator"]}>
|
||||
<a
|
||||
href="/markdown/create"
|
||||
className="button is-primary is-small"
|
||||
>
|
||||
Create New Markdown
|
||||
</a>
|
||||
</PermissionGuard>
|
||||
{!filteredTree || filteredTree.length === 0 ?
|
||||
<p>No Result</p> :
|
||||
<PathNode
|
||||
key={1}
|
||||
path={filteredTree}
|
||||
isRoot={true}
|
||||
onSave={handleSave}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
}
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
export default TreeTab;
|
||||
@@ -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 (<p>Loading...</p>);
|
||||
}
|
||||
|
||||
return setting ? (
|
||||
<div className="box" style={{marginTop: "1rem"}}>
|
||||
<h4 className="title is-5">Permission Setting</h4>
|
||||
<div className="field">
|
||||
<label className="label">Permission</label>
|
||||
<div className="select is-fullwidth">
|
||||
<select
|
||||
value={permission}
|
||||
onChange={(e) => setPermission(e.target.value)}
|
||||
>
|
||||
<option value="">(None)</option>
|
||||
<option value="public">public</option>
|
||||
<option value="protected">protected</option>
|
||||
<option value="private">private</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="button is-primary"
|
||||
type="button"
|
||||
onClick={handleSaveMarkdownPermissionSetting}
|
||||
>
|
||||
Save Permission Setting
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
className="button is-primary"
|
||||
type="button"
|
||||
onClick={handleCreatePermissionSetting}
|
||||
>
|
||||
Create Permission Setting
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarkdownPermissionSettingPanel;
|
||||
75
src/utils/queries/markdown-permission-setting-queries.js
Normal file
75
src/utils/queries/markdown-permission-setting-queries.js
Normal file
@@ -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");
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user