Browse Source

Merge pull request #1274 from visuddhinanda/agile

wbw relation 支持 目标词过滤
visuddhinanda 2 years ago
parent
commit
f91a780aa7

+ 14 - 0
dashboard/src/Router.tsx

@@ -26,11 +26,14 @@ import AdminApi from "./pages/admin/api";
 import AdminApiDashboard from "./pages/admin/api/dashboard";
 
 import LibraryHome from "./pages/library";
+
 import LibraryCommunity from "./pages/library/community";
 import LibraryCommunityList from "./pages/library/community/list";
+
 import LibraryPalicanon from "./pages/library/palicanon";
 import LibraryPalicanonByPath from "./pages/library/palicanon/bypath";
 import LibraryPalicanonChapter from "./pages/library/palicanon/chapter";
+
 import LibraryCourse from "./pages/library/course";
 import LibraryCourseList from "./pages/library/course/list";
 import LibraryCourseShow from "./pages/library/course/course";
@@ -38,14 +41,20 @@ import LibraryCourseShow from "./pages/library/course/course";
 import LibraryTerm from "./pages/library/term";
 import LibraryTermShow from "./pages/library/term/show";
 import LibraryTermList from "./pages/library/term/list";
+
 import LibraryDict from "./pages/library/dict";
 import LibraryDictShow from "./pages/library/dict/show";
+
 import LibraryAnthology from "./pages/library/anthology";
 import LibraryAnthologyShow from "./pages/library/anthology/show";
 import LibraryAnthologyList from "./pages/library/anthology/list";
+
 import LibraryArticle from "./pages/library/article";
 import LibraryArticleShow from "./pages/library/article/show";
 
+import LibraryNissaya from "./pages/library/nissaya";
+import LibraryNissayaShow from "./pages/library/nissaya/show";
+
 import LibraryBlog from "./pages/library/blog";
 import LibraryBlogOverview from "./pages/library/blog/overview";
 import LibraryBlogTranslation from "./pages/library/blog/translation";
@@ -60,6 +69,7 @@ import LibraryDiscussionShow from "./pages/library/discussion/show";
 
 import LibrarySearch from "./pages/library/search";
 import LibrarySearchKey from "./pages/library/search/search";
+
 import LibraryDownload from "./pages/library/download";
 import LibraryDownloadPage from "./pages/library/download/Download";
 
@@ -211,6 +221,10 @@ const Widget = () => {
           />
         </Route>
 
+        <Route path="nissaya" element={<LibraryNissaya />}>
+          <Route path="ending/:ending" element={<LibraryNissayaShow />} />
+        </Route>
+
         <Route path="discussion" element={<LibraryDiscussion />}>
           <Route path="list" element={<LibraryDiscussionList />} />
           <Route path="topic/:id" element={<LibraryDiscussionTopic />} />

+ 2 - 2
dashboard/src/components/fts/FullTextSearchResult.tsx

@@ -137,8 +137,8 @@ const FullTxtSearchResultWidget = ({
             {"/"}
             <Tag>{item.paragraph}</Tag>
           </Space>
-          <div className="search_content">
-            <Marked text={item.content} />
+          <div>
+            <Marked className="search_content" text={item.content} />
           </div>
         </List.Item>
       )}

+ 1 - 1
dashboard/src/components/fts/search.css

@@ -1,4 +1,4 @@
-.search_content del {
+.search_content > p > del {
   background-color: yellow;
   color: black;
   text-decoration: none;

+ 4 - 2
dashboard/src/components/general/Marked.tsx

@@ -5,11 +5,13 @@ const { Text } = Typography;
 
 interface IWidget {
   text?: string;
+  className?: string;
 }
-const MarkedWidget = ({ text }: IWidget) => {
+const MarkedWidget = ({ text, className }: IWidget) => {
   return (
-    <Text>
+    <Text className={className}>
       <div
+        className={className}
         dangerouslySetInnerHTML={{
           __html: marked.parse(text ? text : ""),
         }}

+ 2 - 2
dashboard/src/components/template/Wbw/RelaGraphic.tsx

@@ -1,7 +1,7 @@
 import Mermaid from "../../general/Mermaid";
 import { useAppSelector } from "../../../hooks";
 import { getTerm } from "../../../reducers/term-vocabulary";
-import { IRelation } from "./WbwDetailRelation";
+import { IWbwRelation } from "./WbwDetailRelation";
 import { IWbw } from "./WbwWord";
 
 interface IWidget {
@@ -18,7 +18,7 @@ const RelaGraphicWidget = ({ wbwData }: IWidget) => {
       ?.filter((value) => value.relation)
       .map((item) => {
         if (item.relation && item.relation.value) {
-          const json: IRelation[] = JSON.parse(item.relation.value);
+          const json: IWbwRelation[] = JSON.parse(item.relation.value);
           const graphic = json.map((relation) => {
             const localName = terms?.find(
               (item) => item.word === relation.relation

+ 144 - 91
dashboard/src/components/template/Wbw/WbwDetailRelation.tsx

@@ -1,6 +1,10 @@
 import { Button, List, Select, Space } from "antd";
 import { useEffect, useState } from "react";
-import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
+import {
+  DeleteOutlined,
+  PlusOutlined,
+  InfoCircleOutlined,
+} from "@ant-design/icons";
 
 import { useAppSelector } from "../../../hooks";
 import { getRelation } from "../../../reducers/relation";
@@ -9,18 +13,20 @@ import { IWbw } from "./WbwWord";
 import { useIntl } from "react-intl";
 import store from "../../../store";
 import { add, relationAddParam } from "../../../reducers/relation-add";
+import { IRelation } from "../../../pages/admin/relation/list";
 
 interface IOptions {
   value: string;
   label: JSX.Element;
 }
 
-export interface IRelation {
+export interface IWbwRelation {
   sour_id: string;
   sour_spell: string;
   dest_id: string;
   dest_spell: string;
   relation?: string;
+  is_new?: Boolean;
 }
 interface IWidget {
   data: IWbw;
@@ -28,26 +34,52 @@ interface IWidget {
   onAdd?: Function;
 }
 const WbwDetailRelationWidget = ({ data, onChange, onAdd }: IWidget) => {
+  const getSourId = () => `${data.book}-${data.para}-` + data.sn.join("-");
+
   const intl = useIntl();
-  const [relation, setRelation] = useState<IRelation[]>([]);
+  const [relation, setRelation] = useState<IWbwRelation[]>([]);
+  const [currRelation, setCurrRelation] = useState<IRelation[]>();
+  const [relationOptions, setRelationOptions] = useState<IRelation[]>();
+  const [newRelationName, setNewRelationName] = useState<string>();
+
   const [options, setOptions] = useState<IOptions[]>();
   const terms = useAppSelector(getTerm);
   const relations = useAppSelector(getRelation);
 
   const addParam = useAppSelector(relationAddParam);
+
+  const newRelationRow: IWbwRelation = {
+    sour_id: getSourId(),
+    sour_spell: data.word.value,
+    dest_id: "",
+    dest_spell: "",
+    relation: undefined,
+    is_new: true,
+  };
   useEffect(() => {
     if (
       addParam?.command === "apply" &&
       addParam.src_sn === data.sn.join("-") &&
       addParam.target_spell
     ) {
-      const newRelation: IRelation = {
-        sour_id: `${data.book}-${data.para}-` + data.sn.join("-"),
+      const newRelation: IWbwRelation = {
+        sour_id: getSourId(),
         sour_spell: data.word.value,
         dest_id: addParam.target_id ? addParam.target_id : "",
         dest_spell: addParam.target_spell,
+        relation: newRelationName,
       };
-      setRelation([...relation, newRelation]);
+      setRelation((origin) => {
+        origin.push(newRelation);
+        if (typeof onChange !== "undefined") {
+          onChange({
+            field: "relation",
+            value: JSON.stringify(origin),
+          });
+        }
+        return origin;
+      });
+      setNewRelationName(undefined);
     }
   }, [addParam?.command]);
 
@@ -55,7 +87,7 @@ const WbwDetailRelationWidget = ({ data, onChange, onAdd }: IWidget) => {
     if (typeof data.relation === "undefined") {
       return;
     }
-    const arrRelation: IRelation[] = JSON.parse(
+    const arrRelation: IWbwRelation[] = JSON.parse(
       data.relation?.value ? data.relation?.value : "[]"
     );
     setRelation(arrRelation);
@@ -77,47 +109,52 @@ const WbwDetailRelationWidget = ({ data, onChange, onAdd }: IWidget) => {
     if (typeof grammar === "undefined") {
       return;
     }
-    const mRelation = relations
-      ?.filter((value) => {
-        let caseMatch = true;
-        let spellMatch = true;
-        if (!value.from) {
-          return false;
-        }
-        if (value.from?.case) {
-          let matchCount = 0;
-          if (grammar) {
-            for (const iterator of value.from.case) {
-              if (grammar?.includes(iterator)) {
-                matchCount++;
-              }
+
+    //找出符合条件的relation
+    const filteredRelation = relations?.filter((value) => {
+      let caseMatch = true;
+      let spellMatch = true;
+      if (!value.from) {
+        return false;
+      }
+      if (value.from?.case) {
+        let matchCount = 0;
+        if (grammar) {
+          for (const iterator of value.from.case) {
+            if (grammar?.includes(iterator)) {
+              matchCount++;
             }
           }
-          if (matchCount !== value.from.case.length) {
-            caseMatch = false;
-          }
         }
-        if (value.from?.spell) {
-          if (data.real.value !== value.from?.spell) {
-            spellMatch = false;
-          }
+        if (matchCount !== value.from.case.length) {
+          caseMatch = false;
         }
-        return caseMatch && spellMatch;
-      })
-      .map((item) => {
-        const localName = terms?.find(
-          (term) => term.word === item.name
-        )?.meaning;
-        return {
-          value: item.name,
-          label: (
-            <Space>
-              {item.name}
-              {localName}
-            </Space>
-          ),
-        };
-      });
+      }
+      if (value.from?.spell) {
+        if (data.real.value !== value.from?.spell) {
+          spellMatch = false;
+        }
+      }
+      return caseMatch && spellMatch;
+    });
+    setCurrRelation(filteredRelation);
+    setRelationOptions(filteredRelation);
+    let relationName = new Map<string, string>();
+    filteredRelation?.forEach((value) => {
+      relationName.set(value.name, value.name);
+    });
+    const mRelation = Array.from(relationName.keys()).map((item) => {
+      const localName = terms?.find((term) => term.word === item)?.meaning;
+      return {
+        value: item,
+        label: (
+          <Space>
+            {item}
+            {localName}
+          </Space>
+        ),
+      };
+    });
     setOptions(mRelation);
   }, [
     data.case?.value,
@@ -126,68 +163,84 @@ const WbwDetailRelationWidget = ({ data, onChange, onAdd }: IWidget) => {
     relations,
     terms,
   ]);
+  const addButton = (
+    <Button
+      type="dashed"
+      icon={<PlusOutlined />}
+      onClick={() => {
+        if (typeof onAdd !== "undefined") {
+          onAdd();
+        }
+        store.dispatch(
+          add({
+            book: data.book,
+            para: data.para,
+            src_sn: data.sn.join("-"),
+            command: "add",
+            relations: currRelation,
+          })
+        );
+      }}
+    >
+      {intl.formatMessage({ id: "buttons.relate.to" })}
+    </Button>
+  );
   return (
     <List
       itemLayout="vertical"
       size="small"
-      header={
-        <Button
-          type="dashed"
-          icon={<PlusOutlined />}
-          onClick={() => {
-            if (typeof onAdd !== "undefined") {
-              onAdd();
-            }
-            store.dispatch(
-              add({
-                book: data.book,
-                para: data.para,
-                src_sn: data.sn.join("-"),
-                command: "add",
-              })
-            );
-          }}
-        >
-          {intl.formatMessage({ id: "buttons.add" })}
-        </Button>
-      }
-      dataSource={relation}
+      dataSource={[...relation, newRelationRow]}
       renderItem={(item, index) => (
         <List.Item>
           <Space>
-            <Button
-              type="text"
-              icon={<DeleteOutlined />}
-              onClick={() => {
-                let arrRelation: IRelation[] = [...relation];
-                arrRelation.splice(index, 1);
-                setRelation(arrRelation);
-                if (typeof onChange !== "undefined") {
-                  onChange({
-                    field: "relation",
-                    value: JSON.stringify(arrRelation),
-                  });
-                }
-              }}
-            />
-            {item.dest_spell}
+            {item.is_new ? undefined : (
+              <Button
+                type="text"
+                icon={<DeleteOutlined />}
+                onClick={() => {
+                  let arrRelation: IWbwRelation[] = [...relation];
+                  arrRelation.splice(index, 1);
+                  setRelation(arrRelation);
+                  if (typeof onChange !== "undefined") {
+                    onChange({
+                      field: "relation",
+                      value: JSON.stringify(arrRelation),
+                    });
+                  }
+                }}
+              />
+            )}
+
             <Select
               defaultValue={item.relation}
+              placeholder={"请选择关系"}
+              clearIcon={true}
               style={{ width: 180 }}
               onChange={(value: string) => {
-                console.log(`selected ${value}`);
-                let arrRelation: IRelation[] = [...relation];
-                arrRelation[index].relation = value;
-                setRelation(arrRelation);
-                if (typeof onChange !== "undefined") {
-                  onChange({
-                    field: "relation",
-                    value: JSON.stringify(arrRelation),
-                  });
+                if (item.is_new) {
+                  setNewRelationName(value);
+                  return;
                 }
+                const currSelect = relationOptions?.filter(
+                  (rl) => rl.name === value
+                );
+                setCurrRelation(currSelect);
+                console.log(`selected ${value}`);
+                setRelation((origin) => {
+                  origin[index].relation = value;
+                  if (typeof onChange !== "undefined") {
+                    onChange({
+                      field: "relation",
+                      value: JSON.stringify(origin),
+                    });
+                  }
+                  return origin;
+                });
               }}
               options={options}
             />
+            <Button type="link" icon={<InfoCircleOutlined />} />
+            {item.dest_spell ? item.dest_spell : addButton}
           </Space>
         </List.Item>
       )}

+ 82 - 22
dashboard/src/components/template/Wbw/WbwPali.tsx

@@ -18,38 +18,96 @@ import store from "../../../store";
 import { lookup } from "../../../reducers/command";
 import { useAppSelector } from "../../../hooks";
 import { add, relationAddParam } from "../../../reducers/relation-add";
+import { ArticleMode } from "../../article/Article";
 
 const { Paragraph } = Typography;
 interface IWidget {
   data: IWbw;
   display?: TWbwDisplayMode;
+  mode?: ArticleMode;
   onSave?: Function;
 }
-const WbwPaliWidget = ({ data, display, onSave }: IWidget) => {
+const WbwPaliWidget = ({ data, mode, display, onSave }: IWidget) => {
   const [popOpen, setPopOpen] = useState(false);
   const [paliColor, setPaliColor] = useState("unset");
   const [hasComment, setHasComment] = useState(data.hasComment);
   /**
    * 处理 relation 链接事件
-   * 点击连接或取消后,打开弹窗
+   * 高亮可能的单词
    */
   const addParam = useAppSelector(relationAddParam);
   useEffect(() => {
-    if (
-      (addParam?.command === "apply" || addParam?.command === "cancel") &&
-      addParam.src_sn === data.sn.join("-") &&
-      addParam.book === data.book &&
-      addParam.para === data.para
-    ) {
-      setPopOpen(true);
-      store.dispatch(
-        add({
-          book: data.book,
-          para: data.para,
-          src_sn: data.sn.join("-"),
-          command: "finish",
-        })
-      );
+    let grammar = data.case?.value
+      ?.replace("#", "$")
+      .replaceAll(".", "")
+      .split("$");
+    if (data.grammar2?.value) {
+      if (grammar) {
+        grammar = [data.grammar2?.value, ...grammar];
+      } else {
+        grammar = [data.grammar2?.value];
+      }
+    }
+    console.log("grammar", grammar);
+    if (typeof grammar === "undefined") {
+      return;
+    }
+    const match = addParam?.relations?.filter((value) => {
+      let caseMatch = true;
+      let spellMatch = true;
+      if (!value.to) {
+        return false;
+      }
+      if (value.to?.case) {
+        let matchCount = 0;
+        if (grammar) {
+          for (const iterator of value.to.case) {
+            if (grammar?.includes(iterator)) {
+              matchCount++;
+            }
+          }
+        }
+        if (matchCount !== value.to.case.length) {
+          caseMatch = false;
+        }
+      }
+      if (value.from?.spell) {
+        if (data.real.value !== value.from?.spell) {
+          spellMatch = false;
+        }
+      }
+      return caseMatch && spellMatch;
+    });
+    if (match && match.length > 0) {
+      setPaliColor("greenyellow");
+    }
+  }, [
+    addParam?.relations,
+    data.case?.value,
+    data.grammar2?.value,
+    data.real.value,
+  ]);
+  /**
+   * 点击连接或取消后,打开弹窗
+   */
+  useEffect(() => {
+    if (addParam?.command === "apply" || addParam?.command === "cancel") {
+      setPaliColor("unset");
+      if (
+        addParam.src_sn === data.sn.join("-") &&
+        addParam.book === data.book &&
+        addParam.para === data.para
+      ) {
+        setPopOpen(true);
+        store.dispatch(
+          add({
+            book: data.book,
+            para: data.para,
+            src_sn: data.sn.join("-"),
+            command: "finish",
+          })
+        );
+      }
     }
   }, [
     addParam?.book,
@@ -62,12 +120,14 @@ const WbwPaliWidget = ({ data, display, onSave }: IWidget) => {
   ]);
 
   const handleClickChange = (open: boolean) => {
-    if (open) {
-      setPaliColor("lightblue");
-    } else {
-      setPaliColor("unset");
+    if (mode === "wbw") {
+      if (open) {
+        setPaliColor("lightblue");
+      } else {
+        setPaliColor("unset");
+      }
+      setPopOpen(open);
     }
-    setPopOpen(open);
   };
 
   const wbwDetail = (

+ 1 - 0
dashboard/src/components/template/Wbw/WbwWord.tsx

@@ -237,6 +237,7 @@ const WbwWordWidget = ({
         <WbwPali
           key="pali"
           data={wordData}
+          mode={mode}
           display={display}
           onSave={(e: IWbw, isPublish: boolean) => {
             const newData: IWbw = JSON.parse(JSON.stringify(e));

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

@@ -52,6 +52,7 @@ const items = {
   "buttons.download.link": "下载链接",
   "buttons.lookup": "查字典",
   "buttons.invite": "邀请",
+  "buttons.relate.to": "关联到",
 };
 
 export default items;

+ 15 - 0
dashboard/src/pages/library/nissaya/index.tsx

@@ -0,0 +1,15 @@
+import { Outlet } from "react-router-dom";
+import { Layout } from "antd";
+
+import FooterBar from "../../../components/library/FooterBar";
+
+const Widget = () => {
+  return (
+    <Layout>
+      <Outlet />
+      <FooterBar />
+    </Layout>
+  );
+};
+
+export default Widget;

+ 18 - 0
dashboard/src/pages/library/nissaya/show.tsx

@@ -0,0 +1,18 @@
+import { Col, Row } from "antd";
+import { useParams } from "react-router-dom";
+import NissayaCardWidget from "../../../components/general/NissayaCard";
+
+const Widget = () => {
+  const { ending } = useParams(); //url 参数
+  return (
+    <Row>
+      <Col flex={"auto"}></Col>
+      <Col flex={"960px"}>
+        <NissayaCardWidget text={ending} cache={false} />
+      </Col>
+      <Col flex={"auto"}></Col>
+    </Row>
+  );
+};
+
+export default Widget;

+ 2 - 0
dashboard/src/reducers/relation-add.ts

@@ -2,6 +2,7 @@
  * 查字典,添加术语命令
  */
 import { createSlice, PayloadAction } from "@reduxjs/toolkit";
+import { IRelation } from "../pages/admin/relation/list";
 
 import type { RootState } from "../store";
 
@@ -11,6 +12,7 @@ export interface IRelationParam {
   src_sn?: string;
   target_id?: string;
   target_spell?: string;
+  relations?: IRelation[];
   command: "add" | "apply" | "cancel" | "finish";
 }
 interface IState {