Browse Source

Merge pull request #1198 from visuddhinanda/agile

增加讨论的原文预览
visuddhinanda 2 years ago
parent
commit
11e70ff022
37 changed files with 380 additions and 272 deletions
  1. 2 1
      dashboard/src/Router.tsx
  2. 3 2
      dashboard/src/components/api/Comment.ts
  3. 1 1
      dashboard/src/components/api/Corpus.ts
  4. 1 1
      dashboard/src/components/article/Article.tsx
  5. 26 29
      dashboard/src/components/auth/StudioCard.tsx
  6. 11 5
      dashboard/src/components/channel/ChannelList.tsx
  7. 13 1
      dashboard/src/components/channel/ChannelTable.tsx
  8. 3 0
      dashboard/src/components/channel/ChannelTableModal.tsx
  9. 0 33
      dashboard/src/components/comment/CommentAnchor.tsx
  10. 0 5
      dashboard/src/components/comment/CommentListItem.tsx
  11. 0 28
      dashboard/src/components/comment/CommentTopic.tsx
  12. 8 1
      dashboard/src/components/corpus/ChapterCard.tsx
  13. 0 0
      dashboard/src/components/discussion/AnchorCard.tsx
  14. 58 0
      dashboard/src/components/discussion/DiscussionAnchor.tsx
  15. 10 7
      dashboard/src/components/discussion/DiscussionBox.tsx
  16. 3 3
      dashboard/src/components/discussion/DiscussionCreate.tsx
  17. 3 3
      dashboard/src/components/discussion/DiscussionEdit.tsx
  18. 6 6
      dashboard/src/components/discussion/DiscussionItem.tsx
  19. 3 3
      dashboard/src/components/discussion/DiscussionList.tsx
  20. 37 40
      dashboard/src/components/discussion/DiscussionListCard.tsx
  21. 5 0
      dashboard/src/components/discussion/DiscussionListItem.tsx
  22. 34 33
      dashboard/src/components/discussion/DiscussionShow.tsx
  23. 42 0
      dashboard/src/components/discussion/DiscussionTopic.tsx
  24. 4 4
      dashboard/src/components/discussion/DiscussionTopicChildren.tsx
  25. 9 3
      dashboard/src/components/discussion/DiscussionTopicInfo.tsx
  26. 1 1
      dashboard/src/components/feedback/CreateFeedback.tsx
  27. 1 2
      dashboard/src/components/template/SentEdit.tsx
  28. 3 1
      dashboard/src/components/template/SentEdit/SentAdd.tsx
  29. 7 0
      dashboard/src/components/template/SentEdit/SentCanRead.tsx
  30. 23 1
      dashboard/src/components/template/SentEdit/SentContent.tsx
  31. 1 1
      dashboard/src/components/template/SentEdit/SuggestionToolbar.tsx
  32. 1 1
      dashboard/src/components/template/Wbw/WbwDetail.tsx
  33. 1 1
      dashboard/src/components/template/Wbw/WbwPali.tsx
  34. 1 0
      dashboard/src/locales/zh-Hans/label.ts
  35. 3 3
      dashboard/src/pages/library/discussion/index.tsx
  36. 22 10
      dashboard/src/pages/library/discussion/show.tsx
  37. 34 42
      dashboard/src/pages/library/discussion/topic.tsx

+ 2 - 1
dashboard/src/Router.tsx

@@ -54,6 +54,7 @@ import LibraryBlogTerm from "./pages/library/blog/term";
 import LibraryDiscussion from "./pages/library/discussion";
 import LibraryDiscussionList from "./pages/library/discussion/list";
 import LibraryDiscussionTopic from "./pages/library/discussion/topic";
+import LibraryDiscussionShow from "./pages/library/discussion/show";
 
 import LibrarySearch from "./pages/library/search";
 import LibrarySearchKey from "./pages/library/search/search";
@@ -209,7 +210,7 @@ const Widget = () => {
         <Route path="discussion" element={<LibraryDiscussion />}>
           <Route path="list" element={<LibraryDiscussionList />} />
           <Route path="topic/:id" element={<LibraryDiscussionTopic />} />
-          <Route path="discussion/:id" element={<LibraryDiscussion />} />
+          <Route path="show/:type/:id" element={<LibraryDiscussionShow />} />
         </Route>
 
         <Route path="blog/:studio" element={<LibraryBlog />}>

+ 3 - 2
dashboard/src/components/api/Comment.ts

@@ -1,4 +1,5 @@
-import { TContentType } from "../comment/CommentCreate";
+import { TContentType } from "../discussion/DiscussionCreate";
+import { TResType } from "../discussion/DiscussionListCard";
 import { IUserApiData } from "./Auth";
 
 export interface ICommentRequest {
@@ -17,7 +18,7 @@ export interface ICommentRequest {
 export interface ICommentApiData {
   id: string;
   res_id: string;
-  res_type: string;
+  res_type: TResType;
   title?: string;
   content?: string;
   content_type?: TContentType;

+ 1 - 1
dashboard/src/components/api/Corpus.ts

@@ -1,7 +1,7 @@
 import { IStudio } from "../auth/StudioName";
 import { IUser } from "../auth/User";
 import { IChannel } from "../channel/Channel";
-import { TContentType } from "../comment/CommentCreate";
+import { TContentType } from "../discussion/DiscussionCreate";
 import { ISuggestionCount, IWidgetSentEditInner } from "../template/SentEdit";
 import { TChannelType } from "./Channel";
 import { TagNode } from "./Tag";

+ 1 - 1
dashboard/src/components/article/Article.tsx

@@ -11,7 +11,7 @@ import { ITextbook, refresh } from "../../reducers/current-course";
 import ExerciseList from "./ExerciseList";
 import ExerciseAnswer from "../course/ExerciseAnswer";
 import "./article.css";
-import CommentListCard from "../comment/CommentListCard";
+import CommentListCard from "../discussion/DiscussionListCard";
 import TocTree from "./TocTree";
 import PaliText from "../template/Wbw/PaliText";
 import ArticleSkeleton from "./ArticleSkeleton";

+ 26 - 29
dashboard/src/components/auth/StudioCard.tsx

@@ -11,38 +11,35 @@ const StudioCardWidget = ({ studio, children }: IWidget) => {
   const intl = useIntl();
 
   return (
-    <>
-      <Popover
-        content={
-          <>
-            <div style={{ display: "flex" }}>
-              <div style={{ paddingRight: 8 }}>
-                <Avatar style={{ backgroundColor: "#87d068" }} size="small">
-                  {studio?.nickName.slice(0, 1)}
-                </Avatar>
-              </div>
+    <Popover
+      content={
+        <>
+          <div style={{ display: "flex" }}>
+            <div style={{ paddingRight: 8 }}>
+              <Avatar style={{ backgroundColor: "#87d068" }} size="small">
+                {studio?.nickName.slice(0, 1)}
+              </Avatar>
+            </div>
+            <div>
+              <div>{studio?.nickName}</div>
               <div>
-                <div>{studio?.nickName}</div>
-                <div>译文(2) | 课程(3)</div>
-                <div>
-                  <Link
-                    to={`/blog/${studio?.studioName}/overview`}
-                    target="_blank"
-                  >
-                    {intl.formatMessage({
-                      id: "columns.library.blog.label",
-                    })}
-                  </Link>
-                </div>
+                <Link
+                  to={`/blog/${studio?.studioName}/overview`}
+                  target="_blank"
+                >
+                  {intl.formatMessage({
+                    id: "columns.library.blog.label",
+                  })}
+                </Link>
               </div>
             </div>
-          </>
-        }
-        placement="bottomRight"
-      >
-        {children}
-      </Popover>
-    </>
+          </div>
+        </>
+      }
+      placement="bottomRight"
+    >
+      {children}
+    </Popover>
   );
 };
 

+ 11 - 5
dashboard/src/components/channel/ChannelList.tsx

@@ -1,5 +1,6 @@
+import { useIntl } from "react-intl";
 import { useState, useEffect } from "react";
-import { List, message, Space, Tag } from "antd";
+import { Card, List, message, Space, Tag } from "antd";
 
 import type { IChannelApiData } from "../api/Channel";
 import { IApiResponseChannelList } from "../api/Corpus";
@@ -30,9 +31,9 @@ const ChannelListWidget = ({
   filter = defaultChannelFilterProps,
 }: IWidgetChannelList) => {
   const [tableData, setTableData] = useState<IChannelList[]>([]);
+  const intl = useIntl();
 
   useEffect(() => {
-    console.log("palichapterlist useEffect");
     let url = `/v2/progress?view=channel&channel_type=${filter.channelType}&lang=${filter.lang}&progress=${filter.chapterProgress}`;
     get<IApiResponseChannelList>(url).then(function (json) {
       if (json.ok) {
@@ -54,9 +55,14 @@ const ChannelListWidget = ({
       }
     });
   }, [filter]);
+
   return (
-    <>
-      <h3>Channel</h3>
+    <Card
+      title={intl.formatMessage({
+        id: `columns.studio.channel.title`,
+      })}
+      size="small"
+    >
       <List
         itemLayout="vertical"
         size="small"
@@ -70,7 +76,7 @@ const ChannelListWidget = ({
           </List.Item>
         )}
       />
-    </>
+    </Card>
   );
 };
 

+ 13 - 1
dashboard/src/components/channel/ChannelTable.tsx

@@ -67,10 +67,16 @@ interface IChannelItem {
 interface IWidget {
   studioName?: string;
   type?: string;
+  disableChannels?: string[];
   onSelect?: Function;
 }
 
-const ChannelTableWidget = ({ studioName, type, onSelect }: IWidget) => {
+const ChannelTableWidget = ({
+  studioName,
+  disableChannels,
+  type,
+  onSelect,
+}: IWidget) => {
   const intl = useIntl();
 
   const [openCreate, setOpenCreate] = useState(false);
@@ -79,6 +85,11 @@ const ChannelTableWidget = ({ studioName, type, onSelect }: IWidget) => {
   const [myNumber, setMyNumber] = useState<number>(0);
   const [collaborationNumber, setCollaborationNumber] = useState<number>(0);
   const [collaborator, setCollaborator] = useState<string>();
+
+  useEffect(() => {
+    ref.current?.reload();
+  }, [disableChannels]);
+
   useEffect(() => {
     /**
      * 获取各种课程的数量
@@ -157,6 +168,7 @@ const ChannelTableWidget = ({ studioName, type, onSelect }: IWidget) => {
                 <>
                   <div key={1}>
                     <Button
+                      disabled={disableChannels?.includes(row.uid)}
                       type="link"
                       key={index}
                       onClick={() => {

+ 3 - 0
dashboard/src/components/channel/ChannelTableModal.tsx

@@ -12,6 +12,7 @@ interface IWidget {
   type?: ArticleType | "editable";
   articleId?: string;
   multiSelect?: boolean;
+  disableChannels?: string[];
   open?: boolean;
   onClose?: Function;
   onSelect?: Function;
@@ -21,6 +22,7 @@ const ChannelTableModalWidget = ({
   type,
   articleId,
   multiSelect = true,
+  disableChannels,
   open = false,
   onClose,
   onSelect,
@@ -63,6 +65,7 @@ const ChannelTableModalWidget = ({
         <ChannelTable
           studioName={user?.realName}
           type={type}
+          disableChannels={disableChannels}
           onSelect={(channel: IChannel) => {
             handleCancel();
             if (typeof onClose !== "undefined") {

+ 0 - 33
dashboard/src/components/comment/CommentAnchor.tsx

@@ -1,33 +0,0 @@
-import { useEffect, useState } from "react";
-import { get } from "../../request";
-import { ICommentAnchorResponse } from "../api/Comment";
-import MdView from "../template/MdView";
-import AnchorCard from "./AnchorCard";
-
-interface IWidget {
-  id?: string;
-}
-const CommentAnchorWidget = ({ id }: IWidget) => {
-  const [content, setContent] = useState<string>();
-  useEffect(() => {
-    if (typeof id === "string") {
-      get<ICommentAnchorResponse>(`/v2/discussion-anchor/${id}`).then(
-        (json) => {
-          console.log(json);
-          if (json.ok) {
-            setContent(json.data);
-          }
-        }
-      );
-    }
-  }, [id]);
-  return (
-    <div>
-      <AnchorCard>
-        <MdView html={content} />
-      </AnchorCard>
-    </div>
-  );
-};
-
-export default CommentAnchorWidget;

+ 0 - 5
dashboard/src/components/comment/CommentListItem.tsx

@@ -1,5 +0,0 @@
-const commentListItemWidget = () => {
-  return <div>change password</div>;
-};
-
-export default commentListItemWidget;

+ 0 - 28
dashboard/src/components/comment/CommentTopic.tsx

@@ -1,28 +0,0 @@
-import { Divider } from "antd";
-
-import CommentTopicInfo from "./CommentTopicInfo";
-import CommentTopicChildren from "./CommentTopicChildren";
-
-interface IWidget {
-  topicId?: string;
-  onItemCountChange?: Function;
-}
-const CommentTopicWidget = ({ topicId, onItemCountChange }: IWidget) => {
-  return (
-    <div>
-      <CommentTopicInfo topicId={topicId} />
-      <Divider />
-      <CommentTopicChildren
-        topicId={topicId}
-        onItemCountChange={(count: number, e: string) => {
-          //把新建回答的消息传出去。
-          if (typeof onItemCountChange !== "undefined") {
-            onItemCountChange(count, e);
-          }
-        }}
-      />
-    </div>
-  );
-};
-
-export default CommentTopicWidget;

+ 8 - 1
dashboard/src/components/corpus/ChapterCard.tsx

@@ -1,3 +1,4 @@
+import { useIntl } from "react-intl";
 import { Link } from "react-router-dom";
 import { Row, Col, Progress, Space } from "antd";
 import { Typography } from "antd";
@@ -36,6 +37,7 @@ interface IWidgetChapterCard {
 }
 
 const ChpterCardWidget = ({ data, onTagClick }: IWidgetChapterCard) => {
+  const intl = useIntl();
   const path = JSON.parse(data.path);
   let url = `/article/chapter/${data.book}-${data.paragraph}`;
   url += data.channel.id ? `?channel=${data.channel.id}` : "";
@@ -83,7 +85,12 @@ const ChpterCardWidget = ({ data, onTagClick }: IWidgetChapterCard) => {
             </div>
             <Space>
               <ChannelListItem channel={data.channel} studio={data.studio} />
-              <TimeShow time={data.updatedAt} title="UpdatedAt" />
+              <TimeShow
+                time={data.updatedAt}
+                title={intl.formatMessage({
+                  id: "labels.updated-at",
+                })}
+              />
             </Space>
           </div>
         </Col>

+ 0 - 0
dashboard/src/components/comment/AnchorCard.tsx → dashboard/src/components/discussion/AnchorCard.tsx


+ 58 - 0
dashboard/src/components/discussion/DiscussionAnchor.tsx

@@ -0,0 +1,58 @@
+import { useEffect, useState } from "react";
+import { get } from "../../request";
+import { IArticleResponse } from "../api/Article";
+import { ICommentAnchorResponse } from "../api/Comment";
+import { ISentenceResponse } from "../api/Corpus";
+import MdView from "../template/MdView";
+import AnchorCard from "./AnchorCard";
+import { TResType } from "./DiscussionListCard";
+
+interface IWidget {
+  resId?: string;
+  resType?: TResType;
+  topicId?: string;
+}
+const DiscussionAnchorWidget = ({ resId, resType, topicId }: IWidget) => {
+  const [content, setContent] = useState<string>();
+  useEffect(() => {
+    if (typeof topicId === "string") {
+      get<ICommentAnchorResponse>(`/v2/discussion-anchor/${topicId}`).then(
+        (json) => {
+          console.log(json);
+          if (json.ok) {
+            setContent(json.data);
+          }
+        }
+      );
+    }
+  }, [topicId]);
+
+  useEffect(() => {
+    switch (resType) {
+      case "sentence":
+        get<ISentenceResponse>(`/v2/sentence/${resId}`).then((json) => {
+          if (json.ok) {
+            const id = `${json.data.book}-${json.data.paragraph}-${json.data.word_start}-${json.data.word_end}`;
+            const channel = json.data.channel.id;
+            const url = `/v2/corpus-sent/${id}?mode=edit&channels=${channel}`;
+            console.log("url", url);
+            get<IArticleResponse>(url).then((json) => {
+              if (json.ok) {
+                setContent(json.data.content);
+              }
+            });
+          }
+        });
+        break;
+      default:
+        break;
+    }
+  }, [resId, resType]);
+  return (
+    <AnchorCard>
+      <MdView html={content} />
+    </AnchorCard>
+  );
+};
+
+export default DiscussionAnchorWidget;

+ 10 - 7
dashboard/src/components/comment/CommentBox.tsx → dashboard/src/components/discussion/DiscussionBox.tsx

@@ -1,8 +1,9 @@
 import { useState } from "react";
-import { Drawer } from "antd";
-import CommentTopic from "./CommentTopic";
-import CommentListCard, { TResType } from "./CommentListCard";
-import { IComment } from "./CommentItem";
+import { Divider, Drawer } from "antd";
+import CommentTopic from "./DiscussionTopic";
+import CommentListCard, { TResType } from "./DiscussionListCard";
+import { IComment } from "./DiscussionItem";
+import DiscussionAnchor from "./DiscussionAnchor";
 
 export interface IAnswerCount {
   id: string;
@@ -14,7 +15,7 @@ interface IWidget {
   resType?: TResType;
   onCommentCountChange?: Function;
 }
-const CommentBoxWidget = ({
+const DiscussionBoxWidget = ({
   trigger,
   resId,
   resType,
@@ -54,6 +55,8 @@ const CommentBoxWidget = ({
         open={open}
         maskClosable={false}
       >
+        <DiscussionAnchor resId={resId} resType={resType} />
+        <Divider></Divider>
         <CommentListCard
           resId={resId}
           resType={resType}
@@ -66,7 +69,7 @@ const CommentBoxWidget = ({
           }}
         />
         <Drawer
-          title="回答"
+          title="Answer"
           width={480}
           onClose={() => {
             setChildrenDrawer(false);
@@ -85,4 +88,4 @@ const CommentBoxWidget = ({
   );
 };
 
-export default CommentBoxWidget;
+export default DiscussionBoxWidget;

+ 3 - 3
dashboard/src/components/comment/CommentCreate.tsx → dashboard/src/components/discussion/DiscussionCreate.tsx

@@ -10,7 +10,7 @@ import { Col, Row, Space } from "antd";
 import ReactQuill from "react-quill";
 import "react-quill/dist/quill.snow.css";
 
-import { IComment } from "./CommentItem";
+import { IComment } from "./DiscussionItem";
 import { post } from "../../request";
 import { ICommentRequest, ICommentResponse } from "../api/Comment";
 import { useAppSelector } from "../../hooks";
@@ -27,7 +27,7 @@ interface IWidget {
   onCreated?: Function;
   contentType?: TContentType;
 }
-const CommentCreateWidget = ({
+const DiscussionCreateWidget = ({
   resId,
   resType,
   contentType = "html",
@@ -179,4 +179,4 @@ const CommentCreateWidget = ({
   }
 };
 
-export default CommentCreateWidget;
+export default DiscussionCreateWidget;

+ 3 - 3
dashboard/src/components/comment/CommentEdit.tsx → dashboard/src/components/discussion/DiscussionEdit.tsx

@@ -4,7 +4,7 @@ import { message } from "antd";
 import { ProForm, ProFormTextArea } from "@ant-design/pro-components";
 import { Col, Row, Space } from "antd";
 
-import { IComment } from "./CommentItem";
+import { IComment } from "./DiscussionItem";
 import { put } from "../../request";
 import { ICommentRequest, ICommentResponse } from "../api/Comment";
 
@@ -12,7 +12,7 @@ interface IWidget {
   data: IComment;
   onCreated?: Function;
 }
-const CommentEditWidget = ({ data, onCreated }: IWidget) => {
+const DiscussionEditWidget = ({ data, onCreated }: IWidget) => {
   const intl = useIntl();
   const formItemLayout = {
     labelCol: { span: 4 },
@@ -85,4 +85,4 @@ const CommentEditWidget = ({ data, onCreated }: IWidget) => {
   );
 };
 
-export default CommentEditWidget;
+export default DiscussionEditWidget;

+ 6 - 6
dashboard/src/components/comment/CommentItem.tsx → dashboard/src/components/discussion/DiscussionItem.tsx

@@ -1,13 +1,14 @@
 import { Avatar } from "antd";
 import { useState } from "react";
 import { IUser } from "../auth/User";
-import CommentShow from "./CommentShow";
-import CommentEdit from "./CommentEdit";
+import CommentShow from "./DiscussionShow";
+import CommentEdit from "./DiscussionEdit";
+import { TResType } from "./DiscussionListCard";
 
 export interface IComment {
   id?: string; //id未提供为新建
   resId?: string;
-  resType?: string;
+  resType?: TResType;
   user: IUser;
   parent?: string;
   title?: string;
@@ -22,9 +23,8 @@ interface IWidget {
   onSelect?: Function;
   onCreated?: Function;
 }
-const CommentItemWidget = ({ data, onSelect, onCreated }: IWidget) => {
+const DiscussionItemWidget = ({ data, onSelect, onCreated }: IWidget) => {
   const [edit, setEdit] = useState(false);
-  console.log(data);
   return (
     <div style={{ display: "flex" }}>
       <div style={{ width: "2em" }}>
@@ -53,4 +53,4 @@ const CommentItemWidget = ({ data, onSelect, onCreated }: IWidget) => {
   );
 };
 
-export default CommentItemWidget;
+export default DiscussionItemWidget;

+ 3 - 3
dashboard/src/components/comment/CommentList.tsx → dashboard/src/components/discussion/DiscussionList.tsx

@@ -1,13 +1,13 @@
 import { List, Space } from "antd";
 import { MessageOutlined } from "@ant-design/icons";
 
-import { IComment } from "./CommentItem";
+import { IComment } from "./DiscussionItem";
 
 interface IWidget {
   data: IComment[];
   onSelect?: Function;
 }
-const CommentListWidget = ({ data, onSelect }: IWidget) => {
+const DiscussionListWidget = ({ data, onSelect }: IWidget) => {
   return (
     <div>
       <List
@@ -53,4 +53,4 @@ const CommentListWidget = ({ data, onSelect }: IWidget) => {
   );
 };
 
-export default CommentListWidget;
+export default DiscussionListWidget;

+ 37 - 40
dashboard/src/components/comment/CommentListCard.tsx → dashboard/src/components/discussion/DiscussionListCard.tsx

@@ -4,10 +4,10 @@ import { Card, message, Typography } from "antd";
 
 import { get } from "../../request";
 import { ICommentListResponse } from "../api/Comment";
-import CommentCreate from "./CommentCreate";
-import { IComment } from "./CommentItem";
-import CommentList from "./CommentList";
-import { IAnswerCount } from "./CommentBox";
+import CommentCreate from "./DiscussionCreate";
+import { IComment } from "./DiscussionItem";
+import CommentList from "./DiscussionList";
+import { IAnswerCount } from "./DiscussionBox";
 
 export type TResType = "article" | "channel" | "chapter" | "sentence" | "wbw";
 interface IWidget {
@@ -18,7 +18,7 @@ interface IWidget {
   onSelect?: Function;
   onItemCountChange?: Function;
 }
-const CommentListCardWidget = ({
+const DiscussionListCardWidget = ({
   resId,
   resType,
   topicId,
@@ -44,8 +44,7 @@ const CommentListCardWidget = ({
     let url: string = "";
     if (typeof topicId !== "undefined") {
       url = `/v2/discussion?view=question-by-topic&id=${topicId}`;
-    }
-    if (typeof resId !== "undefined") {
+    } else if (typeof resId !== "undefined") {
       url = `/v2/discussion?view=question&id=${resId}`;
     }
     if (url === "") {
@@ -88,40 +87,38 @@ const CommentListCardWidget = ({
   }
 
   return (
-    <div>
-      <Card title="讨论" extra={"More"}>
-        {data.length > 0 ? (
-          <CommentList
-            onSelect={(
-              e: React.MouseEvent<HTMLSpanElement, MouseEvent>,
-              comment: IComment
-            ) => {
-              if (typeof onSelect !== "undefined") {
-                onSelect(e, comment);
-              }
-            }}
-            data={data}
-          />
-        ) : undefined}
+    <Card title="讨论" extra={"More"}>
+      {data.length > 0 ? (
+        <CommentList
+          onSelect={(
+            e: React.MouseEvent<HTMLSpanElement, MouseEvent>,
+            comment: IComment
+          ) => {
+            if (typeof onSelect !== "undefined") {
+              onSelect(e, comment);
+            }
+          }}
+          data={data}
+        />
+      ) : undefined}
 
-        {resId && resType ? (
-          <CommentCreate
-            contentType="markdown"
-            resId={resId}
-            resType={resType}
-            onCreated={(e: IComment) => {
-              const newData = JSON.parse(JSON.stringify(e));
-              console.log("create", e);
-              if (typeof onItemCountChange !== "undefined") {
-                onItemCountChange(data.length + 1);
-              }
-              setData([...data, newData]);
-            }}
-          />
-        ) : undefined}
-      </Card>
-    </div>
+      {resId && resType ? (
+        <CommentCreate
+          contentType="markdown"
+          resId={resId}
+          resType={resType}
+          onCreated={(e: IComment) => {
+            const newData = JSON.parse(JSON.stringify(e));
+            console.log("create", e);
+            if (typeof onItemCountChange !== "undefined") {
+              onItemCountChange(data.length + 1);
+            }
+            setData([...data, newData]);
+          }}
+        />
+      ) : undefined}
+    </Card>
   );
 };
 
-export default CommentListCardWidget;
+export default DiscussionListCardWidget;

+ 5 - 0
dashboard/src/components/discussion/DiscussionListItem.tsx

@@ -0,0 +1,5 @@
+const DiscussionListItemWidget = () => {
+  return <div>change password</div>;
+};
+
+export default DiscussionListItemWidget;

+ 34 - 33
dashboard/src/components/comment/CommentShow.tsx → dashboard/src/components/discussion/DiscussionShow.tsx

@@ -1,8 +1,9 @@
+import { useIntl } from "react-intl";
 import { Button, Card, Dropdown, Space } from "antd";
 import { MoreOutlined } from "@ant-design/icons";
 import type { MenuProps } from "antd";
 
-import { IComment } from "./CommentItem";
+import { IComment } from "./DiscussionItem";
 import TimeShow from "../general/TimeShow";
 
 interface IWidget {
@@ -10,7 +11,8 @@ interface IWidget {
   onEdit?: Function;
   onSelect?: Function;
 }
-const CommentShowWidget = ({ data, onEdit, onSelect }: IWidget) => {
+const DiscussionShowWidget = ({ data, onEdit, onSelect }: IWidget) => {
+  const intl = useIntl();
   const onClick: MenuProps["onClick"] = (e) => {
     console.log("click ", e);
     switch (e.key) {
@@ -53,38 +55,37 @@ const CommentShowWidget = ({ data, onEdit, onSelect }: IWidget) => {
     },
   ];
   return (
-    <div>
-      <Card
-        size="small"
-        title={
-          <Space>
-            {data.user.nickName}
-            <TimeShow time={data.updatedAt} title="UpdatedAt" />
-          </Space>
-        }
-        extra={
-          <Dropdown menu={{ items, onClick }} placement="bottomRight">
-            <Button
-              shape="circle"
-              size="small"
-              icon={<MoreOutlined />}
-            ></Button>
-          </Dropdown>
-        }
-        style={{ width: "100%" }}
+    <Card
+      size="small"
+      title={
+        <Space>
+          {data.user.nickName}
+          <TimeShow
+            time={data.updatedAt}
+            title={intl.formatMessage({
+              id: "labels.updated-at",
+            })}
+          />
+        </Space>
+      }
+      extra={
+        <Dropdown menu={{ items, onClick }} placement="bottomRight">
+          <Button shape="circle" size="small" icon={<MoreOutlined />}></Button>
+        </Dropdown>
+      }
+      style={{ width: "100%" }}
+    >
+      <span
+        onClick={(e) => {
+          if (typeof onSelect !== "undefined") {
+            onSelect();
+          }
+        }}
       >
-        <span
-          onClick={(e) => {
-            if (typeof onSelect !== "undefined") {
-              onSelect();
-            }
-          }}
-        >
-          {data.content}
-        </span>
-      </Card>
-    </div>
+        {data.content}
+      </span>
+    </Card>
   );
 };
 
-export default CommentShowWidget;
+export default DiscussionShowWidget;

+ 42 - 0
dashboard/src/components/discussion/DiscussionTopic.tsx

@@ -0,0 +1,42 @@
+import { Divider } from "antd";
+
+import CommentTopicInfo from "./DiscussionTopicInfo";
+import CommentTopicChildren from "./DiscussionTopicChildren";
+import { IComment } from "./DiscussionItem";
+
+interface IWidget {
+  topicId?: string;
+  onItemCountChange?: Function;
+  onTopicReady?: Function;
+}
+const DiscussionTopicWidget = ({
+  topicId,
+  onTopicReady,
+  onItemCountChange,
+}: IWidget) => {
+  return (
+    <>
+      <CommentTopicInfo
+        topicId={topicId}
+        onReady={(value: IComment) => {
+          console.log("on Topic Ready", value);
+          if (typeof onTopicReady !== "undefined") {
+            onTopicReady(value);
+          }
+        }}
+      />
+      <Divider />
+      <CommentTopicChildren
+        topicId={topicId}
+        onItemCountChange={(count: number, e: string) => {
+          //把新建回答的消息传出去。
+          if (typeof onItemCountChange !== "undefined") {
+            onItemCountChange(count, e);
+          }
+        }}
+      />
+    </>
+  );
+};
+
+export default DiscussionTopicWidget;

+ 4 - 4
dashboard/src/components/comment/CommentTopicChildren.tsx → dashboard/src/components/discussion/DiscussionTopicChildren.tsx

@@ -3,15 +3,15 @@ import { useEffect, useState } from "react";
 import { useIntl } from "react-intl";
 import { get } from "../../request";
 import { ICommentListResponse } from "../api/Comment";
-import CommentCreate from "./CommentCreate";
+import CommentCreate from "./DiscussionCreate";
 
-import CommentItem, { IComment } from "./CommentItem";
+import CommentItem, { IComment } from "./DiscussionItem";
 
 interface IWidget {
   topicId?: string;
   onItemCountChange?: Function;
 }
-const CommentTopicChildrenWidget = ({
+const DiscussionTopicChildrenWidget = ({
   topicId,
   onItemCountChange,
 }: IWidget) => {
@@ -87,4 +87,4 @@ const CommentTopicChildrenWidget = ({
   );
 };
 
-export default CommentTopicChildrenWidget;
+export default DiscussionTopicChildrenWidget;

+ 9 - 3
dashboard/src/components/comment/CommentTopicInfo.tsx → dashboard/src/components/discussion/DiscussionTopicInfo.tsx

@@ -4,14 +4,15 @@ import { get } from "../../request";
 import { ICommentResponse } from "../api/Comment";
 import TimeShow from "../general/TimeShow";
 
-import { IComment } from "./CommentItem";
+import { IComment } from "./DiscussionItem";
 
 const { Title, Text } = Typography;
 
 interface IWidget {
   topicId?: string;
+  onReady?: Function;
 }
-const CommentTopicInfoWidget = ({ topicId }: IWidget) => {
+const DiscussionTopicInfoWidget = ({ topicId, onReady }: IWidget) => {
   const [data, setData] = useState<IComment>();
   useEffect(() => {
     if (typeof topicId === "undefined") {
@@ -27,6 +28,7 @@ const CommentTopicInfoWidget = ({ topicId }: IWidget) => {
             id: item.id,
             resId: item.res_id,
             resType: item.res_type,
+            parent: item.parent,
             user: item.editor,
             title: item.title,
             content: item.content,
@@ -34,6 +36,10 @@ const CommentTopicInfoWidget = ({ topicId }: IWidget) => {
             updatedAt: item.updated_at,
           };
           setData(discussion);
+          if (typeof onReady !== "undefined") {
+            console.log("on ready");
+            onReady(discussion);
+          }
         } else {
           message.error(json.message);
         }
@@ -65,4 +71,4 @@ const CommentTopicInfoWidget = ({ topicId }: IWidget) => {
   );
 };
 
-export default CommentTopicInfoWidget;
+export default DiscussionTopicInfoWidget;

+ 1 - 1
dashboard/src/components/feedback/CreateFeedback.tsx

@@ -1,5 +1,5 @@
 import modal from "antd/lib/modal";
-import CommentCreate from "../comment/CommentCreate";
+import CommentCreate from "../discussion/DiscussionCreate";
 
 const CreateFeedbackWidget = () => {
   const path = window.location.pathname;

+ 1 - 2
dashboard/src/components/template/SentEdit.tsx

@@ -1,11 +1,10 @@
 import { Card } from "antd";
 import { useEffect, useState } from "react";
-import { TChannelType } from "../api/Channel";
 import { IStudio } from "../auth/StudioName";
 
 import type { IUser } from "../auth/User";
 import { IChannel } from "../channel/Channel";
-import { TContentType } from "../comment/CommentCreate";
+import { TContentType } from "../discussion/DiscussionCreate";
 import { ITocPathNode } from "../corpus/TocPath";
 import SentContent from "./SentEdit/SentContent";
 import SentTab from "./SentEdit/SentTab";

+ 3 - 1
dashboard/src/components/template/SentEdit/SentAdd.tsx

@@ -6,13 +6,15 @@ import { IChannel } from "../../channel/Channel";
 import ChannelTableModal from "../../channel/ChannelTableModal";
 
 interface IWidget {
+  disableChannels?: string[];
   onSelect?: Function;
 }
-const Widget = ({ onSelect }: IWidget) => {
+const Widget = ({ disableChannels, onSelect }: IWidget) => {
   const [channelPickerOpen, setChannelPickerOpen] = useState(false);
 
   return (
     <ChannelTableModal
+      disableChannels={disableChannels}
       trigger={
         <Button
           type="dashed"

+ 7 - 0
dashboard/src/components/template/SentEdit/SentCanRead.tsx

@@ -32,6 +32,8 @@ const SentCanReadWidget = ({
   onReload,
 }: IWidget) => {
   const [sentData, setSentData] = useState<ISentence[]>([]);
+  const [channels, setChannels] = useState<string[]>();
+
   const user = useAppSelector(_currentUser);
 
   const load = () => {
@@ -41,6 +43,10 @@ const SentCanReadWidget = ({
       .then((json) => {
         if (json.ok) {
           console.log("sent load", json.data.rows);
+          const channels: string[] = json.data.rows.map(
+            (item) => item.channel.id
+          );
+          setChannels(channels);
           const newData: ISentence[] = json.data.rows.map((item) => {
             return {
               id: item.id,
@@ -88,6 +94,7 @@ const SentCanReadWidget = ({
         />
       </div>
       <SentAdd
+        disableChannels={channels}
         onSelect={(channel: IChannel) => {
           if (typeof user === "undefined") {
             return;

+ 23 - 1
dashboard/src/components/template/SentEdit/SentContent.tsx

@@ -3,7 +3,7 @@ import SentCell from "./SentCell";
 import { WbwSentCtl } from "../WbwSent";
 import { useAppSelector } from "../../../hooks";
 import { settingInfo } from "../../../reducers/setting";
-import { useEffect, useState } from "react";
+import { useEffect, useLayoutEffect, useRef, useState } from "react";
 import { GetUserSetting } from "../../auth/setting/default";
 import { mode } from "../../../reducers/article-mode";
 import { IWbw } from "../Wbw/WbwWord";
@@ -44,12 +44,22 @@ const SentContentWidget = ({
     left: 5,
     right: 5,
   });
+  const divShell = useRef<HTMLDivElement>(null);
   const settings = useAppSelector(settingInfo);
+  const [divShellWidth, setDivShellWidth] = useState<number>();
+
   useEffect(() => {
+    const width = divShell.current?.offsetWidth;
+    console.log("settings", width);
+    if (width && width < 550) {
+      setLayoutDirection("column");
+      return;
+    }
     const layoutDirection = GetUserSetting(
       "setting.layout.direction",
       settings
     );
+
     if (typeof layoutDirection === "string") {
       setLayoutDirection(layoutDirection as TDirection);
     }
@@ -72,8 +82,20 @@ const SentContentWidget = ({
         break;
     }
   }, [newMode]);
+
+  useLayoutEffect(() => {
+    const width = divShell.current?.offsetWidth;
+    setDivShellWidth(width);
+    console.log("width", width);
+    if (width && width < 550) {
+      setLayoutDirection("column");
+      return;
+    }
+  }, []);
+
   return (
     <div
+      ref={divShell}
       style={{
         display: "flex",
         flexDirection: layoutDirection,

+ 1 - 1
dashboard/src/components/template/SentEdit/SuggestionToolbar.tsx

@@ -2,7 +2,7 @@ import { Divider, Space, Tooltip, Typography } from "antd";
 import { CommentOutlined, LikeOutlined } from "@ant-design/icons";
 import { ISentence } from "../SentEdit";
 import { useState } from "react";
-import CommentBox from "../../comment/CommentBox";
+import CommentBox from "../../discussion/DiscussionBox";
 import SuggestionBox from "./SuggestionBox";
 import PrAcceptButton from "./PrAcceptButton";
 import { HandOutlinedIcon } from "../../../assets/icon";

+ 1 - 1
dashboard/src/components/template/Wbw/WbwDetail.tsx

@@ -12,7 +12,7 @@ import { LockIcon, UnLockIcon } from "../../../assets/icon";
 import { UploadFile } from "antd/es/upload/interface";
 import { IAttachmentResponse } from "../../api/Attachments";
 import WbwDetailAttachment from "./WbwDetailAttachment";
-import CommentBox from "../../comment/CommentBox";
+import CommentBox from "../../discussion/DiscussionBox";
 
 interface IWidget {
   data: IWbw;

+ 1 - 1
dashboard/src/components/template/Wbw/WbwPali.tsx

@@ -12,7 +12,7 @@ import WbwDetail from "./WbwDetail";
 import { IWbw, TWbwDisplayMode } from "./WbwWord";
 import { bookMarkColor } from "./WbwDetailBookMark";
 import WbwVideoButton from "./WbwVideoButton";
-import CommentBox from "../../comment/CommentBox";
+import CommentBox from "../../discussion/DiscussionBox";
 import PaliText from "./PaliText";
 import store from "../../../store";
 import { lookup } from "../../../reducers/command";

+ 1 - 0
dashboard/src/locales/zh-Hans/label.ts

@@ -14,6 +14,7 @@ const items = {
   "labels.first-wbw": "第一个逐词解析",
   "labels.first-translation": "第一个译文",
   "labels.first-course": "第一个课程",
+  "labels.updated-at": "更新于",
 };
 
 export default items;

+ 3 - 3
dashboard/src/pages/library/discussion/index.tsx

@@ -2,12 +2,12 @@ import { Outlet } from "react-router-dom";
 
 import HeadBar from "../../../components/library/HeadBar";
 import FooterBar from "../../../components/library/FooterBar";
-import { Col, Row } from "antd";
+import { Col, Layout, Row } from "antd";
 
 const Widget = () => {
   // TODO
   return (
-    <div>
+    <Layout>
       <HeadBar selectedKeys="discussion" />
       <Row>
         <Col flex={"auto"}></Col>
@@ -17,7 +17,7 @@ const Widget = () => {
         <Col flex={"auto"}></Col>
       </Row>
       <FooterBar />
-    </div>
+    </Layout>
   );
 };
 

+ 22 - 10
dashboard/src/pages/library/discussion/show.tsx

@@ -1,18 +1,30 @@
-import { useParams } from "react-router-dom";
-
-import CommentTopic from "../../../components/comment/CommentTopic";
+import { Divider } from "antd";
+import { useNavigate, useParams } from "react-router-dom";
+import DiscussionAnchor from "../../../components/discussion/DiscussionAnchor";
+import { IComment } from "../../../components/discussion/DiscussionItem";
+import DiscussionListCard, {
+  TResType,
+} from "../../../components/discussion/DiscussionListCard";
 
 const Widget = () => {
   // TODO
-  const { id } = useParams(); //url 参数
+  const { type, id } = useParams(); //url 参数
+  const navigate = useNavigate();
 
   return (
-    <div>
-      <div>锚点</div>
-      <div>
-        <CommentTopic topicId={id} />
-      </div>
-    </div>
+    <>
+      <DiscussionAnchor resId={id} resType={type as TResType} />
+      <Divider></Divider>
+      <DiscussionListCard
+        resId={id}
+        onSelect={(
+          e: React.MouseEvent<HTMLSpanElement, MouseEvent>,
+          comment: IComment
+        ) => {
+          navigate(`/discussion/topic/${comment.id}`);
+        }}
+      />
+    </>
   );
 };
 

+ 34 - 42
dashboard/src/pages/library/discussion/topic.tsx

@@ -1,54 +1,46 @@
 import { useNavigate } from "react-router-dom";
-import { Tabs } from "antd";
+import { Button, Card, Divider } from "antd";
 import { useParams } from "react-router-dom";
-import CommentAnchor from "../../../components/comment/CommentAnchor";
-import { IComment } from "../../../components/comment/CommentItem";
-import CommentListCard from "../../../components/comment/CommentListCard";
+import { ArrowLeftOutlined } from "@ant-design/icons";
 
-import CommentTopic from "../../../components/comment/CommentTopic";
+import CommentAnchor from "../../../components/discussion/DiscussionAnchor";
+import { IComment } from "../../../components/discussion/DiscussionItem";
+import CommentTopic from "../../../components/discussion/DiscussionTopic";
+import { useState } from "react";
 
 const Widget = () => {
-  // TODO
   const { id } = useParams(); //url 参数
   const navigate = useNavigate();
+  const [discussion, setDiscussion] = useState<IComment>();
   return (
-    <div>
-      <div>
-        <CommentAnchor id={id} />
-      </div>
-      <div>
-        <Tabs
-          defaultActiveKey="current"
-          items={[
-            {
-              label: `当前`,
-              key: "current",
-              children: <CommentTopic topicId={id} />,
-            },
-            {
-              label: `全部`,
-              key: "all",
-              children: (
-                <CommentListCard
-                  topicId={id}
-                  onSelect={(
-                    e: React.MouseEvent<HTMLSpanElement, MouseEvent>,
-                    comment: IComment
-                  ) => {
-                    navigate(`/discussion/topic/${comment.id}`);
-                  }}
-                />
-              ),
-            },
-            {
-              label: `类似`,
-              key: "sim",
-              children: "",
-            },
-          ]}
+    <>
+      <CommentAnchor resId={discussion?.resId} resType={discussion?.resType} />
+      <Divider></Divider>
+      <Card
+        title={
+          <Button
+            type="link"
+            disabled={discussion ? false : true}
+            icon={<ArrowLeftOutlined />}
+            onClick={() =>
+              navigate(
+                `/discussion/show/${discussion?.resType}/${discussion?.resId}`
+              )
+            }
+          >
+            {"全部"}
+          </Button>
+        }
+      >
+        <CommentTopic
+          topicId={id}
+          onTopicReady={(value: IComment) => {
+            console.log("onTopicReady");
+            setDiscussion(value);
+          }}
         />
-      </div>
-    </div>
+      </Card>
+    </>
   );
 };