Răsfoiți Sursa

Merge pull request #1437 from visuddhinanda/agile

discussion 移到侧边栏
visuddhinanda 2 ani în urmă
părinte
comite
0be6694857

+ 62 - 16
dashboard/src/components/article/RightPanel.tsx

@@ -1,6 +1,7 @@
-import { Affix, Button, Tabs } from "antd";
+import { Affix, Button, Space, Tabs } from "antd";
 import { useEffect, useState } from "react";
 import { CloseOutlined } from "@ant-design/icons";
+import { FullscreenOutlined, FullscreenExitOutlined } from "@ant-design/icons";
 
 import { IChannel } from "../channel/Channel";
 import ChannelPickerTable from "../channel/ChannelPickerTable";
@@ -9,8 +10,11 @@ import { ArticleType } from "./Article";
 import { useAppSelector } from "../../hooks";
 import { openPanel, rightPanel } from "../../reducers/right-panel";
 import store from "../../store";
+import DiscussionBox from "../discussion/DiscussionBox";
+import { show } from "../../reducers/discussion";
+import { useIntl } from "react-intl";
 
-export type TPanelName = "dict" | "channel" | "close" | "open";
+export type TPanelName = "dict" | "channel" | "discussion" | "close" | "open";
 interface IWidget {
   curr?: TPanelName;
   type: ArticleType;
@@ -31,8 +35,12 @@ const RightPanelWidget = ({
 }: IWidget) => {
   const [open, setOpen] = useState(false);
   const [activeTab, setActiveTab] = useState<string>("dict");
-
   const _openPanel = useAppSelector(rightPanel);
+  const intl = useIntl();
+
+  const divMinWidth = 400;
+  const divMaxWidth = 700;
+  const [divWidth, setDivWidth] = useState(divMinWidth);
 
   useEffect(() => {
     console.log("panel", _openPanel);
@@ -57,6 +65,10 @@ const RightPanelWidget = ({
         setOpen(true);
         setActiveTab(curr);
         break;
+      case "discussion":
+        setOpen(true);
+        setActiveTab(curr);
+        break;
       case "close":
         setOpen(false);
         break;
@@ -70,39 +82,66 @@ const RightPanelWidget = ({
       <div
         key="panel"
         style={{
-          width: 350,
+          width: divWidth,
           height: `calc(100vh - 44px)`,
           overflowY: "scroll",
           display: open ? "block" : "none",
+          paddingLeft: 8,
         }}
       >
         <Tabs
+          type="card"
           size="small"
           defaultActiveKey={curr}
           activeKey={activeTab}
           onChange={(activeKey: string) => setActiveTab(activeKey)}
           tabBarExtraContent={{
             right: (
-              <Button
-                type="text"
-                size="small"
-                icon={<CloseOutlined />}
-                onClick={() => {
-                  if (typeof onClose !== "undefined") {
-                    onClose();
-                  }
-                }}
-              />
+              <Space>
+                {divWidth === divMinWidth ? (
+                  <Button
+                    type="link"
+                    icon={<FullscreenOutlined />}
+                    onClick={() => setDivWidth(divMaxWidth)}
+                  />
+                ) : (
+                  <Button
+                    type="link"
+                    icon={<FullscreenExitOutlined />}
+                    onClick={() => setDivWidth(divMinWidth)}
+                  />
+                )}
+                <Button
+                  type="text"
+                  size="small"
+                  icon={<CloseOutlined />}
+                  onClick={() => {
+                    store.dispatch(
+                      show({
+                        type: "discussion",
+                        resType: "sentence",
+                      })
+                    );
+                    if (typeof onClose !== "undefined") {
+                      onClose();
+                    }
+                  }}
+                />
+              </Space>
             ),
           }}
           items={[
             {
-              label: `字典`,
+              label: intl.formatMessage({
+                id: "columns.library.dict.title",
+              }),
               key: "dict",
               children: <DictComponent />,
             },
             {
-              label: `channel`,
+              label: intl.formatMessage({
+                id: "columns.studio.channel.title",
+              }),
               key: "channel",
               children: (
                 <ChannelPickerTable
@@ -118,6 +157,13 @@ const RightPanelWidget = ({
                 />
               ),
             },
+            {
+              label: intl.formatMessage({
+                id: "buttons.discussion",
+              }),
+              key: "discussion",
+              children: <DiscussionBox />,
+            },
           ]}
         />
       </div>

+ 46 - 81
dashboard/src/components/discussion/DiscussionBox.tsx

@@ -1,37 +1,26 @@
 import { useState } from "react";
-import { Button, Divider, Drawer, Space } from "antd";
-import { FullscreenOutlined, FullscreenExitOutlined } from "@ant-design/icons";
+import { ArrowLeftOutlined } from "@ant-design/icons";
 
 import DiscussionTopic from "./DiscussionTopic";
-import DiscussionListCard, { TResType } from "./DiscussionListCard";
+import DiscussionListCard from "./DiscussionListCard";
 import { IComment } from "./DiscussionItem";
-import DiscussionAnchor from "./DiscussionAnchor";
-import { Link } from "react-router-dom";
+import { useAppSelector } from "../../hooks";
+import { countChange, message, showAnchor } from "../../reducers/discussion";
+import { Button } from "antd";
+import store from "../../store";
 
 export interface IAnswerCount {
   id: string;
   count: number;
 }
-interface IWidget {
-  trigger?: JSX.Element;
-  resId?: string;
-  resType?: TResType;
-  onCommentCountChange?: Function;
-}
-const DiscussionBoxWidget = ({
-  trigger,
-  resId,
-  resType,
-  onCommentCountChange,
-}: IWidget) => {
-  const [open, setOpen] = useState(false);
+
+const DiscussionBoxWidget = () => {
   const [childrenDrawer, setChildrenDrawer] = useState(false);
   const [topicComment, setTopicComment] = useState<IComment>();
   const [answerCount, setAnswerCount] = useState<IAnswerCount>();
-  const drawerMinWidth = 720;
-  const drawerMaxWidth = 1100;
 
-  const [drawerWidth, setDrawerWidth] = useState(drawerMinWidth);
+  const discussionMessage = useAppSelector(message);
+
   const showChildrenDrawer = (comment: IComment) => {
     setChildrenDrawer(true);
     setTopicComment(comment);
@@ -39,51 +28,38 @@ const DiscussionBoxWidget = ({
 
   return (
     <>
-      <span
+      <Button
+        type="link"
         onClick={() => {
-          setOpen(true);
-        }}
-      >
-        {trigger}
-      </span>
-      <Drawer
-        title="Discussion"
-        destroyOnClose
-        extra={
-          <Space>
-            <Link to={`/discussion/show/${resType}/${resId}`} target="_blank">
-              在新窗口打开
-            </Link>
-            {drawerWidth === drawerMinWidth ? (
-              <Button
-                type="link"
-                icon={<FullscreenOutlined />}
-                onClick={() => setDrawerWidth(drawerMaxWidth)}
-              />
-            ) : (
-              <Button
-                type="link"
-                icon={<FullscreenExitOutlined />}
-                onClick={() => setDrawerWidth(drawerMinWidth)}
-              />
-            )}
-          </Space>
-        }
-        width={drawerWidth}
-        onClose={() => {
-          setOpen(false);
-          if (document.getElementsByTagName("body")[0].hasAttribute("style")) {
-            document.getElementsByTagName("body")[0].removeAttribute("style");
-          }
+          store.dispatch(
+            showAnchor({
+              type: "discussion",
+              resId: discussionMessage?.resId,
+              resType: discussionMessage?.resType,
+            })
+          );
         }}
-        open={open}
-        maskClosable={false}
       >
-        <DiscussionAnchor resId={resId} resType={resType} />
-        <Divider></Divider>
+        显示译文
+      </Button>
+      {childrenDrawer ? (
+        <div>
+          <Button
+            shape="circle"
+            icon={<ArrowLeftOutlined />}
+            onClick={() => setChildrenDrawer(false)}
+          />
+          <DiscussionTopic
+            topicId={topicComment?.id}
+            onItemCountChange={(count: number, parent: string) => {
+              setAnswerCount({ id: parent, count: count });
+            }}
+          />
+        </div>
+      ) : (
         <DiscussionListCard
-          resId={resId}
-          resType={resType}
+          resId={discussionMessage?.resId}
+          resType={discussionMessage?.resType}
           onSelect={(
             e: React.MouseEvent<HTMLSpanElement, MouseEvent>,
             comment: IComment
@@ -91,27 +67,16 @@ const DiscussionBoxWidget = ({
           onReply={(comment: IComment) => showChildrenDrawer(comment)}
           changedAnswerCount={answerCount}
           onItemCountChange={(count: number) => {
-            if (typeof onCommentCountChange !== "undefined") {
-              onCommentCountChange(count);
-            }
+            store.dispatch(
+              countChange({
+                count: count,
+                resId: discussionMessage?.resId,
+                resType: discussionMessage?.resType,
+              })
+            );
           }}
         />
-        <Drawer
-          title="Answer"
-          width={700}
-          onClose={() => {
-            setChildrenDrawer(false);
-          }}
-          open={childrenDrawer}
-        >
-          <DiscussionTopic
-            topicId={topicComment?.id}
-            onItemCountChange={(count: number, parent: string) => {
-              setAnswerCount({ id: parent, count: count });
-            }}
-          />
-        </Drawer>
-      </Drawer>
+      )}
     </>
   );
 };

+ 119 - 0
dashboard/src/components/discussion/DiscussionDrawer.tsx

@@ -0,0 +1,119 @@
+import { useState } from "react";
+import { Button, Divider, Drawer, Space } from "antd";
+import { FullscreenOutlined, FullscreenExitOutlined } from "@ant-design/icons";
+
+import DiscussionTopic from "./DiscussionTopic";
+import DiscussionListCard, { TResType } from "./DiscussionListCard";
+import { IComment } from "./DiscussionItem";
+import DiscussionAnchor from "./DiscussionAnchor";
+import { Link } from "react-router-dom";
+
+export interface IAnswerCount {
+  id: string;
+  count: number;
+}
+interface IWidget {
+  trigger?: JSX.Element;
+  resId?: string;
+  resType?: TResType;
+  onCommentCountChange?: Function;
+}
+const DiscussionDrawerWidget = ({
+  trigger,
+  resId,
+  resType,
+  onCommentCountChange,
+}: IWidget) => {
+  const [open, setOpen] = useState(false);
+  const [childrenDrawer, setChildrenDrawer] = useState(false);
+  const [topicComment, setTopicComment] = useState<IComment>();
+  const [answerCount, setAnswerCount] = useState<IAnswerCount>();
+  const drawerMinWidth = 720;
+  const drawerMaxWidth = 1100;
+
+  const [drawerWidth, setDrawerWidth] = useState(drawerMinWidth);
+  const showChildrenDrawer = (comment: IComment) => {
+    setChildrenDrawer(true);
+    setTopicComment(comment);
+  };
+
+  return (
+    <>
+      <span
+        onClick={() => {
+          setOpen(true);
+        }}
+      >
+        {trigger}
+      </span>
+      <Drawer
+        title="Discussion"
+        destroyOnClose
+        extra={
+          <Space>
+            <Link to={`/discussion/show/${resType}/${resId}`} target="_blank">
+              在新窗口打开
+            </Link>
+            {drawerWidth === drawerMinWidth ? (
+              <Button
+                type="link"
+                icon={<FullscreenOutlined />}
+                onClick={() => setDrawerWidth(drawerMaxWidth)}
+              />
+            ) : (
+              <Button
+                type="link"
+                icon={<FullscreenExitOutlined />}
+                onClick={() => setDrawerWidth(drawerMinWidth)}
+              />
+            )}
+          </Space>
+        }
+        width={drawerWidth}
+        onClose={() => {
+          setOpen(false);
+          if (document.getElementsByTagName("body")[0].hasAttribute("style")) {
+            document.getElementsByTagName("body")[0].removeAttribute("style");
+          }
+        }}
+        open={open}
+        maskClosable={false}
+      >
+        <DiscussionAnchor resId={resId} resType={resType} />
+        <Divider></Divider>
+        <DiscussionListCard
+          resId={resId}
+          resType={resType}
+          onSelect={(
+            e: React.MouseEvent<HTMLSpanElement, MouseEvent>,
+            comment: IComment
+          ) => showChildrenDrawer(comment)}
+          onReply={(comment: IComment) => showChildrenDrawer(comment)}
+          changedAnswerCount={answerCount}
+          onItemCountChange={(count: number) => {
+            if (typeof onCommentCountChange !== "undefined") {
+              onCommentCountChange(count);
+            }
+          }}
+        />
+        <Drawer
+          title="Answer"
+          width={700}
+          onClose={() => {
+            setChildrenDrawer(false);
+          }}
+          open={childrenDrawer}
+        >
+          <DiscussionTopic
+            topicId={topicComment?.id}
+            onItemCountChange={(count: number, parent: string) => {
+              setAnswerCount({ id: parent, count: count });
+            }}
+          />
+        </Drawer>
+      </Drawer>
+    </>
+  );
+};
+
+export default DiscussionDrawerWidget;

+ 145 - 144
dashboard/src/components/discussion/DiscussionListCard.tsx

@@ -11,13 +11,14 @@ import {
 
 import DiscussionItem, { IComment } from "./DiscussionItem";
 
-import { IAnswerCount } from "./DiscussionBox";
+import { IAnswerCount } from "./DiscussionDrawer";
 import { ActionType, ProList } from "@ant-design/pro-components";
 import { renderBadge } from "../channel/ChannelTable";
 import DiscussionCreate from "./DiscussionCreate";
 const { Panel } = Collapse;
 
 export type TResType = "article" | "channel" | "chapter" | "sentence" | "wbw";
+
 interface IWidget {
   resId?: string;
   resType?: TResType;
@@ -42,6 +43,10 @@ const DiscussionListCardWidget = ({
   const [closeNumber, setCloseNumber] = useState<number>(0);
   const [count, setCount] = useState<number>(0);
 
+  useEffect(() => {
+    ref.current?.reload();
+  }, [resId, resType]);
+
   useEffect(() => {
     console.log("changedAnswerCount", changedAnswerCount);
     ref.current?.reload();
@@ -57,152 +62,148 @@ const DiscussionListCardWidget = ({
 
   return (
     <>
-      <Collapse bordered={false} defaultActiveKey="list">
-        <Panel header="讨论列表" key="list">
-          <ProList<IComment>
-            itemLayout="vertical"
-            rowKey="id"
-            actionRef={ref}
-            metas={{
-              avatar: {
-                render(dom, entity, index, action, schema) {
-                  return <></>;
-                },
-              },
-              title: {
-                render(dom, entity, index, action, schema) {
-                  return <></>;
-                },
-              },
-              content: {
-                render: (text, row, index, action) => {
-                  return (
-                    <DiscussionItem
-                      data={row}
-                      onSelect={(
-                        e: React.MouseEvent<HTMLSpanElement, MouseEvent>,
-                        data: IComment
-                      ) => {
-                        if (typeof onSelect !== "undefined") {
-                          onSelect(e, data);
-                        }
-                      }}
-                      onDelete={() => {
+      <ProList<IComment>
+        itemLayout="vertical"
+        rowKey="id"
+        actionRef={ref}
+        metas={{
+          avatar: {
+            render(dom, entity, index, action, schema) {
+              return <></>;
+            },
+          },
+          title: {
+            render(dom, entity, index, action, schema) {
+              return <></>;
+            },
+          },
+          content: {
+            render: (text, row, index, action) => {
+              return (
+                <DiscussionItem
+                  data={row}
+                  onSelect={(
+                    e: React.MouseEvent<HTMLSpanElement, MouseEvent>,
+                    data: IComment
+                  ) => {
+                    if (typeof onSelect !== "undefined") {
+                      onSelect(e, data);
+                    }
+                  }}
+                  onDelete={() => {
+                    ref.current?.reload();
+                  }}
+                  onReply={() => {
+                    if (typeof onReply !== "undefined") {
+                      onReply(row);
+                    }
+                  }}
+                  onClose={(value: boolean) => {
+                    console.log("comment", row);
+                    put<ICommentRequest, ICommentResponse>(
+                      `/v2/discussion/${row.id}`,
+                      {
+                        title: row.title,
+                        content: row.content,
+                        status: value ? "close" : "active",
+                      }
+                    ).then((json) => {
+                      console.log(json);
+                      if (json.ok) {
                         ref.current?.reload();
-                      }}
-                      onReply={() => {
-                        if (typeof onReply !== "undefined") {
-                          onReply(row);
-                        }
-                      }}
-                      onClose={(value: boolean) => {
-                        console.log("comment", row);
-                        put<ICommentRequest, ICommentResponse>(
-                          `/v2/discussion/${row.id}`,
-                          {
-                            title: row.title,
-                            content: row.content,
-                            status: value ? "close" : "active",
-                          }
-                        ).then((json) => {
-                          console.log(json);
-                          if (json.ok) {
-                            ref.current?.reload();
-                          }
-                        });
-                      }}
-                    />
-                  );
-                },
+                      }
+                    });
+                  }}
+                />
+              );
+            },
+          },
+        }}
+        request={async (params = {}, sorter, filter) => {
+          let url: string = "/v2/discussion?";
+          if (typeof topicId !== "undefined") {
+            url += `view=question-by-topic&id=${topicId}`;
+          } else if (typeof resId !== "undefined") {
+            url += `view=question&id=${resId}`;
+          } else {
+            return {
+              total: 0,
+              succcess: false,
+            };
+          }
+          const offset =
+            ((params.current ? params.current : 1) - 1) *
+            (params.pageSize ? params.pageSize : 20);
+          url += `&limit=${params.pageSize}&offset=${offset}`;
+          url += params.keyword ? "&search=" + params.keyword : "";
+          url += activeKey ? "&status=" + activeKey : "";
+          console.log("url", url);
+          const res = await get<ICommentListResponse>(url);
+          setCount(res.data.active);
+          const items: IComment[] = res.data.rows.map((item, id) => {
+            return {
+              id: item.id,
+              resId: item.res_id,
+              resType: item.res_type,
+              user: item.editor,
+              title: item.title,
+              parent: item.parent,
+              content: item.content,
+              status: item.status,
+              childrenCount: item.children_count,
+              createdAt: item.created_at,
+              updatedAt: item.updated_at,
+            };
+          });
+          setActiveNumber(res.data.active);
+          setCloseNumber(res.data.close);
+          return {
+            total: res.data.count,
+            succcess: true,
+            data: items,
+          };
+        }}
+        bordered
+        pagination={{
+          showQuickJumper: true,
+          showSizeChanger: true,
+          pageSize: 20,
+        }}
+        search={false}
+        options={{
+          search: false,
+        }}
+        toolbar={{
+          menu: {
+            activeKey,
+            items: [
+              {
+                key: "active",
+                label: (
+                  <span>
+                    active
+                    {renderBadge(activeNumber, activeKey === "active")}
+                  </span>
+                ),
               },
-            }}
-            request={async (params = {}, sorter, filter) => {
-              let url: string = "/v2/discussion?";
-              if (typeof topicId !== "undefined") {
-                url += `view=question-by-topic&id=${topicId}`;
-              } else if (typeof resId !== "undefined") {
-                url += `view=question&id=${resId}`;
-              } else {
-                return {
-                  total: 0,
-                  succcess: false,
-                };
-              }
-              const offset =
-                ((params.current ? params.current : 1) - 1) *
-                (params.pageSize ? params.pageSize : 20);
-              url += `&limit=${params.pageSize}&offset=${offset}`;
-              url += params.keyword ? "&search=" + params.keyword : "";
-              url += activeKey ? "&status=" + activeKey : "";
-              console.log("url", url);
-              const res = await get<ICommentListResponse>(url);
-              setCount(res.data.active);
-              const items: IComment[] = res.data.rows.map((item, id) => {
-                return {
-                  id: item.id,
-                  resId: item.res_id,
-                  resType: item.res_type,
-                  user: item.editor,
-                  title: item.title,
-                  parent: item.parent,
-                  content: item.content,
-                  status: item.status,
-                  childrenCount: item.children_count,
-                  createdAt: item.created_at,
-                  updatedAt: item.updated_at,
-                };
-              });
-              setActiveNumber(res.data.active);
-              setCloseNumber(res.data.close);
-              return {
-                total: res.data.count,
-                succcess: true,
-                data: items,
-              };
-            }}
-            bordered
-            pagination={{
-              showQuickJumper: true,
-              showSizeChanger: true,
-              pageSize: 20,
-            }}
-            search={false}
-            options={{
-              search: false,
-            }}
-            toolbar={{
-              menu: {
-                activeKey,
-                items: [
-                  {
-                    key: "active",
-                    label: (
-                      <span>
-                        active
-                        {renderBadge(activeNumber, activeKey === "active")}
-                      </span>
-                    ),
-                  },
-                  {
-                    key: "close",
-                    label: (
-                      <span>
-                        close
-                        {renderBadge(closeNumber, activeKey === "close")}
-                      </span>
-                    ),
-                  },
-                ],
-                onChange(key) {
-                  console.log("show course", key);
-                  setActiveKey(key);
-                  ref.current?.reload();
-                },
+              {
+                key: "close",
+                label: (
+                  <span>
+                    close
+                    {renderBadge(closeNumber, activeKey === "close")}
+                  </span>
+                ),
               },
-            }}
-          />
-        </Panel>
-      </Collapse>
+            ],
+            onChange(key) {
+              console.log("show course", key);
+              setActiveKey(key);
+              ref.current?.reload();
+            },
+          },
+        }}
+      />
 
       {resId && resType ? (
         <DiscussionCreate

+ 20 - 3
dashboard/src/components/template/SentEdit/SentCell.tsx

@@ -15,6 +15,7 @@ import { my_to_roman } from "../../code/my";
 import SentWbwEdit, { sentSave } from "./SentWbwEdit";
 import { getEnding } from "../../../reducers/nissaya-ending-vocabulary";
 import { nissayaBase } from "../Nissaya/NissayaMeaning";
+import { anchor, message } from "../../../reducers/discussion";
 
 interface IWidget {
   initValue?: ISentence;
@@ -37,9 +38,27 @@ const SentCellWidget = ({
   const intl = useIntl();
   const [isEditMode, setIsEditMode] = useState(editMode);
   const [sentData, setSentData] = useState<ISentence | undefined>(initValue);
+  const [bgColor, setBgColor] = useState<string>();
   const endings = useAppSelector(getEnding);
   const acceptPr = useAppSelector(sentence);
   const [prOpen, setPrOpen] = useState(false);
+  const discussionMessage = useAppSelector(message);
+  const anchorInfo = useAppSelector(anchor);
+  const sid = `${sentData?.book}_${sentData?.para}_${sentData?.wordStart}_${sentData?.wordEnd}_${sentData?.channel.id}`;
+  useEffect(() => {
+    if (discussionMessage?.resId === initValue?.id) {
+      setBgColor("wheat");
+    } else {
+      setBgColor("unset");
+    }
+  }, [discussionMessage?.resId, initValue?.id]);
+
+  useEffect(() => {
+    if (anchorInfo?.resId === initValue?.id) {
+      const ele = document.getElementById(sid);
+      ele?.scrollIntoView();
+    }
+  }, [anchorInfo, initValue?.id, sid]);
 
   useEffect(() => {
     if (value) {
@@ -60,10 +79,8 @@ const SentCellWidget = ({
     }
   }, [acceptPr, sentData, isPr]);
 
-  const sid = `${sentData?.book}_${sentData?.para}_${sentData?.wordStart}_${sentData?.wordEnd}_${sentData?.channel.id}`;
-
   return (
-    <div style={{ marginBottom: "8px" }}>
+    <div style={{ marginBottom: "8px", backgroundColor: bgColor }}>
       {isPr ? undefined : (
         <div
           dangerouslySetInnerHTML={{

+ 39 - 20
dashboard/src/components/template/SentEdit/SuggestionToolbar.tsx

@@ -1,11 +1,15 @@
 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 "../../discussion/DiscussionBox";
+import { useEffect, useState } from "react";
+import CommentBox from "../../discussion/DiscussionDrawer";
 import SuggestionBox from "./SuggestionBox";
 import PrAcceptButton from "./PrAcceptButton";
 import { HandOutlinedIcon } from "../../../assets/icon";
+import store from "../../../store";
+import { count, show } from "../../../reducers/discussion";
+import { useAppSelector } from "../../../hooks";
+import { openPanel } from "../../../reducers/right-panel";
 
 const { Text, Paragraph } = Typography;
 
@@ -30,6 +34,16 @@ const SuggestionToolbarWidget = ({
   const [CommentCount, setCommentCount] = useState<number | undefined>(
     data.suggestionCount?.discussion
   );
+  const discussionCount = useAppSelector(count);
+
+  useEffect(() => {
+    if (
+      discussionCount?.resType === "sentence" &&
+      discussionCount.resId === data.id
+    ) {
+      setCommentCount(discussionCount.count);
+    }
+  }, [data.id, discussionCount]);
 
   return (
     <Paragraph type="secondary" style={style}>
@@ -63,27 +77,32 @@ const SuggestionToolbarWidget = ({
             }
           />
           {compact ? undefined : <Divider type="vertical" />}
+          <Tooltip title="讨论">
+            <Space
+              size={"small"}
+              style={{
+                cursor: "pointer",
+                color: CommentCount && CommentCount > 0 ? "#1890ff" : "unset",
+              }}
+              onClick={(event) => {
+                store.dispatch(
+                  show({
+                    type: "discussion",
+                    resId: data.id,
+                    resType: "sentence",
+                  })
+                );
+                store.dispatch(openPanel("discussion"));
+              }}
+            >
+              <CommentOutlined />
+              {CommentCount}
+            </Space>
+          </Tooltip>
           <CommentBox
             resId={data.id}
             resType="sentence"
-            trigger={
-              <Tooltip title="讨论">
-                <Space
-                  size={"small"}
-                  style={{
-                    cursor: "pointer",
-                    color:
-                      data.suggestionCount?.discussion &&
-                      data.suggestionCount?.discussion > 0
-                        ? "#1890ff"
-                        : "unset",
-                  }}
-                >
-                  <CommentOutlined />
-                  {CommentCount}
-                </Space>
-              </Tooltip>
-            }
+            trigger={<></>}
             onCommentCountChange={(count: number) => {
               setCommentCount(count);
             }}

+ 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 "../../discussion/DiscussionBox";
+import CommentBox from "../../discussion/DiscussionDrawer";
 
 interface IWidget {
   data: IWbw;

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

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

+ 1 - 1
dashboard/src/components/term/TermItem.tsx

@@ -27,7 +27,7 @@ const TermItemWidget = ({ data }: IWidget) => {
             </Space>
             <Space style={{ fontSize: "80%" }}>
               <StudioName data={data?.studio} />
-              {data?.channel ? data.channel.name : ""}
+              {data?.channel ? data.channel.name : "通用于此studio"}
               <Text type="secondary">
                 <UserName {...data?.editor} />
               </Text>

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

@@ -69,6 +69,7 @@ const items = {
   "buttons.channel.public": "公开",
   "buttons.refresh": "刷新",
   "buttons.timeline": "时间线",
+  "buttons.discussion": "讨论",
 };
 
 export default items;

+ 54 - 0
dashboard/src/reducers/discussion.ts

@@ -0,0 +1,54 @@
+/**
+ * 查字典,添加术语命令
+ */
+import { createSlice, PayloadAction } from "@reduxjs/toolkit";
+
+import { TResType } from "../components/discussion/DiscussionListCard";
+
+import type { RootState } from "../store";
+
+export interface ITermCommand {}
+
+export interface IShowDiscussion {
+  type: "discussion" | "pr";
+  resType?: TResType;
+  resId?: string;
+}
+export interface ICount {
+  count: number;
+  resType?: TResType;
+  resId?: string;
+}
+interface IState {
+  message?: IShowDiscussion;
+  count?: ICount;
+  anchor?: IShowDiscussion;
+}
+
+const initialState: IState = {};
+
+export const slice = createSlice({
+  name: "discussion",
+  initialState,
+  reducers: {
+    show: (state, action: PayloadAction<IShowDiscussion>) => {
+      state.message = action.payload;
+    },
+    countChange: (state, action: PayloadAction<ICount>) => {
+      state.count = action.payload;
+    },
+    showAnchor: (state, action: PayloadAction<IShowDiscussion>) => {
+      state.anchor = action.payload;
+    },
+  },
+});
+
+export const { show, countChange, showAnchor } = slice.actions;
+
+export const message = (state: RootState): IShowDiscussion | undefined =>
+  state.discussion.message;
+export const count = (state: RootState): ICount | undefined =>
+  state.discussion.count;
+export const anchor = (state: RootState): IShowDiscussion | undefined =>
+  state.discussion.anchor;
+export default slice.reducer;

+ 2 - 0
dashboard/src/store.ts

@@ -22,6 +22,7 @@ import paraChangeReducer from "./reducers/para-change";
 import rightPanelReducer from "./reducers/right-panel";
 import sentWordsReducer from "./reducers/sent-word";
 import netStatusReducer from "./reducers/net-status";
+import discussionReducer from "./reducers/discussion";
 
 const store = configureStore({
   reducer: {
@@ -47,6 +48,7 @@ const store = configureStore({
     rightPanel: rightPanelReducer,
     sentWords: sentWordsReducer,
     netStatus: netStatusReducer,
+    discussion: discussionReducer,
   },
 });