add: template editor

This commit is contained in:
h z
2025-04-14 17:02:22 +01:00
parent 09338a2683
commit 947b59e3ea
29 changed files with 1277 additions and 166 deletions

View File

@@ -0,0 +1,65 @@
import React, { useState } from "react";
const EnumsEditor = ({ enums, onChange }) => {
const [_enums, setEnums] = useState(enums || []);
return (
<div className="box">
<ul>
{_enums.map((item, index) => (
<li key={index} className="field has-addons" style={{ marginBottom: "0.5rem" }}>
<div className="control is-expanded">
<input
className="input is-small"
type="text"
value={item}
onChange={(e) => {
const updated = [..._enums];
updated[index] = e.target.value;
setEnums(updated);
onChange(updated);
}}
/>
</div>
<div className="control">
<button
className="button is-small is-danger"
type="button"
onClick={() => {
const updated = [..._enums];
updated.splice(index, 1);
setEnums(updated);
onChange(updated);
}}
>
<span className="icon is-small">
<i className="fas fa-times" />
</span>
</button>
</div>
</li>
))}
</ul>
<div className="field">
<div className="control">
<button
className="button is-small is-primary"
type="button"
onClick={() => {
const updated = [..._enums, ""];
setEnums(updated);
onChange(updated);
}}
>
<span className="icon is-small">
<i className="fas fa-plus" />
</span>
<span>Add Enum</span>
</button>
</div>
</div>
</div>
);
};
export default EnumsEditor;

View File

@@ -0,0 +1,16 @@
import React, {useState} from 'react';
const LayoutEditor = ({layout, onChange}) => {
const [_layout, setLayout] = useState(layout || "");
return (
<textarea
className="textarea"
value={_layout}
onChange={(e) => {
setLayout(e.target.value);
onChange(layout);
}}
/>
);
};
export default LayoutEditor;

View File

@@ -0,0 +1,90 @@
import React, { useContext, useState } from "react";
import { AuthContext } from "../../AuthProvider";
import { useNavigate, useParams } from "react-router-dom";
import { useMarkdownTemplate, useSaveMarkdownTemplate } from "../../utils/queries/template-queries";
import LayoutEditor from "./LayoutEditor";
import ParametersManager from "./ParametersManager";
import "bulma/css/bulma.min.css";
const MarkdownTemplateEditor = () => {
const { roles } = useContext(AuthContext);
if (!roles.includes("admin") || roles.includes("creator"))
return <div className="notification is-danger">Permission Denied</div>;
const navigate = useNavigate();
const { id } = useParams();
const { data: template, isFetching: templateIsFetching } = useMarkdownTemplate(id);
const saveMarkdownTemplate = useSaveMarkdownTemplate();
const [title, setTitle] = useState(template?.id || "");
const [parameters, setParameters] = useState(template?.parameters || []);
const [layout, setLayout] = useState(template?.layout || "");
if (templateIsFetching) {
return <p>Loading...</p>;
}
const handleSave = () => {
saveMarkdownTemplate.mutate(
{ id, data: { title, parameters, layout } },
{
onSuccess: () => {
navigate("/");
},
onError: () => {
alert("Error saving template.");
}
}
);
};
return (
<section className="section">
<div className="container">
<h2 className="title is-4">Markdown Template Editor</h2>
<div className="field">
<label className="label">Title:</label>
<div className="control">
<input
className="input"
type="text"
placeholder="Enter template title"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
</div>
</div>
<div className="columns is-variable is-8">
<div className="column">
<h3 className="title is-5">Layout</h3>
<div className="box">
<LayoutEditor
layout={layout}
parameters={parameters}
onChange={(newLayout) => setLayout(newLayout)}
/>
</div>
</div>
<div className="column">
<h3 className="title is-5">Parameters</h3>
<div className="box">
<ParametersManager
parameters={parameters}
onChange={(newParameters) => setParameters(newParameters)}
/>
</div>
</div>
</div>
<div className="field is-grouped">
<div className="control">
<button className="button is-primary" onClick={handleSave}>
Save Template
</button>
</div>
</div>
</div>
</section>
);
};
export default MarkdownTemplateEditor;

View File

@@ -0,0 +1,84 @@
import React, { useState } from "react";
import TypeEditor from "./TypeEditor";
const ParametersManager = ({ parameters, onChange }) => {
const [_parameters, setParameters] = useState(parameters || []);
const handleAdd = () => {
const updated = [
..._parameters,
{
name: "",
type: { base_type: "string" }
}
];
setParameters(updated);
onChange(updated);
};
const handleNameChange = (index, newName) => {
const updated = [..._parameters];
updated[index].name = newName;
setParameters(updated);
onChange(updated);
};
const handleDelete = (index) => {
const updated = [..._parameters];
updated.splice(index, 1);
setParameters(updated);
onChange(updated);
};
return (
<div className="box">
<div className="field">
<div className="control">
<button className="button is-primary" onClick={handleAdd}>
Add Parameter
</button>
</div>
</div>
{_parameters.map((param, index) => (
<div key={index} className="box" style={{ marginBottom: "1rem" }}>
<div className="field is-grouped is-grouped-multiline">
<div className="control is-expanded">
<label className="label">Name:</label>
<input
type="text"
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 className="field">
<label className="label">Type:</label>
<div className="control">
<TypeEditor
type={param.type}
onChange={(newType) => {
const updated = [..._parameters];
updated[index].type = newType;
setParameters(updated);
onChange(updated);
}}
/>
</div>
</div>
</div>
))}
</div>
);
};
export default ParametersManager;

View File

@@ -0,0 +1,30 @@
import React, {useState} from "react";
import {useMarkdownTemplates} from "../../utils/queries/template-queries";
const TemplateSelector = ({template, onChange}) => {
const [_template, setTemplate] = useState(template || {
title: "",
parameters: [],
layout: ""
});
const {data:templates, isFetching: templatesAreFetching} = useMarkdownTemplates();
if(templatesAreFetching) {
return <p>Loading...</p>
}
return (
<select
value={_template.title}
onChange={(e) => {
setTemplate(e.target.value);
onChange(e.target.value);
}}>
{
templates.map((tmpl, index) => (
<option key={index} value={tmpl} >{tmpl.title}</option>
))
}
</select>
);
};
export default TemplateSelector;

View File

@@ -0,0 +1,94 @@
import React from 'react';
import EnumsEditor from './EnumsEditor';
import TemplateSelector from './TemplateSelector';
const TypeEditor = ({ type, onChange }) => {
const [_type, setType] = React.useState(type || {});
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>
);
case 'template':
return <TemplateSelector
template={_type.definition.template}
onChange={(newTemplate) => {
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>
{renderExtraFields()}
</div>
);
};
export default TypeEditor;

View File

@@ -4,14 +4,18 @@ import "katex/dist/katex.min.css";
import "./MarkdownContent.css";
import MarkdownView from "./MarkdownView";
import PermissionGuard from "../PermissionGuard";
import {useMarkdown} from "../../utils/markdown-queries";
import {usePath} from "../../utils/path-queries";
import {useMarkdown} from "../../utils/queries/markdown-queries";
import {usePath} from "../../utils/queries/path-queries";
import {useMarkdownSetting} from "../../utils/queries/setting-queries";
import {useMarkdownTemplate} from "../../utils/queries/template-queries";
const MarkdownContent = () => {
const { id } = useParams();
const [indexTitle, setIndexTitle] = useState(null);
const {data: markdown, isLoading, error} = useMarkdown(id);
const {data: path, isFetching: isPathFetching} = usePath(markdown?.path_id);
const {data: setting, isFetching: isSettingFetching} = useMarkdownSetting(markdown?.setting_id);
const {data: template_setting, isFetching: isTemplateSettingFetching} = useMarkdownTemplate(setting?.template_setting_id);
useEffect(() => {
@@ -21,7 +25,9 @@ const MarkdownContent = () => {
}, [markdown, path]);
if (isLoading || isPathFetching) {
const notReady = isLoading || isPathFetching || isSettingFetching || isTemplateSettingFetching;
if (notReady) {
return <div>Loading...</div>;
}
@@ -39,8 +45,7 @@ const MarkdownContent = () => {
</Link>
</PermissionGuard>
</div>
<MarkdownView content={markdown.content}/>
<MarkdownView content={JSON.parse(markdown.content)} template={template_setting}/>
</div>
);
};

View File

@@ -5,23 +5,29 @@ import "katex/dist/katex.min.css";
import "./MarkdownEditor.css";
import PathManager from "../PathManager";
import MarkdownView from "./MarkdownView";
import { useMarkdown, useSaveMarkdown } from "../../utils/markdown-queries";
import { useMarkdown, useSaveMarkdown } from "../../utils/queries/markdown-queries";
import {useMarkdownSetting} from "../../utils/queries/setting-queries";
import {useMarkdownTemplate} from "../../utils/queries/template-queries";
import TemplatedEditor from "./TemplatedEditor";
const MarkdownEditor = () => {
const { roles } = useContext(AuthContext);
const navigate = useNavigate();
const { id } = useParams();
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [content, setContent] = useState({});
const [shortcut, setShortcut] = useState("");
const [pathId, setPathId] = useState(1);
const {data: markdown, isLoading, error} = useMarkdown(id);
const saveMarkdown = useSaveMarkdown();
const {data: setting, isFetching: isSettingFetching} = useMarkdownSetting(markdown?.id);
const {data: template, isFetching: isTemplateFetching} = useMarkdownTemplate(setting?.id);
const notReady = isLoading || isTemplateFetching || isSettingFetching;
useEffect(() => {
if(markdown){
setTitle(markdown.title);
setContent(markdown.content);
setContent(JSON.parse(markdown.content));
setShortcut(markdown.shortcut);
setPathId(markdown.path_id);
}
@@ -29,7 +35,7 @@ const MarkdownEditor = () => {
const handleSave = () => {
saveMarkdown.mutate(
{id, data: {title, content, path_id: pathId, shortcut}},
{id, data: {title, content: JSON.stringify(content), path_id: pathId, shortcut}},
{
onSuccess: () => {
navigate("/");
@@ -45,12 +51,12 @@ const MarkdownEditor = () => {
if (!hasPermission)
return <div className="notification is-danger">Permission Denied</div>;
if(isLoading)
if(notReady)
return <p>Loading...</p>;
console.log(markdown?.id);
if(error)
return <p>{error.message || "Failed to load markdown"}</p>;
return (
<div className="container mt-5 markdown-editor-container">
<h2 className="title is-4">{id ? "Edit Markdown" : "Create Markdown"}</h2>
@@ -93,13 +99,14 @@ const MarkdownEditor = () => {
<div className="field">
<label className="label">Content</label>
<div className="control">
<textarea
style={{ height: "70vh" }}
className="textarea"
placeholder="Enter Markdown content"
value={content}
onChange={(e) => setContent(e.target.value)}
></textarea>
<TemplatedEditor
style={{height: "70vh"}}
content={content}
template={template}
onContentChanged={(k, v) => setContent(
prev => ({...prev, [k]: v})
)}
/>
</div>
</div>
@@ -120,7 +127,7 @@ const MarkdownEditor = () => {
<div className="column is-half">
<h3 className="subtitle is-5">Preview</h3>
<MarkdownView content={content} height='70vh'/>
<MarkdownView content={content} template={template} height='70vh'/>
</div>
</div>
</div>

View File

@@ -8,18 +8,68 @@ import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { okaidia } from "react-syntax-highlighter/dist/esm/styles/prism";
import "katex/dist/katex.min.css";
import "./MarkdownView.css";
import {useLinks} from "../../utils/markdown-queries";
import {useLinks} from "../../utils/queries/markdown-queries";
const MarkdownView = ({ content, height="auto" }) => {
const Translate = ({variable, value}) => {
if (variable.type.base_type === "markdown" || variable.type.base_type === "string" || variable.type.base_type === "enum") {
return value;
}
if(variable.type.base_type === "list"){
if (!variable.type.extend_type)
return [];
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));
}
if(variable.type.base_type === "template"){
return ParseTemplate({
template: variable.type.definition.template,
variables: value
});
}
};
const ParseTemplate = ({template, variables}) => {
let res = template.layout;
for (const parameter of template.parameters) {
res = res.replaceAll(`<${parameter.name}/>`, Translate({
variable: parameter,
value: variables[parameter.name]
}));
}
return res;
};
const MarkdownView = ({ content, template, height="auto" }) => {
const {data: links, isLoading} = useLinks();
if (isLoading)
return <p>Loading...</p>
const definitions = "\n<!-- Definitions -->\n" + links.join("\n");
return (<p>Loading...</p>);
const linkDefinitions = "\n<!-- Definitions -->\n" + links.join("\n");
const _template = template || {
parameters: [
{
name: "markdown",
type: {
base_type: "markdown",
definition: {}
}
}
],
layout: "<markdown/>",
title: "default"
};
return (
<div className="markdown-preview" style={{height}}>
<ReactMarkdown
children={content + "\n" + definitions}
children={ParseTemplate({
template: _template,
variables: content
}) + "\n" + linkDefinitions}
remarkPlugins={[remarkMath, remarkGfm]}
rehypePlugins={[rehypeKatex, rehypeRaw]}
components={{

View File

@@ -0,0 +1,172 @@
import React, {useState} from "react";
import {useMarkdownTemplate} from "../../utils/queries/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>
);
}
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}
/>
</div>
))
}
<hr/>
</div>
);
}
};
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>
))
}
</div>
);
};
export default TemplatedEditor;

View File

@@ -0,0 +1,76 @@
import {useCreateMarkdownSetting, useMarkdownSetting} from "../../utils/queries/setting-queries";
import {useSaveMarkdown} from "../../utils/queries/markdown-queries";
import {useState} from "react";
import MarkdownTemplateSettingPanel from "../Settings/MarkdownSettings/MarkdownTemplateSettingPanel";
const MarkdownSettingModal = ({isOpen, markdown, onClose}) => {
const {data: markdownSetting, isFetching: markdownSettingIsFetching} = useMarkdownSetting(markdown?.setting_id || 0);
const createMarkdownSetting = useCreateMarkdownSetting();
const updateMarkdown = useSaveMarkdown();
const [activeTab, setActiveTab] = useState("template");
const handleCreateMarkdownSetting = () => {
createMarkdownSetting.mutate({}, {
onSuccess: (res) => {
updateMarkdown.mutate({
id: markdown.id,
data: {
setting_id: res.id
}
});
}
});
};
if(markdownSettingIsFetching)
return(<p>Loading...</p>);
return (
<div className={`modal ${isOpen ? "is-active" : ""}`}>
<div className="modal-background" onClick={onClose} />
<div className="modal-card" style={{width: "60vw"}}>
<header className="modal-card-head">
<p className="modal-card-title">Markdown Settings</p>
<button
className="delete"
type="button"
aria-label="close"
onClick={onClose}
/>
</header>
{
markdownSetting ? (
<section className="modal-card-body">
<button
className="button is-primary"
type="button"
onClick={handleCreateMarkdownSetting}
>
Create Markdown Setting
</button>
</section>
) : (
<section className="modal-card-body">
<div className="tabs">
<ul>
<li className={activeTab==="template" ? "is-active" : ""}>
<a onClick={() => setActiveTab("template")}>Template</a>
</li>
</ul>
</div>
{activeTab === "template" && (
<MarkdownTemplateSettingPanel
markdown={markdownSetting}
onClose={onClose}
/>
)}
</section>
)
}
</div>
</div>
);
};

View File

@@ -1,5 +1,5 @@
import {useUpdatePath} from "../../utils/path-queries";
import {useCreatePathSetting, usePathSetting} from "../../utils/setting-queries";
import {useUpdatePath} from "../../utils/queries/path-queries";
import {useCreatePathSetting, usePathSetting} from "../../utils/queries/setting-queries";
import WebhookSettingPanel from "../Settings/PathSettings/WebhookSettingPanel";
import React, {useState} from "react";
const PathSettingModal = ({ isOpen, path, onClose }) => {
@@ -18,17 +18,22 @@ const PathSettingModal = ({ isOpen, path, onClose }) => {
if(isPathSettingLoading)
return <p>Loading...</p>
return (<p>Loading...</p>);
return (
<div className={`modal ${isOpen ? "is-active" : ""}`}>
<div className="modal-background" onClick={onClose}></div>
<div className="modal-background" onClick={onClose} />
<div className="modal-card" style={{width: "60vw"}}>
<header className="modal-card-head">
<p className="modal-card-title">Path Settings</p>
<button type="button" className="delete" aria-label="close" onClick={onClose} />
<button
type="button"
className="delete"
aria-label="close"
onClick={onClose}
/>
</header>
{!pathSetting && !isPathSettingLoading ? (
{!pathSetting ? (
<section className="modal-card-body">
<button
type="button"

View File

@@ -72,9 +72,7 @@ const MainNavigation = () => {
console.log(contentDisposition);
if (contentDisposition) {
const match = contentDisposition.match(/filename="?([^"]+)"?/);
console.log(match);
if (match && match[1]) {
console.log(match[1]);
filename = match[1];
}
}

View File

@@ -2,8 +2,8 @@ import React, {useState} from "react";
import { Link } from "react-router-dom";
import PermissionGuard from "../PermissionGuard";
import "./PathNode.css";
import {useDeletePath, useMovePath, useUpdatePath} from "../../utils/path-queries";
import {useIndexMarkdown, useMoveMarkdown} from "../../utils/markdown-queries";
import {useDeletePath, useMovePath, useUpdatePath} from "../../utils/queries/path-queries";
import {useIndexMarkdown, useMoveMarkdown} from "../../utils/queries/markdown-queries";
import MarkdownNode from "./MarkdownNode";
import PathSettingModal from "../Modals/PathSettingModal";
@@ -11,7 +11,7 @@ const PathNode = ({ path, isRoot = false }) => {
const [isPathSettingModalOpen, setIsPathSettingModalOpen] = useState(false);
const [isExpanded, setIsExpanded] = useState(isRoot);
const [isEditing, setIsEditing] = useState(false);
const [newName, setNewName] = useState(path.name);
const [newName, setNewName] = useState(path.name || "");
const deletePath = useDeletePath();
const updatePath = useUpdatePath();
@@ -130,8 +130,6 @@ const PathNode = ({ path, isRoot = false }) => {
<button
className="button is-small is-success"
onClick={() => {
console.log("path", path);
setIsPathSettingModalOpen(true);
}}
type="button"
@@ -217,6 +215,7 @@ const PathNode = ({ path, isRoot = false }) => {
<MarkdownNode
markdown={markdown}
handleMoveMarkdown={handleMoveMarkdown}
key={markdown.id}
/>
))}
</ul>

View File

@@ -1,9 +1,9 @@
import PermissionGuard from "../PermissionGuard";
import PathNode from "./PathNode";
import "./SideNavigation.css";
import {useDeletePath, usePaths, useUpdatePath} from "../../utils/path-queries";
import {useDeletePath, usePaths, useUpdatePath} from "../../utils/queries/path-queries";
import React from 'react';
import {useTree} from "../../utils/tree-queries";
import {useTree} from "../../utils/queries/tree-queries";
const SideNavigation = () => {
const {data: tree, isLoading, error} = useTree();
@@ -71,6 +71,12 @@ const SideNavigation = () => {
>
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> :

View File

@@ -1,15 +1,12 @@
import React, {useEffect, useState, useRef, useContext} from "react";
import {useCreatePath, usePath, usePaths} from "../utils/path-queries";
import {useCreatePath, usePaths} from "../utils/queries/path-queries";
import { useQueryClient } from "react-query";
import "./PathManager.css";
import {fetch_} from "../utils/request-utils";
import {ConfigContext} from "../ConfigProvider";
import PathSettingModal from "./Modals/PathSettingModal";
const PathManager = ({ currentPathId = 1, onPathChange }) => {
const { data: currentPath } = usePath(currentPathId);
const [currentFullPath, setCurrentFullPath] = useState([{ name: "Root", id: 1 }]);
const [searchTerm, setSearchTerm] = useState("");
const [dropdownActive, setDropdownActive] = useState(false);
@@ -20,11 +17,7 @@ const PathManager = ({ currentPathId = 1, onPathChange }) => {
const { data: subPaths, isLoading: isSubPathsLoading, error: subPathsError } = usePaths(currentPathId);
const createPath = useCreatePath();
const config = useContext(ConfigContext).config;
const [isPathSettingModalOpen, setIsPathSettingModalModalOpen] = useState(false);
const handleSettingClick = () => {
setIsPathSettingModalModalOpen(true);
}
const buildFullPath = async (pathId) => {
const path = [];
@@ -117,31 +110,16 @@ const PathManager = ({ currentPathId = 1, onPathChange }) => {
{currentFullPath.map((path, index) => (
<span
key={path.id}
className="breadcrumb-item is-clickable"
className="tag is-clickable is-link is-light"
onClick={() => handlePathClick(path.id, index)}
>
{path.name}
{index < currentFullPath.length - 1 && " / "}
{path.name + "/"}
</span>
))}
</div>
<div className="control">
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
</div>
<div className="control">
<button
className="button is-small is-primary"
type="button"
onClick={handleSettingClick}
>
Settings
</button>
<PathSettingModal
isOpen={isPathSettingModalOpen}
path={currentPath}
onClose={() => setIsPathSettingModalModalOpen(false)}
/>
</div>
</div>
<div className="path-manager-body">

View File

@@ -0,0 +1,85 @@
import {
useCreateMarkdownTemplateSetting,
useMarkdownTemplate,
useMarkdownTemplates,
useMarkdownTemplateSetting, useUpdateMarkdownTemplateSetting
} from "../../../utils/queries/template-queries";
import {useState} from "react";
import {useUpdateMarkdownSetting} from "../../../utils/queries/setting-queries";
const MarkdownTemplateSettingPanel = ({markdownSetting, onClose}) => {
const {data: setting, isFetching: settingIsFetching } = useMarkdownTemplateSetting(markdownSetting.template_setting_id || 0);
const {data: templates, isFetching: templatesAreFetching}=useMarkdownTemplates();
const {data: template, isFetching: templateIsFetching} = useMarkdownTemplate(setting?.template_id);
const [selectedTemplate, setSelectedTemplate] = useState(template);
const createMarkdownTemplateSetting = useCreateMarkdownTemplateSetting();
const updateMarkdownSetting = useUpdateMarkdownSetting();
const updateMarkdownTemplateSetting = useUpdateMarkdownTemplateSetting();
const handleCreateTemplateSetting = () => {
createMarkdownTemplateSetting.mutate({}, {
onSuccess: (data) => {
updateMarkdownSetting.mutate({
id: markdownSetting.id,
data: {
template_setting_id: data.id,
}
});
}
});
};
const handleSaveMarkdownTemplateSetting = () => {
updateMarkdownTemplateSetting.mutate({
id: setting.id,
data: {
template_id: selectedTemplate.id,
}
}, {
onSuccess: () => alert("Saved"),
onError: () => alert("Failed to save"),
});
onClose();
};
if (settingIsFetching || templatesAreFetching || templatesAreFetching || templateIsFetching) {
return (<p>Loading...</p>);
}
return setting ? (
<div className="box" style={{marginTop: "1rem"}}>
<h4 className="title is-5">Template Setting</h4>
<div className="field">
<label className="label">Use Template</label>
<div className="select is-fullwidth">
<select
value={selectedTemplate.title}
onChange={(e) => setSelectedTemplate(e.target.value)}
>
<option value="">(default)</option>
{templates.map((_template, index) => (
<option key={index} value={_template}>{_template.title}</option>
))}
</select>
</div>
</div>
<button
className="button is-primary"
type="button"
onClick={handleSaveMarkdownTemplateSetting}
>
Save Template Setting
</button>
</div>
) : (
<button
className="button is-primary"
type="button"
onClick={handleCreateTemplateSetting}
>
Create Template Setting
</button>
);
};
export default MarkdownTemplateSettingPanel;

View File

@@ -4,14 +4,14 @@ import {
useCreateWebhookSetting,
useUpdateWebhookSetting,
useWebhooks,
} from "../../../utils/webhook-queries";
} from "../../../utils/queries/webhook-queries";
import {
useCreateWebhook,
useUpdateWebhook,
useDeleteWebhook,
} from "../../../utils/webhook-queries";
import {useUpdatePathSetting} from "../../../utils/setting-queries";
} from "../../../utils/queries/webhook-queries";
import {useUpdatePathSetting} from "../../../utils/queries/setting-queries";
const WebhookSettingPanel = ({pathSetting, onClose}) => {
@@ -89,7 +89,12 @@ const WebhookSettingPanel = ({pathSetting, onClose}) => {
const handleCreateWebhookSetting = () => {
createWebhookSetting.mutate({}, {
onSuccess: (data) => {
updatePathSetting.mutate({id: pathSetting.id, data: {webhook_setting_id: data.id}});
updatePathSetting.mutate({
id: pathSetting.id,
data: {
webhook_setting_id: data.id
}
});
}
});
};
@@ -115,9 +120,6 @@ const WebhookSettingPanel = ({pathSetting, onClose}) => {
}
const found = webhooks.find((wh) => wh.id === setting.webhook_id);
console.log("found", found);
console.log("webhooks", webhooks);
console.log("setting.webhook_id", setting.webhook_id);
setSelectedUrl(found ? found.hook_url : "");
} else {
setEnabled(false);
@@ -191,8 +193,6 @@ const WebhookSettingPanel = ({pathSetting, onClose}) => {
on_events: onEvents,
};
console.log(webhooks);
console.log(payload);
if(!setting || !setting.id){
createWebhookSetting.mutate(payload, {
onSuccess: (res) => {