From ad61923f965197ae451525b58c268d7e287e29b7 Mon Sep 17 00:00:00 2001 From: yuanyxh <15766118362@139.com> Date: Sat, 18 May 2024 13:35:26 +0800 Subject: [PATCH] feat: md handle add upload image setting --- src/components/Dialog/Dialog.module.less | 3 + src/components/Dialog/Dialog.tsx | 8 +- src/filehandle/components/BackgroundMenu.tsx | 6 +- .../md_editor/component/MDEditor.tsx | 66 ++++++++++- .../md_editor/component/MDHandle.tsx | 104 ++++++++++++++++++ src/filehandle/md_editor/store/useMDStore.ts | 38 +++++++ src/filehandle/md_editor/utils/index.ts | 18 +++ src/hooks/index.ts | 2 +- src/hooks/useResizeObserver.ts | 2 +- src/router/components/Loading.tsx | 8 +- src/utils/localStorageTools.ts | 3 +- src/viewer/Layout.tsx | 2 +- src/viewer/ProfileLayout.tsx | 4 +- src/viewer/components/Feedback.tsx | 5 +- vite.config.ts | 6 +- 15 files changed, 253 insertions(+), 22 deletions(-) create mode 100644 src/filehandle/md_editor/store/useMDStore.ts create mode 100644 src/filehandle/md_editor/utils/index.ts diff --git a/src/components/Dialog/Dialog.module.less b/src/components/Dialog/Dialog.module.less index a702e3e..269dc76 100644 --- a/src/components/Dialog/Dialog.module.less +++ b/src/components/Dialog/Dialog.module.less @@ -35,6 +35,9 @@ } .header .bar { + display: flex; + align-items: center; + justify-content: start; flex: 1; height: 100%; cursor: move; diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index 4a2636c..4b1dfee 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -26,8 +26,9 @@ export type DialogProps = Omit< >; export interface IDialogProps extends DialogProps { - draggable?: boolean; open: boolean; + draggable?: boolean; + toolbar?: React.ReactNode; onMinimize?(): any; onClose?(): any; } @@ -93,6 +94,7 @@ const Dialog = forwardRef>( function Dialog(props, ref) { const { open, + toolbar = null, draggable = true, className = '', children, @@ -218,7 +220,9 @@ const Dialog = forwardRef>( className={styles.bar} style={{ cursor: draggable ? void 0 : 'default' }} onMouseDownCapture={handleStart} - > + > + {toolbar} +
{ + if (!uploadInfo || uploadInfo.url.trim() === '') { + return window.URL.createObjectURL(file); + } + + const body = uploadInfo.body.trim() ? JSON.parse(uploadInfo.body) : {}; + const data = toFormData({ ...body, [uploadInfo.field]: file }); + + const navigation = uploadInfo.navigation; + + return fetch(uploadInfo.url, { + method: 'POST', + body: data + }) + .then((res) => res.json()) + .then((value) => + navigation.split('.').reduce((prev, curr) => prev[curr], value) + ); +}; + +const uploader: Uploader = async (files, schema) => { + const images: File[] = []; + + for (let i = 0; i < files.length; i++) { + const file = files.item(i); + if (!file) { + continue; + } + + // You can handle whatever the file type you want, we handle image here. + if (!file.type.includes('image')) { + continue; + } + + images.push(file); + } + + const nodes = await Promise.all( + images.map(async (image) => { + const src = await selfUpload(image); + const alt = image.name; + return schema.nodes.image.createAndFill({ + src, + alt + })!; + }) + ); + + return nodes; +}; + function createMDEditor( el: HTMLElement, value = '', @@ -68,6 +124,11 @@ function createMDEditor( ctx.set(rootCtx, el); ctx.set(defaultValueCtx, value); + ctx.update(uploadConfig.key, (prev) => ({ + ...prev, + uploader + })); + ctx.set(prismConfig.key, { configureRefractor: (r) => { r.alias('shell', 'sh'); @@ -102,10 +163,11 @@ const MDEditor = forwardRef( function MDEditor(props, ref) { const { currentHandle, changed, onChanged, onSave } = props; + uploadInfo = useMDStore().uploadInfo; + const editorContainerRef = useRef(null); const editorRef = useRef(); const mdStringRef = useRef(''); - const creatingRef = useRef(false); useImperativeHandle(ref, () => ({ diff --git a/src/filehandle/md_editor/component/MDHandle.tsx b/src/filehandle/md_editor/component/MDHandle.tsx index c7436ca..12c1c91 100644 --- a/src/filehandle/md_editor/component/MDHandle.tsx +++ b/src/filehandle/md_editor/component/MDHandle.tsx @@ -1,5 +1,7 @@ import { useEffect, useId, useRef, useState } from 'react'; +import { Button, Form, Input, Modal, Row } from 'antd'; + import { error } from '@/utils'; import type { BackgroundManager } from '@/filehandle/BackgroundManager'; @@ -10,6 +12,8 @@ import type { IMDContentExpose } from './MDContent'; import { MDContent } from './MDContent'; import styles from './styles/MDHandle.module.less'; import type { DH, FH } from '../../utils/fileManager'; +import type { UploadInfo } from '../store/useMDStore'; +import { useMDStore } from '../store/useMDStore'; export interface IMDHandle { handle: DH | FH; @@ -18,6 +22,105 @@ export interface IMDHandle { update(): void; } +const Toolbar = () => { + const [uploadSettingModal, setUploadSettingModal] = useState(false); + + const { uploadInfo, setUploadInfo } = useMDStore(); + + const [form] = Form.useForm(); + + const handleSetImageUpload = () => { + setUploadSettingModal(true); + }; + + const handleCancel = () => { + setUploadSettingModal(false); + + form.resetFields(); + }; + + const handleSetUploadSetting = () => { + form + .validateFields() + .then((value) => { + setUploadInfo(value); + + setUploadSettingModal(false); + + form.resetFields(); + }) + .catch(() => { + /* empty */ + }); + }; + + return ( + <> + + + + + +
+ + label="上传地址" + name="url" + rules={[{ required: true, message: 'Please input upload url!' }]} + > + + + + + label="文件字段" + name="field" + rules={[{ required: true, message: 'Please input file field!' }]} + > + + + + + label="响应路径" + name="navigation" + rules={[ + { required: true, message: 'Please input response navigation!' } + ]} + > + + + + {/* TODO: JSON editor tool */} + label="额外请求体" name="body"> + + + +
+ + ); +}; + const MDHandle: React.FC = (props) => { const { handle, backgroundManager, update, destroy } = props; @@ -74,6 +177,7 @@ const MDHandle: React.FC = (props) => { className={styles.mdHandle} open={open} draggable={false} + toolbar={} onAnimationEnd={onAnimationEnd} onMinimize={handleMinimize} onClose={onClose} diff --git a/src/filehandle/md_editor/store/useMDStore.ts b/src/filehandle/md_editor/store/useMDStore.ts new file mode 100644 index 0000000..6b224c1 --- /dev/null +++ b/src/filehandle/md_editor/store/useMDStore.ts @@ -0,0 +1,38 @@ +import { getStorage, setStorage } from '@/utils'; + +import { create } from 'zustand'; + +export interface UploadInfo { + url: string; + field: string; + navigation: string; + body: string; +} + +export interface MDState { + uploadInfo: UploadInfo; +} + +export interface MDActions { + setUploadInfo(uploadInfo: UploadInfo): void; +} + +export const useMDStore = create((set) => ({ + ...getStorage('filesystem/md', { + uploadInfo: { + url: '', + field: '', + navigation: '', + body: '' + } + }), + setUploadInfo(uploadInfo) { + set(() => { + const store = { uploadInfo }; + + setStorage('filesystem/md', store); + + return store; + }); + } +})); diff --git a/src/filehandle/md_editor/utils/index.ts b/src/filehandle/md_editor/utils/index.ts new file mode 100644 index 0000000..9a21631 --- /dev/null +++ b/src/filehandle/md_editor/utils/index.ts @@ -0,0 +1,18 @@ +export const toFormData = ( + params: Record, + data?: FormData +) => { + data = data || new FormData(); + + const strategy: Record string | Blob> = { + $timestamp() { + return Date.now().toString(); + } + }; + + for (const key in params) { + data.append(key, strategy[params[key].toString()]?.() || params[key]); + } + + return data; +}; diff --git a/src/hooks/index.ts b/src/hooks/index.ts index a4c180f..39df8e8 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1 +1 @@ -export { default as useResizeObserver } from './useResizeObserver'; +export * from './useResizeObserver'; diff --git a/src/hooks/useResizeObserver.ts b/src/hooks/useResizeObserver.ts index f791f09..486ef23 100644 --- a/src/hooks/useResizeObserver.ts +++ b/src/hooks/useResizeObserver.ts @@ -6,7 +6,7 @@ const map: { const sizeMap: { [key: string]: { width: number; height: number } } = {}; -export default function useResizeObserver(selector: string) { +export function useResizeObserver(selector: string) { const [size, setSize] = useState( sizeMap[selector] || { width: 0, diff --git a/src/router/components/Loading.tsx b/src/router/components/Loading.tsx index d29e6cf..0ee9aee 100644 --- a/src/router/components/Loading.tsx +++ b/src/router/components/Loading.tsx @@ -1,9 +1,9 @@ -interface ILoadignProps {} +interface ILoadingProps {} -const Loadign: React.FC> = (props) => { +const Loading: React.FC> = (props) => { console.log(props); - return
Loadign
; + return
Loading
; }; -export default Loadign; +export default Loading; diff --git a/src/utils/localStorageTools.ts b/src/utils/localStorageTools.ts index 7af3928..02535ed 100644 --- a/src/utils/localStorageTools.ts +++ b/src/utils/localStorageTools.ts @@ -3,7 +3,8 @@ import { merge } from 'lodash-es'; export const KEYS = { /** Website configuration key */ APP_KEY: 'app', - USER_KEY: 'user' + USER_KEY: 'user', + FILE_SYSTEM_MD: 'filesystem/md' } as const; type TKEYS = (typeof KEYS)[keyof typeof KEYS]; diff --git a/src/viewer/Layout.tsx b/src/viewer/Layout.tsx index cb37057..f4c03a6 100644 --- a/src/viewer/Layout.tsx +++ b/src/viewer/Layout.tsx @@ -27,7 +27,7 @@ import { Icon } from '@/components'; import LogoImage from '@/assets/images/main.webp'; -import Feedback from './components/Feedback'; +import { Feedback } from './components/Feedback'; import languageData from './data/language.json'; import navbarData from './data/navbar.json'; import styles from './styles/Layout.module.less'; diff --git a/src/viewer/ProfileLayout.tsx b/src/viewer/ProfileLayout.tsx index e316a59..29c22c0 100644 --- a/src/viewer/ProfileLayout.tsx +++ b/src/viewer/ProfileLayout.tsx @@ -6,7 +6,7 @@ import profileSiderbarData from './data/profileSidebar.json'; const { Content, Sider } = Layout; -const Settings = () => { +const ProfileLayout = () => { const history = useHistory(); const location = useLocation(); @@ -50,4 +50,4 @@ const Settings = () => { ); }; -export default Settings; +export default ProfileLayout; diff --git a/src/viewer/components/Feedback.tsx b/src/viewer/components/Feedback.tsx index dee7710..193342c 100644 --- a/src/viewer/components/Feedback.tsx +++ b/src/viewer/components/Feedback.tsx @@ -14,7 +14,8 @@ const initialValues = { type: 'suggestion', description: '' }; -const Feedback: React.FC = ({ visible, onChange }) => { + +export const Feedback: React.FC = ({ visible, onChange }) => { const [uploading, setUploading] = useState(false); const [form] = Form.useForm(); @@ -120,5 +121,3 @@ const Feedback: React.FC = ({ visible, onChange }) => { ); }; - -export default Feedback; diff --git a/vite.config.ts b/vite.config.ts index f8810a6..51054a7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -19,7 +19,7 @@ import rehypePrism from '@mapbox/rehype-prism'; import viteRouteGenerator from './helpers/vite-route-generator'; import vitePrerender from './helpers/vite-prerender'; import viteGenerateSitemap from './helpers/vite-generate-sitemap'; -// import basicSsl from '@vitejs/plugin-basic-ssl'; +import basicSsl from '@vitejs/plugin-basic-ssl'; import type { ConfigEnv, UserConfig } from 'vite'; @@ -90,8 +90,8 @@ export default ({ command, mode }: ConfigEnv): UserConfig => { iconDirs: [resolve('./src/assets/svgs')], symbolId: 'icon-[dir]-[name]', svgoOptions: true - }) - // basicSsl() + }), + basicSsl() ], resolve: { alias: [