Procházet zdrojové kódy

Merge pull request #1040 from visuddhinanda/agile

:sparkles: 鼠标移入单词块,查词,并渲染下拉菜单
visuddhinanda před 3 roky
rodič
revize
7fe763be5c

+ 67 - 71
dashboard/src/components/nut/users/SignIn.tsx

@@ -10,86 +10,82 @@ import { get, post } from "../../../request";
 import store from "../../../store";
 
 interface IFormData {
-	email: string;
-	password: string;
+  email: string;
+  password: string;
 }
 interface ISignInResponse {
-	ok: boolean;
-	message: string;
-	data: string;
+  ok: boolean;
+  message: string;
+  data: string;
 }
 interface IUserResponse {
-	ok: boolean;
-	message: string;
-	data: IUser;
+  ok: boolean;
+  message: string;
+  data: IUser;
 }
 interface ISignInRequest {
-	username: string;
-	password: string;
+  username: string;
+  password: string;
 }
 const Widget = () => {
-	const intl = useIntl();
-	const dispatch = useAppDispatch();
-	const navigate = useNavigate();
+  const intl = useIntl();
+  const dispatch = useAppDispatch();
+  const navigate = useNavigate();
 
-	return (
-		<ProForm<IFormData>
-			onFinish={async (values: IFormData) => {
-				// TODO
-				console.log(values);
-				const user = {
-					username: values.email,
-					password: values.password,
-				};
-				const signin = await post<ISignInRequest, ISignInResponse>(
-					"/v2/auth/signin",
-					user
-				);
-				if (signin.ok) {
-					console.log("token", signin.data);
-					localStorage.setItem("token", signin.data);
-					get<IUserResponse>("/v2/auth/current").then((json) => {
-						if (json.ok) {
-							dispatch(signIn([json.data, signin.data]));
-							navigate(TO_HOME);
-						} else {
-							console.error(json.message);
-						}
-					});
-					message.success(
-						intl.formatMessage({ id: "flashes.success" })
-					);
-				} else {
-					message.error(signin.message);
-				}
-			}}
-		>
-			<ProForm.Group>
-				<ProFormText
-					width="md"
-					name="email"
-					required
-					label={intl.formatMessage({
-						id: "forms.fields.email.label",
-					})}
-					rules={[
-						{ required: true, type: "email", max: 255, min: 6 },
-					]}
-				/>
-			</ProForm.Group>
-			<ProForm.Group>
-				<ProFormText
-					width="md"
-					name="password"
-					required
-					label={intl.formatMessage({
-						id: "forms.fields.password.label",
-					})}
-					rules={[{ required: true, max: 32, min: 4 }]}
-				/>
-			</ProForm.Group>
-		</ProForm>
-	);
+  return (
+    <ProForm<IFormData>
+      onFinish={async (values: IFormData) => {
+        // TODO
+        console.log(values);
+        const user = {
+          username: values.email,
+          password: values.password,
+        };
+        const signin = await post<ISignInRequest, ISignInResponse>(
+          "/v2/auth/signin",
+          user
+        );
+        if (signin.ok) {
+          console.log("token", signin.data);
+          localStorage.setItem("token", signin.data);
+          get<IUserResponse>("/v2/auth/current").then((json) => {
+            if (json.ok) {
+              dispatch(signIn([json.data, signin.data]));
+              navigate(TO_HOME);
+            } else {
+              console.error(json.message);
+            }
+          });
+          message.success(intl.formatMessage({ id: "flashes.success" }));
+        } else {
+          message.error(signin.message);
+        }
+      }}
+    >
+      <ProForm.Group>
+        <ProFormText
+          width="md"
+          name="email"
+          required
+          label={intl.formatMessage({
+            id: "forms.fields.email.label",
+          })}
+          rules={[{ required: true, max: 255, min: 6 }]}
+        />
+      </ProForm.Group>
+      <ProForm.Group>
+        <ProFormText
+          width="md"
+          name="password"
+          required
+          label={intl.formatMessage({
+            id: "forms.fields.password.label",
+          })}
+          rules={[{ required: true, max: 32, min: 4 }]}
+        />
+      </ProForm.Group>
+    </ProForm>
+  );
 };
 
 export default Widget;

+ 42 - 17
dashboard/src/components/template/Wbw/WbwFactorMeaning.tsx

@@ -1,26 +1,15 @@
 import { useIntl } from "react-intl";
+import { useState, useEffect } from "react";
 import type { MenuProps } from "antd";
-import { Dropdown } from "antd";
-import { Typography } from "antd";
+import { Dropdown, Space, Typography } from "antd";
+import { LoadingOutlined } from "@ant-design/icons";
 
 import { IWbw, TWbwDisplayMode } from "./WbwWord";
 import { PaliReal } from "../../../utils";
-const { Text } = Typography;
+import { useAppSelector } from "../../../hooks";
+import { inlineDict as _inlineDict } from "../../../reducers/inline-dict";
 
-const items: MenuProps["items"] = [
-  {
-    key: "factor1+意思",
-    label: "factor1+意思",
-  },
-  {
-    key: "factor2+意思",
-    label: "factor2+意思",
-  },
-  {
-    key: "factor3+意思",
-    label: "factor3+意思",
-  },
-];
+const { Text } = Typography;
 
 interface IWidget {
   data: IWbw;
@@ -29,6 +18,42 @@ interface IWidget {
 }
 const Widget = ({ data, display, onChange }: IWidget) => {
   const intl = useIntl();
+  const defaultMenu: MenuProps["items"] = [
+    {
+      key: "loading",
+      label: (
+        <Space>
+          <LoadingOutlined />
+          {"Loading"}
+        </Space>
+      ),
+    },
+  ];
+  const [items, setItems] = useState<MenuProps["items"]>(defaultMenu);
+
+  const inlineDict = useAppSelector(_inlineDict);
+  useEffect(() => {
+    if (inlineDict.wordIndex.includes(data.word.value)) {
+      const result = inlineDict.wordList.filter(
+        (word) => word.word === data.word.value
+      );
+      //查重
+      //TODO 加入信心指数并排序
+      let myMap = new Map<string, number>();
+      let factors: string[] = [];
+      for (const iterator of result) {
+        myMap.set(iterator.factormean, 1);
+      }
+      myMap.forEach((value, key, map) => {
+        factors.push(key);
+      });
+
+      const menu = factors.map((item) => {
+        return { key: item, label: item };
+      });
+      setItems(menu);
+    }
+  }, [inlineDict]);
 
   const onClick: MenuProps["onClick"] = (e) => {
     console.log("click ", e);

+ 42 - 16
dashboard/src/components/template/Wbw/WbwFactors.tsx

@@ -1,26 +1,16 @@
+import { useState, useEffect } from "react";
 import { useIntl } from "react-intl";
 import type { MenuProps } from "antd";
-import { Dropdown } from "antd";
-import { Typography } from "antd";
+import { Dropdown, Space, Typography } from "antd";
+import { LoadingOutlined } from "@ant-design/icons";
 
 import { IWbw, TWbwDisplayMode } from "./WbwWord";
 import { PaliReal } from "../../../utils";
+import { useAppSelector } from "../../../hooks";
+import { inlineDict as _inlineDict } from "../../../reducers/inline-dict";
+
 const { Text } = Typography;
 
-const items: MenuProps["items"] = [
-  {
-    key: "factor1+word",
-    label: "factor1+word",
-  },
-  {
-    key: "factor2+word",
-    label: "factor2+word",
-  },
-  {
-    key: "factor3+word",
-    label: "factor3+word",
-  },
-];
 interface IWidget {
   data: IWbw;
   display?: TWbwDisplayMode;
@@ -29,6 +19,42 @@ interface IWidget {
 
 const Widget = ({ data, display, onChange }: IWidget) => {
   const intl = useIntl();
+  const defaultMenu: MenuProps["items"] = [
+    {
+      key: "loading",
+      label: (
+        <Space>
+          <LoadingOutlined />
+          {"Loading"}
+        </Space>
+      ),
+    },
+  ];
+  const [items, setItems] = useState<MenuProps["items"]>(defaultMenu);
+
+  const inlineDict = useAppSelector(_inlineDict);
+  useEffect(() => {
+    if (inlineDict.wordIndex.includes(data.word.value)) {
+      const result = inlineDict.wordList.filter(
+        (word) => word.word === data.word.value
+      );
+      //查重
+      //TODO 加入信心指数并排序
+      let myMap = new Map<string, number>();
+      let factors: string[] = [];
+      for (const iterator of result) {
+        myMap.set(iterator.factors, 1);
+      }
+      myMap.forEach((value, key, map) => {
+        factors.push(key);
+      });
+
+      const menu = factors.map((item) => {
+        return { key: item, label: item };
+      });
+      setItems(menu);
+    }
+  }, [inlineDict]);
 
   const onClick: MenuProps["onClick"] = (e) => {
     console.log("click ", e);

+ 6 - 3
dashboard/src/components/template/Wbw/WbwPali.tsx

@@ -1,5 +1,5 @@
 import { useState } from "react";
-import { Popover } from "antd";
+import { Popover, Typography } from "antd";
 import { TagTwoTone, InfoCircleOutlined } from "@ant-design/icons";
 
 import WbwDetail from "./WbwDetail";
@@ -7,7 +7,7 @@ import { IWbw } from "./WbwWord";
 import { bookMarkColor } from "./WbwDetailBookMark";
 import "./wbw.css";
 import { PaliReal } from "../../../utils";
-
+const { Paragraph } = Typography;
 interface IWidget {
   data: IWbw;
   onSave?: Function;
@@ -51,7 +51,10 @@ const Widget = ({ data, onSave }: IWidget) => {
     : "white";
 
   const bookMarkIcon = data.bookMarkText ? (
-    <Popover content={data.bookMarkText.value} placement="bottom">
+    <Popover
+      content={<Paragraph copyable>{data.bookMarkText.value}</Paragraph>}
+      placement="bottom"
+    >
       <TagTwoTone twoToneColor={color} />
     </Popover>
   ) : (

+ 8 - 5
dashboard/src/components/template/Wbw/WbwWord.tsx

@@ -9,9 +9,9 @@ import "./wbw.css";
 import WbwPara from "./WbwPara";
 import WbwPage from "./WbwPage";
 import { useAppSelector } from "../../../hooks";
-import { add, wordList } from "../../../reducers/inline-dict";
+import { add, wordList, wordIndex } from "../../../reducers/inline-dict";
 import { get } from "../../../request";
-import { IApiResponseDictList } from "../../api/Dict";
+import { IApiResponseDictList, IDictDataRequest } from "../../api/Dict";
 import store from "../../../store";
 
 export type TFieldName =
@@ -95,7 +95,8 @@ const Widget = ({
   const [wordData, setWordData] = useState(data);
   const [fieldDisplay, setFieldDisplay] = useState(fields);
   const intervalRef = useRef<number | null>(null); //防抖计时器句柄
-  const inlineWords = useAppSelector(wordList);
+  const inlineWordList = useAppSelector(wordList);
+  const inlineWordIndex = useAppSelector(wordIndex);
 
   useEffect(() => {
     setWordData(data);
@@ -131,14 +132,16 @@ const Widget = ({
   const lookup = (word: string) => {
     stopLookup();
     //查询这个词在内存字典里是否有
-    if (inlineWords.has(word)) {
+    if (inlineWordIndex.includes(word)) {
       //已经有了,退出
       return;
     }
     get<IApiResponseDictList>(`/v2/wbwlookup?word=${word}`).then((json) => {
       console.log("lookup ok", json.data.count);
-      store.dispatch(add([word, json.data.rows]));
+      //扫描结果将结果按照词头分开
+      store.dispatch(add(json.data.rows));
     });
+
     console.log("lookup", word);
   };
   if (wordData.type?.value === ".ctl.") {

+ 2 - 0
dashboard/src/locales/zh-Hans/dict/index.ts

@@ -132,6 +132,8 @@ const items = {
   "dict.fields.type.un.short.label": "连音",
   "dict.fields.type.none.label": "无",
   "dict.fields.type.none.short.label": "",
+  "dict.fields.type.?.label": "?",
+  "dict.fields.type.?.short.label": "?",
 };
 
 export default items;

+ 25 - 9
dashboard/src/reducers/inline-dict.ts

@@ -9,21 +9,36 @@ import { IDictDataRequest } from "../components/api/Dict";
  * value: 查询到的单词列表
  */
 interface IState {
-  wordMap: Map<string, IDictDataRequest[]>;
-  word?: string;
-  value?: IDictDataRequest[];
+  wordList: IDictDataRequest[];
+  wordIndex: string[];
 }
 
 const initialState: IState = {
-  wordMap: new Map<string, IDictDataRequest[]>([["word", []]]),
+  wordList: [],
+  wordIndex: [],
 };
 
 export const slice = createSlice({
   name: "inline-dict",
   initialState,
   reducers: {
-    add: (state, action: PayloadAction<[string, IDictDataRequest[]]>) => {
-      state.wordMap.set(action.payload[0], action.payload[1]);
+    add: (state, action: PayloadAction<IDictDataRequest[]>) => {
+      let words: string[] = [];
+      let newWordData = new Array(...state.wordList);
+      let newIndexData = new Array(...state.wordIndex);
+      //查询没有的词并添加
+      for (const iterator of action.payload) {
+        if (!newIndexData.includes(iterator.word)) {
+          if (!words.includes(iterator.word)) {
+            words.push(iterator.word);
+          }
+          newWordData.push(iterator);
+        }
+      }
+      newIndexData = [...newIndexData, ...words];
+      state.wordList = newWordData;
+      state.wordIndex = newIndexData;
+      console.log("add inline dict", words);
     },
   },
 });
@@ -32,7 +47,8 @@ export const { add } = slice.actions;
 
 export const inlineDict = (state: RootState): IState => state.inlineDict;
 
-export const wordList = (state: RootState): Map<string, IDictDataRequest[]> =>
-  state.inlineDict.wordMap;
-
+export const wordList = (state: RootState): IDictDataRequest[] =>
+  state.inlineDict.wordList;
+export const wordIndex = (state: RootState): string[] =>
+  state.inlineDict.wordIndex;
 export default slice.reducer;

+ 0 - 37
openapi/public/assets/protocol/channel.yaml

@@ -1,37 +0,0 @@
-openapi: 3.0.0
-info:
-  title: Channel API
-  description: 版本风格的增删改查
-  version: 0.1.9
-servers:
-  - url: https://www-hk.wikipali.org/api/v2
-    description: Main (production) server
-  - url: http://spring.visuddhinanda.wikipali.org
-    description: Internal staging server for testing
-paths:
-  /channel:
-    get:
-      summary: Returns a list of channels.
-      description: 返回多行数据。支持关键字搜索,分页,排序
-      parameters:
-        - name: view
-          in: path
-          required: true
-          description: 查询的视图。如:studio,user 等
-          schema:
-            type: string
-            enum:
-              - studio
-              - user
-      responses:
-        "200": # status code
-          description: A JSON array of user names
-          content:
-            application/json:
-              schema:
-                type: object
-                properties:
-                  ok:
-                    type: boolean
-                  message:
-                    type: string

+ 4 - 0
openapi/public/assets/protocol/main.yaml

@@ -13,3 +13,7 @@ paths:
     $ref: "./resources/auth/users/index.yaml"
   /users/sign-in:
     $ref: "./resources/auth/users/sign-in.yaml"
+  /channel:
+    $ref: "./resources/channel/index.yaml"
+  /palitext:
+    $ref: "./resources/corpus/pali-text/index.yaml"

+ 27 - 0
openapi/public/assets/protocol/resources/channel/index.yaml

@@ -0,0 +1,27 @@
+get:
+  summary: Returns a list of channels.
+  tags:
+    - channel
+  description: 返回多行数据。支持关键字搜索,分页,排序
+  parameters:
+    - name: view
+      in: path
+      required: true
+      description: 查询的视图。如:studio,user 等
+      schema:
+        type: string
+        enum:
+          - studio
+          - user
+  responses:
+    "200": # status code
+      description: A JSON array of user names
+      content:
+        application/json:
+          schema:
+            type: object
+            properties:
+              ok:
+                type: boolean
+              message:
+                type: string

+ 13 - 0
openapi/public/assets/protocol/resources/corpus/pali-text/index.yaml

@@ -0,0 +1,13 @@
+get:
+  summary: Returns a list of users.
+  tags:
+    - corpus
+  responses:
+    "200":
+      description: A JSON array of user names
+      content:
+        application/json:
+          schema:
+            type: array
+            items:
+              type: string