diff --git a/src/components/Markdown/Editor.tsx b/src/components/Markdown/Editor.tsx index cca249d..22fe339 100644 --- a/src/components/Markdown/Editor.tsx +++ b/src/components/Markdown/Editor.tsx @@ -8,9 +8,10 @@ import './index.scss'; interface MarkdownEditorProps { value?: string; onChange?: (v: string) => void; + mode?: string; } -const MarkdownEditor: FC = ({ value = '', onChange }) => { +const MarkdownEditor: FC = ({ value = '', onChange, mode = 'split' }) => { const [content, setContent] = useState(value); useEffect(() => { @@ -23,20 +24,26 @@ const MarkdownEditor: FC = ({ value = '', onChange }) => { onChange && onChange(e); } + const isSplit = mode === 'split'; + return (
- - - - - - {content} - + {['md', 'split'].includes(mode) && ( + + + + )} + {isSplit && } + {['doc', 'split'].includes(mode) && ( + + {content} + + )}
) diff --git a/src/components/Tags/index.tsx b/src/components/Tags/index.tsx index b88fa88..dd349e4 100644 --- a/src/components/Tags/index.tsx +++ b/src/components/Tags/index.tsx @@ -8,9 +8,11 @@ import { DISABLE_AUTO_COMPLETE } from '@/utils'; interface TagsProps { value?: string[]; onChange?: (v: string[]) => void; + addTxt?: string; + max?: number; } -const Tags: FC = ({ value = [], onChange }) => { +const Tags: FC = ({ max = 99, value = [], onChange, addTxt = 'New Tag' }) => { const [tags, setTags] = useState(value); const [inputVisible, setInputVisible] = useState(false); const [inputValue, setInputValue] = useState(''); @@ -86,9 +88,9 @@ const Tags: FC = ({ value = [], onChange }) => { {...DISABLE_AUTO_COMPLETE} /> )} - {!inputVisible && ( + {!inputVisible && tags.length < max && ( - New Tag + {addTxt} )} diff --git a/src/icons/SplitIcon.tsx b/src/icons/SplitIcon.tsx new file mode 100644 index 0000000..4192674 --- /dev/null +++ b/src/icons/SplitIcon.tsx @@ -0,0 +1,17 @@ +import Icon from "@ant-design/icons"; +import type { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon'; + +interface IconProps { + onClick: () => void; +} + +export default function SplitIcon(props: Partial) { + return ( + + + + )} + /> +} diff --git a/src/layout/index.tsx b/src/layout/index.tsx index d5397f9..e100e9b 100644 --- a/src/layout/index.tsx +++ b/src/layout/index.tsx @@ -66,7 +66,7 @@ export default function ChatLayout() { theme={ appInfo.appTheme === "dark" ? "dark" : "light" } inlineIndent={12} items={menuItems} - defaultOpenKeys={['/model']} + // defaultOpenKeys={['/model']} onClick={(i) => go(i.key)} /> diff --git a/src/main.scss b/src/main.scss index cbaf8cc..8bf3c9f 100644 --- a/src/main.scss +++ b/src/main.scss @@ -57,6 +57,7 @@ html, body { margin-bottom: 5px; } +.chat-tags, .chat-prompts-tags { .ant-tag { margin: 2px; @@ -95,4 +96,8 @@ html, body { cursor: pointer; text-decoration: underline; } +} + +.chatico { + cursor: pointer; } \ No newline at end of file diff --git a/src/routes.tsx b/src/routes.tsx index 187c2b6..5e17488 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -7,10 +7,12 @@ import { UserOutlined, DownloadOutlined, FormOutlined, + GlobalOutlined, } from '@ant-design/icons'; import type { MenuProps } from 'antd'; -import General from '@view/General'; +import General from '@/view/General'; +import Awesome from '@/view/awesome'; import UserCustom from '@/view/model/UserCustom'; import SyncPrompts from '@/view/model/SyncPrompts'; import SyncCustom from '@/view/model/SyncCustom'; @@ -35,10 +37,10 @@ type ChatRouteObject = { export const routes: Array = [ { path: '/', - element: , + element: , meta: { - label: 'General', - icon: , + label: 'Awesome', + icon: , }, }, { @@ -101,6 +103,14 @@ export const routes: Array = [ icon: , }, }, + { + path: '/general', + element: , + meta: { + label: 'General', + icon: , + }, + }, ]; type MenuItem = Required['items'][number]; diff --git a/src/utils.ts b/src/utils.ts index ba74daf..c65f117 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -6,9 +6,11 @@ export const CHAT_CONF_JSON = 'chat.conf.json'; export const CHAT_MODEL_JSON = 'chat.model.json'; export const CHAT_MODEL_CMD_JSON = 'chat.model.cmd.json'; export const CHAT_DOWNLOAD_JSON = 'chat.download.json'; +export const CHAT_AWESOME_JSON = 'chat.awesome.json'; export const CHAT_NOTES_JSON = 'chat.notes.json'; export const CHAT_PROMPTS_CSV = 'chat.prompts.csv'; export const GITHUB_PROMPTS_CSV_URL = 'https://raw.githubusercontent.com/f/awesome-chatgpt-prompts/main/prompts.csv'; + export const DISABLE_AUTO_COMPLETE = { autoCapitalize: 'off', autoComplete: 'off', diff --git a/src/view/awesome/Form.tsx b/src/view/awesome/Form.tsx new file mode 100644 index 0000000..aa55668 --- /dev/null +++ b/src/view/awesome/Form.tsx @@ -0,0 +1,63 @@ +import { useEffect, ForwardRefRenderFunction, useImperativeHandle, forwardRef } from 'react'; +import { Form, Input, Switch } from 'antd'; +import type { FormProps } from 'antd'; + +import Tags from '@comps/Tags'; +import { DISABLE_AUTO_COMPLETE } from '@/utils'; + +interface AwesomeFormProps { + record?: Record | null; +} + +const initFormValue = { + title: '', + url: '', + enable: true, + tags: [], + category: '', +}; + +const AwesomeForm: ForwardRefRenderFunction = ({ record }, ref) => { + const [form] = Form.useForm(); + useImperativeHandle(ref, () => ({ form })); + + useEffect(() => { + if (record) { + form.setFieldsValue(record); + } + }, [record]); + + return ( +
+ + + + + + + + + + + + + + + +
+ ) +} + +export default forwardRef(AwesomeForm); diff --git a/src/view/awesome/config.tsx b/src/view/awesome/config.tsx new file mode 100644 index 0000000..208c752 --- /dev/null +++ b/src/view/awesome/config.tsx @@ -0,0 +1,68 @@ +import { Tag, Space, Popconfirm, Switch } from 'antd'; + +export const awesomeColumns = () => [ + { + title: 'Title', + dataIndex: 'title', + fixed: 'left', + key: 'title', + width: 160, + }, + { + title: 'URL', + dataIndex: 'url', + key: 'url', + width: 120, + }, + // { + // title: 'Icon', + // dataIndex: 'icon', + // key: 'icon', + // width: 120, + // }, + { + title: 'Enable', + dataIndex: 'enable', + key: 'enable', + width: 80, + render: (v: boolean = true, row: Record, action: Record) => ( + action.setRecord({ ...row, enable: v }, 'enable')} /> + ), + }, + { + title: 'Category', + dataIndex: 'category', + key: 'category', + width: 200, + render: (v: string) => {v} + }, + { + title: 'Tags', + dataIndex: 'tags', + key: 'tags', + width: 150, + render: (v: string[]) => ( + {v?.map(i => {i})} + ), + }, + { + title: 'Action', + fixed: 'right', + width: 150, + render: (_: any, row: any, actions: any) => { + return ( + + actions.setRecord(row, 'edit')}>Edit + actions.setRecord(row, 'delete')} + okText="Yes" + cancelText="No" + > + Delete + + + ) + } + } +]; diff --git a/src/view/awesome/index.tsx b/src/view/awesome/index.tsx new file mode 100644 index 0000000..1660d98 --- /dev/null +++ b/src/view/awesome/index.tsx @@ -0,0 +1,125 @@ +import { useRef, useEffect, useState } from 'react'; +import { Table, Modal, Popconfirm, Button, message } from 'antd'; + +import useJson from '@/hooks/useJson'; +import useData from '@/hooks/useData'; +import useColumns from '@/hooks/useColumns'; +import FilePath from '@/components/FilePath'; +import { CHAT_AWESOME_JSON } from '@/utils'; +import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable'; +import { awesomeColumns } from './config'; +import AwesomeForm from './Form'; + +export default function Awesome() { + const formRef = useRef(null); + const [isVisible, setVisible] = useState(false); + const { opData, opInit, opAdd, opReplace, opReplaceItems, opRemove, opSafeKey } = useData([]); + const { columns, ...opInfo } = useColumns(awesomeColumns()); + const { rowSelection, selectedRowIDs } = useTableRowSelection(); + const { json, updateJson } = useJson(CHAT_AWESOME_JSON); + const selectedItems = rowSelection.selectedRowKeys || []; + + useEffect(() => { + if (!json || json.length <= 0) return; + opInit(json); + }, [json?.length]); + + useEffect(() => { + if (!opInfo.opType) return; + if (['edit', 'new'].includes(opInfo.opType)) { + setVisible(true); + } + if (['delete'].includes(opInfo.opType)) { + const data = opRemove(opInfo?.opRecord?.[opSafeKey]); + updateJson(data); + opInfo.resetRecord(); + } + }, [opInfo.opType, formRef]); + + const hide = () => { + setVisible(false); + opInfo.resetRecord(); + }; + + useEffect(() => { + if (opInfo.opType === 'enable') { + const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord); + updateJson(data); + } + }, [opInfo.opTime]) + + const handleDelete = () => { + + }; + + const handleOk = () => { + formRef.current?.form?.validateFields() + .then(async (vals: Record) => { + if (opInfo.opType === 'new') { + const data = opAdd(vals); + await updateJson(data); + opInit(data); + message.success('Data added successfully'); + } + if (opInfo.opType === 'edit') { + const data = opReplace(opInfo?.opRecord?.[opSafeKey], vals); + await updateJson(data); + message.success('Data updated successfully'); + } + hide(); + }) + }; + + const handleEnable = (isEnable: boolean) => { + const data = opReplaceItems(selectedRowIDs, { enable: isEnable }) + updateJson(data); + }; + + const modalTitle = `${({ new: 'Create', edit: 'Edit' })[opInfo.opType]} URL`; + + return ( +
+
+ +
+ {selectedItems.length > 0 && ( + <> + + + + + + Selected {selectedItems.length} items + + )} +
+
+ + + + + + + ) +} \ No newline at end of file diff --git a/src/view/download/index.tsx b/src/view/download/index.tsx index f42b539..4192651 100644 --- a/src/view/download/index.tsx +++ b/src/view/download/index.tsx @@ -105,7 +105,7 @@ export default function Download() { okText="Yes" cancelText="No" > - + Selected {selectedItems.length} items diff --git a/src/view/markdown/index.scss b/src/view/markdown/index.scss index 3b61798..71f7cc5 100644 --- a/src/view/markdown/index.scss +++ b/src/view/markdown/index.scss @@ -1,6 +1,8 @@ .md-task { margin-bottom: 5px; + display: flex; + justify-content: space-between; .ant-breadcrumb-link { padding: 3px 5px; diff --git a/src/view/markdown/index.tsx b/src/view/markdown/index.tsx index ed1a165..088dd9b 100644 --- a/src/view/markdown/index.tsx +++ b/src/view/markdown/index.tsx @@ -6,12 +6,20 @@ import MarkdownEditor from '@/components/Markdown/Editor'; import { fs, shell } from '@tauri-apps/api'; import useInit from '@/hooks/useInit'; +import SplitIcon from '@/icons/SplitIcon'; import { getPath } from '@/view/notes/config'; import './index.scss'; +const modeMap: any = { + 0: 'split', + 1: 'md', + 2: 'doc', +} + export default function Markdown() { const [filePath, setFilePath] = useState(''); const [source, setSource] = useState(''); + const [previewMode, setPreviewMode] = useState(0); const location = useLocation(); const state = location?.state; @@ -25,6 +33,12 @@ export default function Markdown() { await fs.writeTextFile(filePath, v); }; + const handlePreview = () => { + let mode = previewMode + 1; + if (mode > 2) mode = 0; + setPreviewMode(mode); + }; + return ( <>
@@ -36,8 +50,11 @@ export default function Markdown() { {filePath} +
+ +
- + ); } \ No newline at end of file diff --git a/src/view/model/SyncCustom/Form.tsx b/src/view/model/SyncCustom/Form.tsx index adbb4c0..fbf3e3a 100644 --- a/src/view/model/SyncCustom/Form.tsx +++ b/src/view/model/SyncCustom/Form.tsx @@ -84,14 +84,14 @@ const SyncForm: ForwardRefRenderFunction = ({ record, - + { formRef.current?.form?.validateFields() .then((vals: Record) => { - let data = []; - switch (opInfo.opType) { - case 'new': data = opAdd(vals); break; - case 'edit': data = opReplace(opInfo?.opRecord?.[opSafeKey], vals); break; - default: break; + if (opInfo.opType === 'new') { + const data = opAdd(vals); + modelSet(data); + message.success('Data added successfully'); + } + if (opInfo.opType === 'edit') { + const data = opReplace(opInfo?.opRecord?.[opSafeKey], vals); + modelSet(data); + message.success('Data updated successfully'); } - modelSet(data); hide(); }) }; diff --git a/src/view/model/UserCustom/Form.tsx b/src/view/model/UserCustom/Form.tsx index 92e7a5b..c810a77 100644 --- a/src/view/model/UserCustom/Form.tsx +++ b/src/view/model/UserCustom/Form.tsx @@ -35,16 +35,16 @@ const UserCustomForm: ForwardRefRenderFunction = - + - + @@ -55,9 +55,9 @@ const UserCustomForm: ForwardRefRenderFunction = - + ) diff --git a/src/view/model/UserCustom/index.tsx b/src/view/model/UserCustom/index.tsx index 5c85e84..a98615a 100644 --- a/src/view/model/UserCustom/index.tsx +++ b/src/view/model/UserCustom/index.tsx @@ -52,14 +52,6 @@ export default function LanguageModel() { } }, [opInfo.opTime]) - - useEffect(() => { - if (opInfo.opType === 'enable') { - const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord); - modelCacheSet(data); - } - }, [opInfo.opTime]); - const handleEnable = (isEnable: boolean) => { const data = opReplaceItems(selectedRowIDs, { enable: isEnable }) modelCacheSet(data); diff --git a/src/view/notes/index.tsx b/src/view/notes/index.tsx index 1ce675a..c007ea1 100644 --- a/src/view/notes/index.tsx +++ b/src/view/notes/index.tsx @@ -95,7 +95,7 @@ export default function Notes() { okText="Yes" cancelText="No" > - + Selected {selectedItems.length} items