Explorar o código

Merge pull request #1876 from visuddhinanda/agile

添加句子复制购物车
visuddhinanda %!s(int64=2) %!d(string=hai) anos
pai
achega
ab90d65751

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

@@ -14,7 +14,7 @@ interface IDetailsWidget {
   isPr?: boolean;
 }
 export const Details = ({ data, isPr }: IDetailsWidget) => (
-  <Space>
+  <Space wrap>
     <Channel {...data.channel} />
     <User {...data.editor} showAvatar={isPr ? true : false} />
     {data.prEditAt ? (

+ 110 - 0
dashboard/src/components/template/SentEdit/SentCart.tsx

@@ -0,0 +1,110 @@
+import { Badge, Button, List, Popover, Tooltip, Typography } from "antd";
+import { useEffect, useState } from "react";
+import { ShoppingCartOutlined, DeleteOutlined } from "@ant-design/icons";
+import { ISentCart } from "./SentTabCopy";
+import "./style.css";
+
+const { Text } = Typography;
+
+const SentCartWidget = () => {
+  const [count, setCount] = useState<number>();
+  const [sentences, setSentences] = useState<ISentCart[]>();
+
+  const query = () => {
+    const cartText = localStorage.getItem("cart/text");
+    if (cartText) {
+      const sent: ISentCart[] = JSON.parse(cartText);
+      setSentences(sent);
+    } else {
+      setSentences([]);
+    }
+  };
+
+  useEffect(() => {
+    if (sentences) {
+      setCount(sentences.length);
+      localStorage.setItem("cart/text", JSON.stringify(sentences));
+    }
+  }, [sentences]);
+
+  useEffect(() => {
+    query();
+    let timer = setInterval(query, 1000 * 2);
+    return () => {
+      clearInterval(timer);
+    };
+  }, []);
+
+  return (
+    <>
+      <Popover
+        placement="bottomRight"
+        arrowPointAtCenter
+        destroyTooltipOnHide
+        getTooltipContainer={(node: HTMLElement) =>
+          document.getElementsByClassName("toolbar_center")[0] as HTMLElement
+        }
+        content={
+          <div>
+            <div style={{ display: "flex", justifyContent: "space-between" }}>
+              <div></div>
+              <div>
+                <Text
+                  disabled={!sentences || sentences.length === 0}
+                  copyable={{
+                    text: sentences?.map((item) => item.id).join("\n"),
+                  }}
+                />
+                <Tooltip title="清空列表保留剪贴板数据">
+                  <Button
+                    disabled={!sentences || sentences.length === 0}
+                    type="link"
+                    danger
+                    icon={<DeleteOutlined />}
+                    onClick={() => {
+                      setSentences([]);
+                    }}
+                  />
+                </Tooltip>
+              </div>
+            </div>
+            <div style={{ width: 450, height: 300, overflowY: "auto" }}>
+              <List
+                size="small"
+                dataSource={sentences}
+                renderItem={(item, index) => (
+                  <List.Item key={index} className="cart_item">
+                    <List.Item.Meta title={item.text} description={item.id} />
+                    <Button
+                      className="cart_delete"
+                      type="link"
+                      danger
+                      icon={<DeleteOutlined />}
+                      onClick={() => {
+                        if (sentences) {
+                          let newArr = [...sentences];
+                          newArr.splice(index, 1);
+                          console.debug("delete", index, newArr);
+                          setSentences(newArr);
+                        }
+                      }}
+                    />
+                  </List.Item>
+                )}
+              />
+            </div>
+          </div>
+        }
+        trigger="click"
+      >
+        <Badge style={{ cursor: "pointer" }} count={count} size="small">
+          <span style={{ color: "white", cursor: "pointer" }}>
+            <ShoppingCartOutlined />
+          </span>
+        </Badge>
+      </Popover>
+    </>
+  );
+};
+
+export default SentCartWidget;

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

@@ -16,6 +16,7 @@ import RelaGraphic from "../Wbw/RelaGraphic";
 import SentMenu from "./SentMenu";
 import { ArticleMode } from "../../article/Article";
 import { IResNumber } from "../SentEdit";
+import SentTabCopy from "./SentTabCopy";
 
 const { Text } = Typography;
 
@@ -123,7 +124,8 @@ const SentTabWidget = ({
             data={mPath}
             trigger={path ? path.length > 0 ? path[0].title : <></> : <></>}
           />
-          <Text copyable={{ text: `{{${sentId[0]}}}` }}>{sentId[0]}</Text>
+          <Text>{sentId[0]}</Text>
+          <SentTabCopy wbwData={wbwData} text={`{{${sentId[0]}}}`} />
           <SentMenu
             book={book}
             para={para}

+ 106 - 0
dashboard/src/components/template/SentEdit/SentTabCopy.tsx

@@ -0,0 +1,106 @@
+import { Dropdown, Tooltip } from "antd";
+import {
+  CopyOutlined,
+  ShoppingCartOutlined,
+  CheckOutlined,
+} from "@ant-design/icons";
+import { useEffect, useState } from "react";
+import { IWbw } from "../Wbw/WbwWord";
+import store from "../../../store";
+import { modeChange } from "../../../reducers/cart-mode";
+import { useAppSelector } from "../../../hooks";
+import { mode as _mode } from "../../../reducers/cart-mode";
+
+export interface ISentCart {
+  id: string;
+  text: string;
+}
+interface IWidget {
+  text?: string;
+  wbwData?: IWbw[];
+}
+const SentTabCopyWidget = ({ text, wbwData }: IWidget) => {
+  const [mode, setMode] = useState("copy");
+  const [success, setSuccess] = useState(false);
+  const currMode = useAppSelector(_mode);
+
+  useEffect(() => {
+    const modeSetting = localStorage.getItem("cart/mode");
+    if (modeSetting === "cart") {
+      setMode("cart");
+    }
+  }, []);
+
+  useEffect(() => {
+    localStorage.setItem("cart/mode", mode);
+  }, [mode]);
+
+  useEffect(() => {
+    if (currMode) {
+      setMode(currMode);
+    }
+  }, [currMode]);
+
+  const copy = (mode: string) => {
+    if (text) {
+      if (mode === "copy") {
+        navigator.clipboard.writeText(text).then(() => {
+          setSuccess(true);
+          setTimeout(() => setSuccess(false), 3000);
+        });
+      } else {
+        const oldText = localStorage.getItem("cart/text");
+        let cartText: ISentCart[] = [];
+        if (oldText) {
+          cartText = JSON.parse(oldText);
+        }
+        const paliText = wbwData
+          ?.filter((value) => value.type?.value !== ".ctl.")
+          .map((item) => item.word.value)
+          .join(" ");
+        cartText.push({ id: text, text: paliText ? paliText : "" });
+        localStorage.setItem("cart/text", JSON.stringify(cartText));
+        setSuccess(true);
+        setTimeout(() => setSuccess(false), 3000);
+      }
+    }
+  };
+  return (
+    <Dropdown.Button
+      size="small"
+      type="link"
+      menu={{
+        items: [
+          {
+            label: "copy",
+            key: "copy",
+            icon: <CopyOutlined />,
+          },
+          {
+            label: "add to cart",
+            key: "cart",
+            icon: <ShoppingCartOutlined />,
+          },
+        ],
+        onClick: (e) => {
+          setMode(e.key);
+          store.dispatch(modeChange(e.key));
+          copy(e.key);
+        },
+      }}
+      onClick={() => copy(mode)}
+    >
+      <Tooltip title={(success ? "已经" : "") + `${mode}`}>
+        {success ? (
+          <CheckOutlined />
+        ) : mode === "copy" ? (
+          <CopyOutlined />
+        ) : (
+          <ShoppingCartOutlined />
+        )}
+      </Tooltip>
+    </Dropdown.Button>
+  );
+};
+
+export default SentTabCopyWidget;

+ 8 - 0
dashboard/src/components/template/SentEdit/style.css

@@ -4,3 +4,11 @@
 .sentence.ant-typography p {
   margin: 0;
 }
+
+.cart_delete {
+  visibility: hidden;
+}
+
+.cart_item:hover .cart_delete {
+  visibility: unset;
+}

+ 32 - 17
dashboard/src/components/template/Wbw/WbwDetailBasic.tsx

@@ -1,8 +1,17 @@
 import { useEffect, useState } from "react";
 import { useIntl } from "react-intl";
-import { Form, Input, AutoComplete, Button, Popover, Space, Badge } from "antd";
+import {
+  Form,
+  Input,
+  AutoComplete,
+  Button,
+  Popover,
+  Space,
+  Badge,
+  Tooltip,
+} from "antd";
 import { Collapse } from "antd";
-import { MoreOutlined } from "@ant-design/icons";
+import { MoreOutlined, QuestionCircleOutlined } from "@ant-design/icons";
 
 import SelectCase from "../../dict/SelectCase";
 import { IWbw, IWbwField } from "./WbwWord";
@@ -263,22 +272,28 @@ const WbwDetailBasicWidget = ({
                   {intl.formatMessage({ id: "buttons.relate" })}
                   <Badge color="geekblue" count={relationCount} />
                 </Space>
-                <Button
-                  type="link"
-                  onClick={() => {
-                    if (data.case && data.case?.value) {
-                      const caseParts = data.case?.value
-                        .split("$")
-                        .map((item) => item.replaceAll(".", ""));
-                      const endCase = caseParts[caseParts.length - 1];
-                      store.dispatch(
-                        lookup(`type:term word:${endCase}.relations`)
-                      );
-                    }
-                  }}
+                <Tooltip
+                  title={intl.formatMessage({
+                    id: "columns.library.palihandbook.title",
+                  })}
                 >
-                  {"语法手册"}
-                </Button>
+                  <Button
+                    disabled
+                    type="link"
+                    onClick={() => {
+                      if (data.case && data.case?.value) {
+                        const caseParts = data.case?.value
+                          .split("$")
+                          .map((item) => item.replaceAll(".", ""));
+                        const endCase = caseParts[caseParts.length - 1];
+                        store.dispatch(
+                          lookup(`type:term word:${endCase}.relations`)
+                        );
+                      }
+                    }}
+                    icon={<QuestionCircleOutlined />}
+                  />
+                </Tooltip>
               </div>
             }
             key="relation"

+ 3 - 0
dashboard/src/pages/library/article/show.tsx

@@ -52,6 +52,7 @@ import ShareButton from "../../../components/export/ShareButton";
 import ChannelAlert from "../../../components/channel/ChannelAlert";
 import PrPull from "../../../components/corpus/PrPull";
 import NotificationIcon from "../../../components/notification/NotificationIcon";
+import SentCart from "../../../components/template/SentEdit/SentCart";
 
 export interface ISearchParams {
   key: string;
@@ -168,6 +169,7 @@ const Widget = () => {
             </div>
             <div style={{ display: "flex" }} key="middle"></div>
             <div
+              className="toolbar_center"
               style={{ display: "flex", height: 44, alignItems: "center" }}
               key="right"
             >
@@ -196,6 +198,7 @@ const Widget = () => {
                 articleId={id}
                 anthologyId={searchParams.get("anthology")}
               />
+              <SentCart />
               <SearchButton style={{ marginRight: 8 }} />
               <ToStudio style={{ marginRight: 8 }} />
               <Avatar placement="bottom" style={{ marginRight: 8 }} />

+ 29 - 0
dashboard/src/reducers/cart-mode.ts

@@ -0,0 +1,29 @@
+/**
+ * 查字典,添加术语命令
+ */
+import { createSlice, PayloadAction } from "@reduxjs/toolkit";
+
+import type { RootState } from "../store";
+
+interface IState {
+  mode?: string;
+}
+
+const initialState: IState = {};
+
+export const slice = createSlice({
+  name: "cart-mode",
+  initialState,
+  reducers: {
+    modeChange: (state, action: PayloadAction<string>) => {
+      state.mode = action.payload;
+    },
+  },
+});
+
+export const { modeChange } = slice.actions;
+
+export const mode = (state: RootState): string | undefined =>
+  state.cartMode.mode;
+
+export default slice.reducer;

+ 2 - 0
dashboard/src/store.ts

@@ -28,6 +28,7 @@ import termOrderReducer from "./reducers/term-order";
 import focusReducer from "./reducers/focus";
 import prLoadReducer from "./reducers/pr-load";
 import termClickReducer from "./reducers/term-click";
+import cartModeReducer from "./reducers/cart-mode";
 
 const store = configureStore({
   reducer: {
@@ -59,6 +60,7 @@ const store = configureStore({
     focus: focusReducer,
     prLoad: prLoadReducer,
     termClick: termClickReducer,
+    cartMode: cartModeReducer,
   },
 });