visuddhinanda 2 лет назад
Родитель
Сommit
fc59db4e43

+ 43 - 0
dashboard/src/components/anthology/EditableTocTree.tsx

@@ -7,6 +7,7 @@ import { get as getUiLang } from "../../locales";
 import { get, post, put } from "../../request";
 import {
   IArticleCreateRequest,
+  IArticleDataResponse,
   IArticleMapAddResponse,
   IArticleMapListResponse,
   IArticleMapUpdateRequest,
@@ -17,6 +18,8 @@ import EditableTree, {
   ListNodeData,
   TreeNodeData,
 } from "../article/EditableTree";
+import ArticleEditDrawer from "../article/ArticleEditDrawer";
+import ArticleDrawer from "../article/ArticleDrawer";
 
 interface IWidget {
   anthologyId?: string;
@@ -30,6 +33,11 @@ const EditableTocTreeWidget = ({
 }: IWidget) => {
   const [tocData, setTocData] = useState<ListNodeData[]>([]);
   const [addArticle, setAddArticle] = useState<TreeNodeData>();
+  const [articleId, setArticleId] = useState<string>();
+  const [openEditor, setOpenEditor] = useState(false);
+  const [updatedArticle, setUpdatedArticle] = useState<TreeNodeData>();
+  const [openViewer, setOpenViewer] = useState(false);
+  const [viewArticleId, setViewArticleId] = useState<string>();
   useEffect(() => {
     get<IArticleMapListResponse>(
       `/v2/article-map?view=anthology&id=${anthologyId}`
@@ -70,6 +78,7 @@ const EditableTocTreeWidget = ({
             }}
           />
         }
+        updatedNode={updatedArticle}
         onChange={(data: ListNodeData[]) => {
           console.log("onChange", data);
         }}
@@ -131,6 +140,40 @@ const EditableTocTreeWidget = ({
             return;
           }
         }}
+        onNodeEdit={(key: string) => {
+          setArticleId(key);
+          setOpenEditor(true);
+        }}
+        onTitleClick={(
+          e: React.MouseEvent<HTMLElement, MouseEvent>,
+          node: TreeNodeData
+        ) => {
+          if (e.ctrlKey || e.metaKey) {
+          } else {
+            setViewArticleId(node.key);
+            setOpenViewer(true);
+          }
+        }}
+      />
+      <ArticleEditDrawer
+        articleId={articleId}
+        open={openEditor}
+        onClose={() => setOpenEditor(false)}
+        onChange={(data: IArticleDataResponse) => {
+          console.log("new title", data.title);
+          setUpdatedArticle({
+            key: data.uid,
+            title: data.title,
+            level: 0,
+            children: [],
+          });
+        }}
+      />
+      <ArticleDrawer
+        articleId={viewArticleId}
+        type="article"
+        open={openViewer}
+        onClose={() => setOpenViewer(false)}
       />
     </div>
   );

+ 149 - 137
dashboard/src/components/article/ArticleEdit.tsx

@@ -35,9 +35,15 @@ interface IWidget {
   studioName?: string;
   articleId?: string;
   onReady?: Function;
+  onChange?: Function;
 }
 
-const ArticleEditWidget = ({ studioName, articleId, onReady }: IWidget) => {
+const ArticleEditWidget = ({
+  studioName,
+  articleId,
+  onReady,
+  onChange,
+}: IWidget) => {
   const intl = useIntl();
   const [unauthorized, setUnauthorized] = useState(false);
   const [readonly, setReadonly] = useState(false);
@@ -51,145 +57,151 @@ const ArticleEditWidget = ({ studioName, articleId, onReady }: IWidget) => {
       extra={<></>}
     />
   ) : (
-    <ProForm<IFormData>
-      onFinish={async (values: IFormData) => {
-        // TODO
+    <>
+      {readonly ? "只读" : undefined}
+      <ProForm<IFormData>
+        onFinish={async (values: IFormData) => {
+          // TODO
 
-        const request = {
-          uid: articleId ? articleId : "",
-          title: values.title,
-          subtitle: values.subtitle,
-          summary: values.summary,
-          content: values.content,
-          content_type: "markdown",
-          status: values.status,
-          lang: values.lang,
-        };
-        console.log("save", request);
-        put<IArticleDataRequest, IArticleResponse>(
-          `/v2/article/${articleId}`,
-          request
-        )
-          .then((res) => {
-            console.log("save response", res);
-            if (res.ok) {
-              message.success(intl.formatMessage({ id: "flashes.success" }));
-            } else {
-              message.error(res.message);
-            }
-          })
-          .catch((e: IArticleResponse) => {
-            message.error(e.message);
-          });
-      }}
-      request={async () => {
-        const res = await get<IArticleResponse>(`/v2/article/${articleId}`);
-        console.log("article", res);
-        let mTitle: string,
-          mReadonly = false;
-        if (res.ok) {
-          mReadonly = res.data.role === "editor" ? false : true;
-          setReadonly(mReadonly);
-          mTitle = res.data.title;
-          setContent(res.data.content);
-        } else {
-          setUnauthorized(true);
-          mTitle = "无权访问";
-        }
-        if (typeof onReady !== "undefined") {
-          onReady(mTitle, mReadonly, res.data.studio?.realName);
-        }
-        return {
-          uid: res.data.uid,
-          title: res.data.title,
-          subtitle: res.data.subtitle,
-          summary: res.data.summary,
-          content: res.data.content,
-          content_type: res.data.content_type,
-          lang: res.data.lang,
-          status: res.data.status,
-        };
-      }}
-    >
-      <Tabs
-        items={[
-          {
-            key: "info",
-            label: intl.formatMessage({ id: "course.basic.info.label" }),
-            children: (
-              <>
-                <ProForm.Group>
-                  <ProFormText
-                    width="md"
-                    name="title"
-                    required
-                    label={intl.formatMessage({
-                      id: "forms.fields.title.label",
-                    })}
-                    rules={[
-                      {
-                        required: true,
-                        message: intl.formatMessage({
-                          id: "forms.message.title.required",
-                        }),
-                      },
-                    ]}
-                  />
-                  <ProFormText
-                    width="md"
-                    name="subtitle"
-                    label={intl.formatMessage({
-                      id: "forms.fields.subtitle.label",
-                    })}
-                  />
-                </ProForm.Group>
-                <ProForm.Group>
-                  <LangSelect width="md" />
-                  <PublicitySelect width="md" />
-                </ProForm.Group>
+          const request = {
+            uid: articleId ? articleId : "",
+            title: values.title,
+            subtitle: values.subtitle,
+            summary: values.summary,
+            content: values.content,
+            content_type: "markdown",
+            status: values.status,
+            lang: values.lang,
+          };
+          console.log("save", request);
+          put<IArticleDataRequest, IArticleResponse>(
+            `/v2/article/${articleId}`,
+            request
+          )
+            .then((res) => {
+              console.log("save response", res);
+              if (res.ok) {
+                if (typeof onChange !== "undefined") {
+                  onChange(res.data);
+                }
+                message.success(intl.formatMessage({ id: "flashes.success" }));
+              } else {
+                message.error(res.message);
+              }
+            })
+            .catch((e: IArticleResponse) => {
+              message.error(e.message);
+            });
+        }}
+        request={async () => {
+          const res = await get<IArticleResponse>(`/v2/article/${articleId}`);
+          console.log("article", res);
+          let mTitle: string,
+            mReadonly = false;
+          if (res.ok) {
+            mReadonly = res.data.role === "editor" ? false : true;
+            setReadonly(mReadonly);
+            mTitle = res.data.title;
+            setContent(res.data.content);
+          } else {
+            setUnauthorized(true);
+            mTitle = "无权访问";
+          }
+          if (typeof onReady !== "undefined") {
+            onReady(mTitle, mReadonly, res.data.studio?.realName);
+          }
+          return {
+            uid: res.data.uid,
+            title: res.data.title,
+            subtitle: res.data.subtitle,
+            summary: res.data.summary,
+            content: res.data.content,
+            content_type: res.data.content_type,
+            lang: res.data.lang,
+            status: res.data.status,
+          };
+        }}
+      >
+        <Tabs
+          items={[
+            {
+              key: "info",
+              label: intl.formatMessage({ id: "course.basic.info.label" }),
+              children: (
+                <>
+                  <ProForm.Group>
+                    <ProFormText
+                      width="md"
+                      name="title"
+                      required
+                      label={intl.formatMessage({
+                        id: "forms.fields.title.label",
+                      })}
+                      rules={[
+                        {
+                          required: true,
+                          message: intl.formatMessage({
+                            id: "forms.message.title.required",
+                          }),
+                        },
+                      ]}
+                    />
+                    <ProFormText
+                      width="md"
+                      name="subtitle"
+                      label={intl.formatMessage({
+                        id: "forms.fields.subtitle.label",
+                      })}
+                    />
+                  </ProForm.Group>
+                  <ProForm.Group>
+                    <LangSelect width="md" />
+                    <PublicitySelect width="md" />
+                  </ProForm.Group>
+                  <ProForm.Group>
+                    <ProFormTextArea
+                      name="summary"
+                      width="lg"
+                      label={intl.formatMessage({
+                        id: "forms.fields.summary.label",
+                      })}
+                    />
+                  </ProForm.Group>
+                </>
+              ),
+            },
+            {
+              key: "content",
+              label: intl.formatMessage({ id: "forms.fields.content.label" }),
+              forceRender: true,
+              children: (
                 <ProForm.Group>
-                  <ProFormTextArea
-                    name="summary"
-                    width="lg"
-                    label={intl.formatMessage({
-                      id: "forms.fields.summary.label",
-                    })}
-                  />
+                  <Form.Item
+                    name="content"
+                    label={
+                      <Space>
+                        {intl.formatMessage({
+                          id: "forms.fields.content.label",
+                        })}
+                        {articleId ? (
+                          <ArticlePrevDrawer
+                            trigger={<Button>预览</Button>}
+                            articleId={articleId}
+                            content={content}
+                          />
+                        ) : undefined}
+                      </Space>
+                    }
+                  >
+                    <MDEditor onChange={(value) => setContent(value)} />
+                  </Form.Item>
                 </ProForm.Group>
-              </>
-            ),
-          },
-          {
-            key: "content",
-            label: intl.formatMessage({ id: "forms.fields.content.label" }),
-            forceRender: true,
-            children: (
-              <ProForm.Group>
-                <Form.Item
-                  name="content"
-                  label={
-                    <Space>
-                      {intl.formatMessage({
-                        id: "forms.fields.content.label",
-                      })}
-                      {articleId ? (
-                        <ArticlePrevDrawer
-                          trigger={<Button>预览</Button>}
-                          articleId={articleId}
-                          content={content}
-                        />
-                      ) : undefined}
-                    </Space>
-                  }
-                >
-                  <MDEditor onChange={(value) => setContent(value)} />
-                </Form.Item>
-              </ProForm.Group>
-            ),
-          },
-        ]}
-      />
-    </ProForm>
+              ),
+            },
+          ]}
+        />
+      </ProForm>
+    </>
   );
 };
 

+ 8 - 0
dashboard/src/components/article/ArticleEditDrawer.tsx

@@ -1,5 +1,6 @@
 import { Drawer } from "antd";
 import React, { useEffect, useState } from "react";
+import { IArticleDataResponse } from "../api/Article";
 
 import ArticleEdit from "./ArticleEdit";
 import ArticleEditTools from "./ArticleEditTools";
@@ -9,6 +10,7 @@ interface IWidget {
   articleId?: string;
   open?: boolean;
   onClose?: Function;
+  onChange?: Function;
 }
 
 const ArticleEditDrawerWidget = ({
@@ -16,6 +18,7 @@ const ArticleEditDrawerWidget = ({
   articleId,
   open,
   onClose,
+  onChange,
 }: IWidget) => {
   const [openDrawer, setOpenDrawer] = useState(open);
   const [title, setTitle] = useState("loading");
@@ -72,6 +75,11 @@ const ArticleEditDrawerWidget = ({
             setReadonly(readonly);
             setStudioName(studio);
           }}
+          onChange={(data: IArticleDataResponse) => {
+            if (typeof onChange !== "undefined") {
+              onChange(data);
+            }
+          }}
         />
       </Drawer>
     </>

+ 41 - 1
dashboard/src/components/article/EditableTree.tsx

@@ -118,21 +118,27 @@ interface IWidget {
   treeData: ListNodeData[];
   addFileButton?: React.ReactNode;
   addOnArticle?: TreeNodeData;
+  updatedNode?: TreeNodeData;
   onChange?: Function;
   onSelect?: Function;
   onSave?: Function;
   onAddFile?: Function;
   onAppend?: Function;
+  onNodeEdit?: Function;
+  onTitleClick?: Function;
 }
 const EditableTreeWidget = ({
   treeData,
   addFileButton,
   addOnArticle,
+  updatedNode,
   onChange,
   onSelect,
   onSave,
   onAddFile,
   onAppend,
+  onNodeEdit,
+  onTitleClick,
 }: IWidget) => {
   const intl = useIntl();
 
@@ -140,6 +146,30 @@ const EditableTreeWidget = ({
   const [listTreeData, setListTreeData] = useState<ListNodeData[]>();
   const [keys, setKeys] = useState<Key>("");
 
+  useEffect(() => {
+    //找到节点并更新
+    if (typeof updatedNode === "undefined") {
+      return;
+    }
+    const update = (_node: TreeNodeData[]) => {
+      _node.forEach((value, index, array) => {
+        if (value.key === updatedNode.key) {
+          array[index].title = updatedNode.title;
+          console.log("key found");
+          return;
+        } else {
+          update(array[index].children);
+        }
+        return;
+      });
+    };
+    const newTree = [...gData];
+    update(newTree);
+    setGData(newTree);
+    const list = treeToList(newTree);
+    setListTreeData(list);
+  }, [updatedNode]);
+
   useEffect(() => {
     if (typeof addOnArticle === "undefined") {
       return;
@@ -150,7 +180,7 @@ const EditableTreeWidget = ({
     setGData(newTreeData);
     const list = treeToList(newTreeData);
     setListTreeData(list);
-  }, [addOnArticle, gData]);
+  }, [addOnArticle]);
 
   useEffect(() => {
     const data = tocGetTreeData(treeData);
@@ -327,6 +357,11 @@ const EditableTreeWidget = ({
           return (
             <EditableTreeNode
               node={node}
+              onEdit={() => {
+                if (typeof onNodeEdit !== "undefined") {
+                  onNodeEdit(node.key);
+                }
+              }}
               onAdd={async () => {
                 if (typeof onAppend !== "undefined") {
                   const newNode = await onAppend(node);
@@ -342,6 +377,11 @@ const EditableTreeWidget = ({
                   return false;
                 }
               }}
+              onTitleClick={(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
+                if (typeof onTitleClick !== "undefined") {
+                  onTitleClick(e, node);
+                }
+              }}
             />
           );
         }}

+ 34 - 5
dashboard/src/components/article/EditableTreeNode.tsx

@@ -1,14 +1,21 @@
-import { Button, Space, Typography } from "antd";
+import { Button, message, Space, Typography } from "antd";
 import { useState } from "react";
-import { PlusOutlined } from "@ant-design/icons";
+import { PlusOutlined, EditOutlined } from "@ant-design/icons";
 import { TreeNodeData } from "./EditableTree";
 const { Text } = Typography;
 
 interface IWidget {
   node: TreeNodeData;
   onAdd?: Function;
+  onEdit?: Function;
+  onTitleClick?: Function;
 }
-const EditableTreeNodeWidget = ({ node, onAdd }: IWidget) => {
+const EditableTreeNodeWidget = ({
+  node,
+  onAdd,
+  onEdit,
+  onTitleClick,
+}: IWidget) => {
   const [showNodeMenu, setShowNodeMenu] = useState(false);
   const [loading, setLoading] = useState(false);
 
@@ -17,13 +24,32 @@ const EditableTreeNodeWidget = ({ node, onAdd }: IWidget) => {
       {node.title}
     </Text>
   ) : (
-    <>{node.title}</>
+    <Text
+      onClick={(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
+        if (typeof onTitleClick !== "undefined") {
+          onTitleClick(e);
+        }
+      }}
+    >
+      {node.title}
+    </Text>
   );
   const menu = (
     <Space style={{ visibility: showNodeMenu ? "visible" : "hidden" }}>
       <Button
         loading={loading}
-        size="small"
+        size="middle"
+        icon={<EditOutlined />}
+        type="text"
+        onClick={async () => {
+          if (typeof onEdit !== "undefined") {
+            onEdit();
+          }
+        }}
+      />
+      <Button
+        loading={loading}
+        size="middle"
         icon={<PlusOutlined />}
         type="text"
         onClick={async () => {
@@ -31,6 +57,9 @@ const EditableTreeNodeWidget = ({ node, onAdd }: IWidget) => {
             setLoading(true);
             const ok = await onAdd();
             setLoading(false);
+            if (!ok) {
+              message.error("error");
+            }
           }
         }}
       />

+ 11 - 2
dashboard/src/components/template/SentEdit/SentTab.tsx

@@ -3,6 +3,7 @@ import {
   TranslationOutlined,
   CloseOutlined,
   BlockOutlined,
+  BarsOutlined,
 } from "@ant-design/icons";
 
 import SentTabButton from "./SentTabButton";
@@ -61,7 +62,6 @@ const SentTabWidget = ({
 }: IWidget) => {
   const intl = useIntl();
   const [isCompact, setIsCompact] = useState(compact);
-
   useEffect(() => setIsCompact(compact), [compact]);
   const mPath = path
     ? [
@@ -78,7 +78,16 @@ const SentTabWidget = ({
   return (
     <>
       <Tabs
-        style={isCompact ? { position: "absolute", marginTop: -32 } : undefined}
+        style={
+          isCompact
+            ? {
+                position: "absolute",
+                marginTop: -32,
+                width: "100%",
+                marginRight: 10,
+              }
+            : undefined
+        }
         tabBarStyle={{ marginBottom: 0 }}
         size="small"
         tabBarGutter={0}