Jelajahi Sumber

Merge pull request #1418 from visuddhinanda/agile

add TermCommunity
visuddhinanda 2 tahun lalu
induk
melakukan
d6db52b01b

+ 18 - 13
dashboard/src/components/anthology/AnthologyTocTree.tsx

@@ -17,6 +17,7 @@ const AnthologyTocTreeWidget = ({
   onArticleSelect,
   onArticleSelect,
 }: IWidget) => {
 }: IWidget) => {
   const [tocData, setTocData] = useState<ListNodeData[]>([]);
   const [tocData, setTocData] = useState<ListNodeData[]>([]);
+  const [expandedKeys, setExpandedKeys] = useState<string[]>();
 
 
   useEffect(() => {
   useEffect(() => {
     if (typeof anthologyId === "undefined") {
     if (typeof anthologyId === "undefined") {
@@ -35,23 +36,27 @@ const AnthologyTocTreeWidget = ({
           };
           };
         });
         });
         setTocData(toc);
         setTocData(toc);
+        setExpandedKeys(
+          json.data.rows
+            .filter((value) => value.level === 1)
+            .map((item) => (item.article_id ? item.article_id : item.title))
+        );
       }
       }
     });
     });
   }, [anthologyId]);
   }, [anthologyId]);
   return (
   return (
-    <div>
-      <TocTree
-        treeData={tocData}
-        onSelect={(keys: string[]) => {
-          if (
-            typeof onArticleSelect !== "undefined" &&
-            typeof anthologyId !== "undefined"
-          ) {
-            onArticleSelect(anthologyId, keys);
-          }
-        }}
-      />
-    </div>
+    <TocTree
+      treeData={tocData}
+      expandedKeys={expandedKeys}
+      onSelect={(keys: string[]) => {
+        if (
+          typeof onArticleSelect !== "undefined" &&
+          typeof anthologyId !== "undefined"
+        ) {
+          onArticleSelect(anthologyId, keys);
+        }
+      }}
+    />
   );
   );
 };
 };
 
 

+ 1 - 0
dashboard/src/components/api/Term.ts

@@ -30,6 +30,7 @@ export interface ITermDataResponse {
   studio: IStudio;
   studio: IStudio;
   editor: IUser;
   editor: IUser;
   role?: TRole;
   role?: TRole;
+  exp?: number;
   language: string;
   language: string;
   created_at: string;
   created_at: string;
   updated_at: string;
   updated_at: string;

+ 25 - 1
dashboard/src/components/article/PaliTextToc.tsx

@@ -14,6 +14,9 @@ interface IWidget {
 }
 }
 const PaliTextTocWidget = ({ book, para, channel, onSelect }: IWidget) => {
 const PaliTextTocWidget = ({ book, para, channel, onSelect }: IWidget) => {
   const [tocList, setTocList] = useState<ListNodeData[]>([]);
   const [tocList, setTocList] = useState<ListNodeData[]>([]);
+  const [selectedKeys, setSelectedKeys] = useState<Key[]>();
+  const [expandedKeys, setExpandedKeys] = useState<Key[]>();
+
   useEffect(() => {
   useEffect(() => {
     get<IPaliTocListResponse>(
     get<IPaliTocListResponse>(
       `/v2/palitext?view=book-toc&book=${book}&para=${para}`
       `/v2/palitext?view=book-toc&book=${book}&para=${para}`
@@ -26,12 +29,33 @@ const PaliTextTocWidget = ({ book, para, channel, onSelect }: IWidget) => {
         };
         };
       });
       });
       setTocList(toc);
       setTocList(toc);
+      if (json.data.rows.length > 0) {
+        let currLevel = 0;
+        let path: string[] = [];
+        for (let index = json.data.rows.length - 1; index >= 0; index--) {
+          const element = json.data.rows[index];
+          if (element.book === book && element.paragraph === para) {
+            currLevel = parseInt(element.level);
+          }
+          if (
+            parseInt(element.level) === 1 ||
+            (element.book === book && parseInt(element.level) < currLevel)
+          ) {
+            currLevel = parseInt(element.level);
+            path.push(`${element.book}-${element.paragraph}`);
+          }
+        }
+        setExpandedKeys(path);
+      }
+      setSelectedKeys([`${book}-${para}`]);
     });
     });
   }, [book, para]);
   }, [book, para]);
+
   return (
   return (
     <TocTree
     <TocTree
       treeData={tocList}
       treeData={tocList}
-      expandedKey={[`${book}-${para}`]}
+      expandedKeys={expandedKeys}
+      selectedKeys={selectedKeys}
       onSelect={(selectedKeys: Key[]) => {
       onSelect={(selectedKeys: Key[]) => {
         if (typeof onSelect !== "undefined") {
         if (typeof onSelect !== "undefined") {
           onSelect(selectedKeys);
           onSelect(selectedKeys);

+ 29 - 17
dashboard/src/components/article/TocTree.tsx

@@ -1,9 +1,9 @@
 import { Tree, Typography } from "antd";
 import { Tree, Typography } from "antd";
-import type { TreeProps } from "antd/es/tree";
 import { useEffect, useState } from "react";
 import { useEffect, useState } from "react";
 
 
 import type { ListNodeData } from "./EditableTree";
 import type { ListNodeData } from "./EditableTree";
 import PaliText from "../template/Wbw/PaliText";
 import PaliText from "../template/Wbw/PaliText";
+import { Key } from "antd/lib/table/interface";
 
 
 const { Text } = Typography;
 const { Text } = Typography;
 
 
@@ -91,37 +91,49 @@ function tocGetTreeData(
 
 
 interface IWidgetTocTree {
 interface IWidgetTocTree {
   treeData?: ListNodeData[];
   treeData?: ListNodeData[];
-  expandedKey?: string[];
+  expandedKeys?: Key[];
+  selectedKeys?: Key[];
   onSelect?: Function;
   onSelect?: Function;
 }
 }
 
 
-const TocTreeWidget = ({ treeData, expandedKey, onSelect }: IWidgetTocTree) => {
+const TocTreeWidget = ({
+  treeData,
+  expandedKeys,
+  selectedKeys,
+  onSelect,
+}: IWidgetTocTree) => {
   const [tree, setTree] = useState<TreeNodeData[]>();
   const [tree, setTree] = useState<TreeNodeData[]>();
-  const [expanded, setExpanded] = useState(expandedKey);
-  console.log("new tree data", treeData);
+  const [expanded, setExpanded] = useState<Key[]>();
+  const [selected, setSelected] = useState<Key[]>();
+
   useEffect(() => {
   useEffect(() => {
+    console.log("new tree data", treeData);
     if (treeData && treeData.length > 0) {
     if (treeData && treeData.length > 0) {
       const data = tocGetTreeData(treeData);
       const data = tocGetTreeData(treeData);
       setTree(data);
       setTree(data);
-      setExpanded(expandedKey);
-      console.log("create tree", treeData.length, expandedKey);
+      console.log("create tree", treeData.length);
     } else {
     } else {
       setTree([]);
       setTree([]);
     }
     }
-  }, [treeData, expandedKey]);
-  const onNodeSelect: TreeProps["onSelect"] = (selectedKeys, info) => {
-    console.log("selected", selectedKeys);
-    if (typeof onSelect !== "undefined") {
-      onSelect(selectedKeys);
-    }
-  };
+  }, [treeData]);
+
+  useEffect(() => setSelected(selectedKeys), [selectedKeys]);
+  useEffect(() => setExpanded(expandedKeys), [expandedKeys]);
 
 
   return (
   return (
     <Tree
     <Tree
-      onSelect={onNodeSelect}
       treeData={tree}
       treeData={tree}
-      defaultExpandedKeys={expanded}
-      defaultSelectedKeys={expanded}
+      expandedKeys={expanded}
+      selectedKeys={selected}
+      onExpand={(expandedKeys: Key[]) => {
+        setExpanded(expandedKeys);
+      }}
+      onSelect={(selectedKeys: Key[]) => {
+        setSelected(selectedKeys);
+        if (typeof onSelect !== "undefined") {
+          onSelect(selectedKeys);
+        }
+      }}
       blockNode
       blockNode
       titleRender={(node: TreeNodeData) => {
       titleRender={(node: TreeNodeData) => {
         const currNode =
         const currNode =

+ 2 - 2
dashboard/src/components/corpus/PaliChapterHead.tsx

@@ -16,14 +16,14 @@ const PaliChapterHeadWidget = ({ para, onChange }: IWidget) => {
   const [pathData, setPathData] = useState<ITocPathNode[]>([]);
   const [pathData, setPathData] = useState<ITocPathNode[]>([]);
   const [chapterData, setChapterData] = useState<IChapterInfo>({ title: "" });
   const [chapterData, setChapterData] = useState<IChapterInfo>({ title: "" });
   useEffect(() => {
   useEffect(() => {
-    console.log("palichapterlist useEffect");
+    console.log("pali chapter list useEffect");
     fetchData(para);
     fetchData(para);
   }, [para]);
   }, [para]);
 
 
   function fetchData(para: IChapter) {
   function fetchData(para: IChapter) {
     let url = `/v2/palitext?view=paragraph&book=${para.book}&para=${para.para}`;
     let url = `/v2/palitext?view=paragraph&book=${para.book}&para=${para.para}`;
     get<IApiResponsePaliChapter>(url).then(function (myJson) {
     get<IApiResponsePaliChapter>(url).then(function (myJson) {
-      console.log("ajex", myJson);
+      console.log("ajax", myJson);
       const data = myJson;
       const data = myJson;
       let path: ITocPathNode[] = JSON.parse(data.data.path);
       let path: ITocPathNode[] = JSON.parse(data.data.path);
       path.push({
       path.push({

+ 12 - 0
dashboard/src/components/dict/GrammarPop.tsx

@@ -50,4 +50,16 @@ const GrammarPopWidget = ({ text, gid }: IWidget) => {
   );
   );
 };
 };
 
 
+interface IWidgetShell {
+  props: string;
+}
+export const GrammarPopShell = ({ props }: IWidgetShell) => {
+  const prop = JSON.parse(atob(props)) as IWidget;
+  return (
+    <>
+      <GrammarPopWidget {...prop} />
+    </>
+  );
+};
+
 export default GrammarPopWidget;
 export default GrammarPopWidget;

+ 2 - 0
dashboard/src/components/dict/WordCard.tsx

@@ -6,6 +6,7 @@ import GrammarPop from "./GrammarPop";
 import WordCardByDict from "./WordCardByDict";
 import WordCardByDict from "./WordCardByDict";
 import { useIntl } from "react-intl";
 import { useIntl } from "react-intl";
 import Community from "./Community";
 import Community from "./Community";
+import TermCommunity from "../term/TermCommunity";
 
 
 const { Title, Text } = Typography;
 const { Title, Text } = Typography;
 
 
@@ -81,6 +82,7 @@ const WordCardWidget = ({ data }: IWidgetWordCard) => {
         <Text>{caseList}</Text>
         <Text>{caseList}</Text>
       </div>
       </div>
       <Community word={data.word} />
       <Community word={data.word} />
+      <TermCommunity word={data.word} />
       <div>
       <div>
         {data.dict.map((it, id) => {
         {data.dict.map((it, id) => {
           return <WordCardByDict key={id} data={it} />;
           return <WordCardByDict key={id} data={it} />;

+ 3 - 13
dashboard/src/components/dict/WordCardByDict.tsx

@@ -1,7 +1,8 @@
 import { Card } from "antd";
 import { Card } from "antd";
 import { Typography } from "antd";
 import { Typography } from "antd";
+import MdView from "../template/MdView";
 
 
-import IWidgetGrammarPop from "./GrammarPop";
+import GrammarPop from "./GrammarPop";
 
 
 const { Title, Text } = Typography;
 const { Title, Text } = Typography;
 
 
@@ -21,18 +22,7 @@ const WordCardByDictWidget = (prop: IWidgetWordCardByDict) => {
         {prop.data.dictname}
         {prop.data.dictname}
       </Title>
       </Title>
       <div>
       <div>
-        <Text>
-          {prop.data.note.split("|").map((it, id) => {
-            if (it.slice(0, 1) === "@") {
-              const [showText, keyText] = it.slice(1).split("-");
-              return (
-                <IWidgetGrammarPop key={id} gid={keyText} text={showText} />
-              );
-            } else {
-              return <span key={id * 200}>{it}</span>;
-            }
-          })}
-        </Text>
+        <MdView html={prop.data.note} />
       </div>
       </div>
     </Card>
     </Card>
   );
   );

+ 12 - 0
dashboard/src/components/discussion/DiscussionShow.tsx

@@ -234,6 +234,18 @@ const DiscussionShowWidget = ({
               </>
               </>
             ) : undefined}
             ) : undefined}
           </span>
           </span>
+          <Button
+            type="text"
+            onClick={() => {
+              if (typeof onReply !== "undefined") {
+                onReply();
+              }
+            }}
+          >
+            {intl.formatMessage({
+              id: "buttons.reply",
+            })}
+          </Button>
           <Dropdown
           <Dropdown
             menu={{ items, onClick }}
             menu={{ items, onClick }}
             placement="bottomRight"
             placement="bottomRight"

+ 2 - 1
dashboard/src/components/nut/Home.tsx

@@ -9,6 +9,7 @@ import TreeTest from "./TreeTest";
 import { Layout, Typography } from "antd";
 import { Layout, Typography } from "antd";
 import CaseFormula from "../template/Wbw/CaseFormula";
 import CaseFormula from "../template/Wbw/CaseFormula";
 import EditableLabel from "../general/EditableLabel";
 import EditableLabel from "../general/EditableLabel";
+import Tree from "./test/Tree";
 const { Paragraph } = Typography;
 const { Paragraph } = Typography;
 
 
 const Widget = () => {
 const Widget = () => {
@@ -20,7 +21,7 @@ const Widget = () => {
       </Paragraph>
       </Paragraph>
       <CaseFormula />
       <CaseFormula />
       <h2>TreeTest</h2>
       <h2>TreeTest</h2>
-      <TreeTest />
+      <Tree />
 
 
       <br />
       <br />
       <FontBox />
       <FontBox />

+ 1 - 1
dashboard/src/components/nut/TreeTest.tsx

@@ -31,7 +31,7 @@ const Widget = () => {
   };
   };
 
 
   return (
   return (
-    <TocTree onSelect={onSelect} treeData={treeData} expandedKey={["0-3"]} />
+    <TocTree onSelect={onSelect} treeData={treeData} expandedKeys={["0-3"]} />
   );
   );
 };
 };
 
 

+ 59 - 0
dashboard/src/components/nut/test/Tree.tsx

@@ -0,0 +1,59 @@
+import { title1 } from "@uiw/react-md-editor";
+import { Button, Tree } from "antd";
+import { Key } from "antd/lib/table/interface";
+import { useState } from "react";
+
+interface DataNode {
+  title: string;
+  key: string;
+  children?: DataNode[];
+}
+
+const Widget = () => {
+  const [selectedKeys, setSelectedKeys] = useState<Key[]>();
+  const [expandedKeys, setExpandedKeys] = useState<Key[]>();
+  const [treeData, setTreeData] = useState<DataNode[]>([
+    {
+      title: "title1",
+      key: "title1",
+      children: [
+        { title: "title1-1", key: "title1-1" },
+        { title: "title1-2", key: "title1-2" },
+      ],
+    },
+    { title: "title2", key: "title2" },
+  ]);
+  return (
+    <>
+      <Button
+        onClick={() => {
+          setTreeData((origin) => {
+            return [...origin, { title: "title3", key: "title3" }];
+          });
+        }}
+      >
+        add
+      </Button>
+      <Button
+        onClick={() => {
+          setExpandedKeys(["title1"]);
+          setSelectedKeys(["title1-2"]);
+        }}
+      >
+        expand
+      </Button>
+      <Tree
+        treeData={treeData}
+        expandedKeys={expandedKeys}
+        selectedKeys={selectedKeys}
+        onExpand={(expandedKeys: Key[]) => {
+          console.log("expandedKeys", expandedKeys);
+          setSelectedKeys(expandedKeys);
+        }}
+        onSelect={(selectedKeys: Key[]) => setSelectedKeys(selectedKeys)}
+      />
+    </>
+  );
+};
+
+export default Widget;

+ 3 - 0
dashboard/src/components/template/MdTpl.tsx

@@ -1,3 +1,4 @@
+import GrammarPop, { GrammarPopShell } from "../dict/GrammarPop";
 import Article from "./Article";
 import Article from "./Article";
 import Exercise from "./Exercise";
 import Exercise from "./Exercise";
 import Mermaid from "./Mermaid";
 import Mermaid from "./Mermaid";
@@ -45,6 +46,8 @@ const Widget = ({ tpl, props, children }: IWidgetMdTpl) => {
       return <ParaHandle props={props ? props : ""} />;
       return <ParaHandle props={props ? props : ""} />;
     case "mermaid":
     case "mermaid":
       return <Mermaid props={props ? props : ""} />;
       return <Mermaid props={props ? props : ""} />;
+    case "grammar-pop":
+      return <GrammarPopShell props={props ? props : ""} />;
     default:
     default:
       return <>未定义模版({tpl})</>;
       return <>未定义模版({tpl})</>;
   }
   }

+ 11 - 20
dashboard/src/components/template/utilities.ts

@@ -2,7 +2,6 @@ import React from "react";
 
 
 import MdTpl from "./MdTpl";
 import MdTpl from "./MdTpl";
 import { WdCtl } from "./Wd";
 import { WdCtl } from "./Wd";
-import { Divider } from "antd";
 import { roman_to_my, my_to_roman } from "../code/my";
 import { roman_to_my, my_to_roman } from "../code/my";
 import { roman_to_si } from "../code/si";
 import { roman_to_si } from "../code/si";
 import { roman_to_thai } from "../code/thai";
 import { roman_to_thai } from "../code/thai";
@@ -23,25 +22,26 @@ export function XmlToReact(
   convertor?: TCodeConvertor
   convertor?: TCodeConvertor
 ): React.ReactNode[] | undefined {
 ): React.ReactNode[] | undefined {
   //console.log("html string:", text);
   //console.log("html string:", text);
-  text = text.replaceAll("<br>", "<div></div>");
   const parser = new DOMParser();
   const parser = new DOMParser();
-  const xmlDoc = parser.parseFromString(
-    `<body>${text}</body>`,
-    "application/xml"
-  );
+  const xmlDoc = parser.parseFromString(`<body>${text}</body>`, "text/html");
   const x = xmlDoc.documentElement;
   const x = xmlDoc.documentElement;
   //console.log("解析成功", x);
   //console.log("解析成功", x);
-  return convert(x, wordWidget, convertor);
+  return convert(x.getElementsByTagName("body")[0], wordWidget, convertor);
 
 
   function getAttr(node: ChildNode, key: number): Object {
   function getAttr(node: ChildNode, key: number): Object {
     const ele = node as Element;
     const ele = node as Element;
     const attr = ele.attributes;
     const attr = ele.attributes;
+    //console.log("attr", attr);
     let output: any = { key: key };
     let output: any = { key: key };
     for (let i = 0; i < attr.length; i++) {
     for (let i = 0; i < attr.length; i++) {
       if (attr[i].nodeType === 2) {
       if (attr[i].nodeType === 2) {
         let key: string = attr[i].nodeName;
         let key: string = attr[i].nodeName;
         if (key !== "style") {
         if (key !== "style") {
-          output[key] = attr[i].nodeValue;
+          if (key === "class") {
+            output["className"] = attr[i].nodeValue;
+          } else {
+            output[key] = attr[i].nodeValue;
+          }
         } else {
         } else {
           //TODO 把css style 转换为react style
           //TODO 把css style 转换为react style
         }
         }
@@ -62,8 +62,8 @@ export function XmlToReact(
 
 
       switch (value.nodeType) {
       switch (value.nodeType) {
         case 1: //element node
         case 1: //element node
-          //console.log("tag name", value.nodeName);
-          const tagName = value.nodeName;
+          const tagName = value.nodeName.toLowerCase();
+          //console.log("tag", value.nodeName, tagName);
           switch (tagName) {
           switch (tagName) {
             case "parsererror":
             case "parsererror":
               output.push(
               output.push(
@@ -74,7 +74,7 @@ export function XmlToReact(
                 )
                 )
               );
               );
               break;
               break;
-            case "MdTpl":
+            case "mdtpl":
               output.push(
               output.push(
                 React.createElement(
                 React.createElement(
                   MdTpl,
                   MdTpl,
@@ -83,15 +83,6 @@ export function XmlToReact(
                 )
                 )
               );
               );
               break;
               break;
-            case "hr":
-              output.push(
-                React.createElement(
-                  Divider,
-                  getAttr(value, i),
-                  convert(value, wordWidget, convertor)
-                )
-              );
-              break;
             case "param":
             case "param":
               output.push(
               output.push(
                 React.createElement(
                 React.createElement(

+ 231 - 0
dashboard/src/components/term/TermCommunity.tsx

@@ -0,0 +1,231 @@
+import {
+  Badge,
+  Card,
+  Dropdown,
+  MenuProps,
+  Popover,
+  Space,
+  Typography,
+} from "antd";
+import { DownOutlined } from "@ant-design/icons";
+import { useState, useEffect } from "react";
+import { useIntl } from "react-intl";
+import { get } from "../../request";
+import { IUser } from "../auth/User";
+import { ITermListResponse } from "../api/Term";
+
+const { Title, Link, Text } = Typography;
+
+interface IItem<R> {
+  value: R;
+  score: number;
+}
+interface IWord {
+  meaning: IItem<string>[];
+  note: IItem<string>[];
+  editor: IItem<IUser>[];
+}
+
+interface IWidget {
+  word: string | undefined;
+}
+const TermCommunityWidget = ({ word }: IWidget) => {
+  const intl = useIntl();
+  const [show, setShow] = useState(false);
+  const [wordData, setWordData] = useState<IWord>();
+  const minScore = 100; //分数阈值。低于这个分数只显示在弹出菜单中
+
+  useEffect(() => {
+    if (typeof word === "undefined") {
+      return;
+    }
+    const url = `/v2/terms?view=word&word=${word}&exp=1`;
+    console.log("url", url);
+    get<ITermListResponse>(url)
+      .then((json) => {
+        if (json.ok === false) {
+          console.log("dict community", json.message);
+          return;
+        }
+        console.log("count", json.data);
+        let meaning = new Map<string, number>();
+        let note = new Map<string, number>();
+        let editorId = new Map<string, number>();
+        let editor = new Map<string, IUser>();
+        for (const it of json.data.rows) {
+          let score: number | undefined;
+          let currScore = 100;
+          if (it.exp) {
+            //分数计算
+            currScore = Math.floor(it.exp / 3600);
+          }
+          if (it.meaning) {
+            score = meaning.get(it.meaning);
+            meaning.set(it.meaning, score ? score + currScore : currScore);
+          }
+
+          if (it.note) {
+            score = note.get(it.note);
+            const noteScore = it.note.length;
+            note.set(it.note, score ? score + noteScore : noteScore);
+          }
+
+          if (it.editor) {
+            score = editorId.get(it.editor.id);
+            editorId.set(it.editor.id, score ? score + currScore : currScore);
+            editor.set(it.editor.id, it.editor);
+          }
+        }
+        let _data: IWord = {
+          meaning: [],
+          note: [],
+          editor: [],
+        };
+        meaning.forEach((value, key, map) => {
+          if (key && key.length > 0) {
+            _data.meaning.push({ value: key, score: value });
+          }
+        });
+        _data.meaning.sort((a, b) => b.score - a.score);
+        note.forEach((value, key, map) => {
+          if (key && key.length > 0) {
+            _data.note.push({ value: key, score: value });
+          }
+        });
+        _data.note.sort((a, b) => b.score - a.score);
+
+        editorId.forEach((value, key, map) => {
+          const currEditor = editor.get(key);
+          if (currEditor) {
+            _data.editor.push({ value: currEditor, score: value });
+          }
+        });
+        _data.editor.sort((a, b) => b.score - a.score);
+        setWordData(_data);
+        console.log("_data", _data);
+        if (_data.editor.length > 0) {
+          console.log("show", _data.editor.length);
+          setShow(true);
+        }
+      })
+      .catch((error) => {
+        console.error(error);
+      });
+  }, [word, setWordData]);
+
+  const isShow = (score: number, index: number) => {
+    const Ms = 500,
+      Rd = 5,
+      minScore = 15;
+    const minOrder = Math.log(score) / Math.log(Math.pow(Ms, 1 / Rd));
+    if (index < minOrder && score > minScore) {
+      return true;
+    } else {
+      return false;
+    }
+  };
+
+  const meaningLow = wordData?.meaning.filter(
+    (value, index: number) => !isShow(value.score, index)
+  );
+  const meaningExtra = meaningLow?.map((item, id) => {
+    return <span key={id}>{item.value}</span>;
+  });
+
+  const mainCollaboratorNum = 3; //默认显示的协作者数量,其余的在更多中显示
+  const collaboratorRender = (name: string, id: number, score: number) => {
+    return (
+      <Space key={id}>
+        {name}
+        <Badge color="geekblue" size="small" count={score} />
+      </Space>
+    );
+  };
+  const items: MenuProps["items"] = wordData?.editor
+    .filter((value, index) => index >= mainCollaboratorNum)
+    .map((item, id) => {
+      return {
+        key: id,
+        label: collaboratorRender(item.value.nickName, id, item.score),
+      };
+    });
+  const more = wordData ? (
+    wordData.editor.length > mainCollaboratorNum ? (
+      <Dropdown menu={{ items }}>
+        <Link>
+          <Space>
+            {intl.formatMessage({
+              id: `buttons.more`,
+            })}
+            <DownOutlined />
+          </Space>
+        </Link>
+      </Dropdown>
+    ) : undefined
+  ) : undefined;
+
+  return show ? (
+    <Card>
+      <Title level={5} id={`community`}>
+        {"社区术语"}
+      </Title>
+      <div key="meaning">
+        <Space style={{ flexWrap: "wrap" }}>
+          <Text strong>{"意思:"}</Text>
+          {wordData?.meaning
+            .filter((value, index: number) => isShow(value.score, index))
+            .map((item, id) => {
+              return (
+                <Space key={id}>
+                  {item.value}
+                  <Badge color="geekblue" size="small" count={item.score} />
+                </Space>
+              );
+            })}
+          {meaningLow && meaningLow.length > 0 ? (
+            <Popover content={<Space>{meaningExtra}</Space>} placement="bottom">
+              <Link>
+                <Space>
+                  {intl.formatMessage({
+                    id: `buttons.more`,
+                  })}
+                  <DownOutlined />
+                </Space>
+              </Link>
+            </Popover>
+          ) : undefined}
+        </Space>
+      </div>
+      <div key="note">
+        <Space style={{ flexWrap: "wrap" }}>
+          <Text strong>{"note:"}</Text>
+          {wordData?.note
+            .filter((value) => value.score >= minScore)
+            .map((item, id) => {
+              return (
+                <Space key={id}>
+                  {item.value}
+                  <Badge color="geekblue" size="small" count={item.score} />
+                </Space>
+              );
+            })}
+        </Space>
+      </div>
+      <div key="collaborator">
+        <Space style={{ flexWrap: "wrap" }}>
+          <Text strong>{"贡献者:"}</Text>
+          {wordData?.editor
+            .filter((value, index) => index < mainCollaboratorNum)
+            .map((item, id) => {
+              return collaboratorRender(item.value.nickName, id, item.score);
+            })}
+          {more}
+        </Space>
+      </div>
+    </Card>
+  ) : (
+    <></>
+  );
+};
+
+export default TermCommunityWidget;