Просмотр исходного кода

feat(term): 术语页编辑按钮国际化并新增分享弹窗

- Edit 按钮改用 buttons.edit 国际化文案并添加 EditOutlined 图标
- 新增 TermShare 组件:分享弹窗包含藏经阁访问链接与译经楼访问链接,均带复制/打开按钮
- 补充 labels.library.access.link/tip、labels.studio.access.link、message.copy.success/fail 多语言 key
visuddhinanda 3 дней назад
Родитель
Сommit
74aa0c3227

+ 15 - 3
dashboard-v6/src/components/article/TypeTerm.tsx

@@ -1,4 +1,6 @@
 import { Breadcrumb, Button, Space } from "antd";
+import { useIntl } from "react-intl";
+import { EditOutlined, ShareAltOutlined } from "@ant-design/icons";
 import type { ArticleMode } from "../../api/article";
 import { useAppSelector } from "../../hooks";
 import { currentUser } from "../../reducers/current-user";
@@ -7,6 +9,7 @@ import "./article.css";
 import ArticleHeader from "./components/ArticleHeader";
 import ArticleLayout from "./components/ArticleLayout";
 import { useTerm } from "./hooks/useTerm";
+import TermShare from "../term/TermShare";
 
 interface IWidget {
   id?: string;
@@ -29,6 +32,7 @@ const TypeTermWidget = ({
     mode,
   });
   const currUser = useAppSelector(currentUser);
+  const intl = useIntl();
 
   const path = [
     { title: currUser?.nickName },
@@ -52,9 +56,17 @@ const TypeTermWidget = ({
           </Space>
         }
         action={
-          <Button type="primary" onClick={onEdit}>
-            Edit
-          </Button>
+          <Space>
+            <Button type="primary" icon={<EditOutlined />} onClick={onEdit}>
+              {intl.formatMessage({ id: "buttons.edit" })}
+            </Button>
+            {id ? (
+              <TermShare
+                id={id}
+                trigger={<Button icon={<ShareAltOutlined />} />}
+              />
+            ) : undefined}
+          </Space>
         }
       />
       <ArticleLayout

+ 115 - 0
dashboard-v6/src/components/term/TermShare.tsx

@@ -0,0 +1,115 @@
+import { useIntl } from "react-intl";
+import { useState } from "react";
+import { Button, Divider, Input, Modal, Typography, message } from "antd";
+import {
+  CopyOutlined,
+  ExportOutlined,
+  GlobalOutlined,
+  LinkOutlined,
+} from "@ant-design/icons";
+
+import { fullUrl } from "../../utils";
+
+const { Text } = Typography;
+
+interface IWidget {
+  id?: string;
+  trigger?: React.ReactNode;
+  open?: boolean;
+  onClose?: () => void;
+}
+
+const TermShareWidget = ({ id, trigger, open, onClose }: IWidget) => {
+  const intl = useIntl();
+  const [innerOpen, setInnerOpen] = useState(false);
+
+  const isOpen = open ?? innerOpen;
+
+  // 藏经阁(对外公开)访问链接
+  const libraryUrl = id
+    ? `${import.meta.env.VITE_APP_API_SERVER}/library/wiki/${intl.locale}/${id}`
+    : "";
+  // 译经楼(工作台)访问链接
+  const studioUrl = id ? fullUrl(`/workspace/term/${id}`) : "";
+
+  const close = () => {
+    if (onClose) {
+      onClose();
+    } else {
+      setInnerOpen(false);
+    }
+  };
+
+  const copy = (url: string) => {
+    navigator.clipboard
+      .writeText(url)
+      .then(() => {
+        message.success(intl.formatMessage({ id: "message.copy.success" }));
+      })
+      .catch(() => {
+        message.error(intl.formatMessage({ id: "message.copy.fail" }));
+      });
+  };
+
+  const linkRow = (
+    label: string,
+    url: string,
+    prefix: React.ReactNode
+  ): React.ReactNode => (
+    <div>
+      <Text type="secondary">{label}</Text>
+      <div style={{ display: "flex", gap: 8, marginTop: 8 }}>
+        <Input
+          readOnly
+          value={url}
+          prefix={prefix}
+          style={{ flex: 1 }}
+          onFocus={(e) => e.target.select()}
+        />
+        <Button icon={<CopyOutlined />} onClick={() => copy(url)}>
+          {intl.formatMessage({ id: "buttons.copy" })}
+        </Button>
+        <Button
+          icon={<ExportOutlined />}
+          onClick={() => window.open(url, "_blank")}
+        >
+          {intl.formatMessage({ id: "buttons.open" })}
+        </Button>
+      </div>
+    </div>
+  );
+
+  return (
+    <>
+      <span onClick={() => setInnerOpen(true)}>{trigger}</span>
+      <Modal
+        destroyOnHidden
+        width={560}
+        title={intl.formatMessage({ id: "buttons.share" })}
+        open={isOpen}
+        onCancel={close}
+        footer={null}
+      >
+        {linkRow(
+          intl.formatMessage({ id: "labels.library.access.link" }),
+          libraryUrl,
+          <GlobalOutlined />
+        )}
+        <Text
+          type="secondary"
+          style={{ fontSize: 12, display: "block", marginTop: 8 }}
+        >
+          {intl.formatMessage({ id: "labels.library.access.tip" })}
+        </Text>
+        <Divider style={{ margin: "16px 0" }} />
+        {linkRow(
+          intl.formatMessage({ id: "labels.studio.access.link" }),
+          studioUrl,
+          <LinkOutlined />
+        )}
+      </Modal>
+    </>
+  );
+};
+
+export default TermShareWidget;

+ 4 - 0
dashboard-v6/src/locales/en-US/label.ts

@@ -9,6 +9,10 @@ const items = {
   "labels.collaborators": "collaborators",
   "labels.collaboration": "collaboration",
   "labels.link": "link",
+  "labels.library.access.link": "Library access link",
+  "labels.library.access.tip":
+    "Anyone with this link can read this term in the Library.",
+  "labels.studio.access.link": "Studio access link",
   "labels.upload": "upload",
   "labels.first-term": "first term",
   "labels.sign-in": "sign in",

+ 2 - 0
dashboard-v6/src/locales/en-US/message.ts

@@ -1,5 +1,7 @@
 const items = {
   "flashes.success": "success",
+  "message.copy.success": "Copied to clipboard",
+  "message.copy.fail": "Copy failed",
   "message.irrevocable": "irrevocable",
   "message.delete.confirm": "confirm delete",
   "message.auth.guest.alert": "guest read public resource only.",

+ 3 - 0
dashboard-v6/src/locales/zh-Hans/label.ts

@@ -9,6 +9,9 @@ const items = {
   "labels.collaborators": "协作者",
   "labels.collaboration": "协作",
   "labels.link": "链接",
+  "labels.library.access.link": "藏经阁访问链接",
+  "labels.library.access.tip": "任何获得此链接的人,都可以在藏经阁中阅读该术语。",
+  "labels.studio.access.link": "译经楼访问链接",
   "labels.upload": "上传",
   "labels.first-term": "第一个术语",
   "labels.sign-in": "登录",

+ 2 - 0
dashboard-v6/src/locales/zh-Hans/message.ts

@@ -1,5 +1,7 @@
 const items = {
   "flashes.success": "操作成功",
+  "message.copy.success": "已复制到剪贴板",
+  "message.copy.fail": "复制失败",
   "message.irrevocable": "此操作不可撤销",
   "message.delete.confirm": "确定删除吗?",
   "message.auth.guest.alert":