Explorar o código

:fire: v4 backup

visuddhinanda hai 5 días
pai
achega
4cebc5ea1c
Modificáronse 100 ficheiros con 0 adicións e 11482 borrados
  1. 0 12
      dashboard-v6/backup/components/README.md
  2. 0 59
      dashboard-v6/backup/components/admin/HeadBar.tsx
  3. 0 94
      dashboard-v6/backup/components/admin/LeftSider.tsx
  4. 0 64
      dashboard-v6/backup/components/admin/api/ApiDelayHour.tsx
  5. 0 83
      dashboard-v6/backup/components/admin/api/ApiGauge.tsx
  6. 0 42
      dashboard-v6/backup/components/admin/relation/CaseSelect.tsx
  7. 0 84
      dashboard-v6/backup/components/admin/relation/GrammarSelect.tsx
  8. 0 128
      dashboard-v6/backup/components/admin/relation/NissayaEndingEdit.tsx
  9. 0 200
      dashboard-v6/backup/components/admin/relation/RelationEdit.tsx
  10. 0 80
      dashboard-v6/backup/components/ai/AiAssistantSelect.tsx
  11. 0 69
      dashboard-v6/backup/components/ai/AiModelCreate.tsx
  12. 0 114
      dashboard-v6/backup/components/ai/AiModelEdit.tsx
  13. 0 150
      dashboard-v6/backup/components/ai/AiModelList.tsx
  14. 0 154
      dashboard-v6/backup/components/ai/AiModelLogList.tsx
  15. 0 78
      dashboard-v6/backup/components/ai/AiTranslate.tsx
  16. 0 145
      dashboard-v6/backup/components/ai/ModelSelector.tsx
  17. 0 83
      dashboard-v6/backup/components/anthology/AnthologyCreate.tsx
  18. 0 396
      dashboard-v6/backup/components/anthology/AnthologyList.tsx
  19. 0 67
      dashboard-v6/backup/components/anthology/AnthologyModal.tsx
  20. 0 52
      dashboard-v6/backup/components/anthology/AnthologySelect.tsx
  21. 0 87
      dashboard-v6/backup/components/anthology/AnthologyTocTree.tsx
  22. 0 215
      dashboard-v6/backup/components/anthology/EditableTocTree.tsx
  23. 0 42
      dashboard-v6/backup/components/anthology/TextBookToc.tsx
  24. 0 246
      dashboard-v6/backup/components/api/Article.ts
  25. 0 38
      dashboard-v6/backup/components/api/Attachments.ts
  26. 0 124
      dashboard-v6/backup/components/api/Auth.ts
  27. 0 74
      dashboard-v6/backup/components/api/Channel.ts
  28. 0 88
      dashboard-v6/backup/components/api/Comment.ts
  29. 0 326
      dashboard-v6/backup/components/api/Corpus.ts
  30. 0 227
      dashboard-v6/backup/components/api/Course.ts
  31. 0 141
      dashboard-v6/backup/components/api/Dict.ts
  32. 0 25
      dashboard-v6/backup/components/api/Exp.ts
  33. 0 80
      dashboard-v6/backup/components/api/Group.ts
  34. 0 5
      dashboard-v6/backup/components/api/Guide.ts
  35. 0 46
      dashboard-v6/backup/components/api/Share.ts
  36. 0 30
      dashboard-v6/backup/components/api/Suggestion.ts
  37. 0 84
      dashboard-v6/backup/components/api/Tag.ts
  38. 0 83
      dashboard-v6/backup/components/api/Term.ts
  39. 0 45
      dashboard-v6/backup/components/api/Transfer.ts
  40. 0 105
      dashboard-v6/backup/components/api/ai.ts
  41. 0 56
      dashboard-v6/backup/components/api/like.ts
  42. 0 43
      dashboard-v6/backup/components/api/notification.ts
  43. 0 260
      dashboard-v6/backup/components/api/task.ts
  44. 0 26
      dashboard-v6/backup/components/api/token.ts
  45. 0 51
      dashboard-v6/backup/components/api/view.ts
  46. 0 45
      dashboard-v6/backup/components/api/webhook.ts
  47. 0 82
      dashboard-v6/backup/components/article/AddToAnthology.tsx
  48. 0 91
      dashboard-v6/backup/components/article/AnchorNav.tsx
  49. 0 72
      dashboard-v6/backup/components/article/AnthologiesAtArticle.tsx
  50. 0 68
      dashboard-v6/backup/components/article/AnthologyCard.tsx
  51. 0 126
      dashboard-v6/backup/components/article/AnthologyDetail.tsx
  52. 0 198
      dashboard-v6/backup/components/article/AnthologyInfoEdit.tsx
  53. 0 87
      dashboard-v6/backup/components/article/AnthologyList.tsx
  54. 0 56
      dashboard-v6/backup/components/article/AnthologyStudioList.tsx
  55. 0 226
      dashboard-v6/backup/components/article/Article.tsx
  56. 0 90
      dashboard-v6/backup/components/article/ArticleCard.tsx
  57. 0 78
      dashboard-v6/backup/components/article/ArticleCardMainMenu.tsx
  58. 0 139
      dashboard-v6/backup/components/article/ArticleCreate.tsx
  59. 0 126
      dashboard-v6/backup/components/article/ArticleDrawer.tsx
  60. 0 302
      dashboard-v6/backup/components/article/ArticleEdit.tsx
  61. 0 74
      dashboard-v6/backup/components/article/ArticleEditDrawer.tsx
  62. 0 59
      dashboard-v6/backup/components/article/ArticleEditTools.tsx
  63. 0 553
      dashboard-v6/backup/components/article/ArticleList.tsx
  64. 0 58
      dashboard-v6/backup/components/article/ArticleListModal.tsx
  65. 0 109
      dashboard-v6/backup/components/article/ArticleListPublic.tsx
  66. 0 94
      dashboard-v6/backup/components/article/ArticlePrevDrawer.tsx
  67. 0 13
      dashboard-v6/backup/components/article/ArticleSkeleton.tsx
  68. 0 153
      dashboard-v6/backup/components/article/ArticleView.tsx
  69. 0 73
      dashboard-v6/backup/components/article/ChapterToc.tsx
  70. 0 463
      dashboard-v6/backup/components/article/EditableTree.tsx
  71. 0 72
      dashboard-v6/backup/components/article/EditableTreeNode.tsx
  72. 0 83
      dashboard-v6/backup/components/article/ExerciseList.tsx
  73. 0 56
      dashboard-v6/backup/components/article/Find.tsx
  74. 0 21
      dashboard-v6/backup/components/article/MainMenu.tsx
  75. 0 82
      dashboard-v6/backup/components/article/ModeSwitch.tsx
  76. 0 38
      dashboard-v6/backup/components/article/Nav.tsx
  77. 0 80
      dashboard-v6/backup/components/article/Navigate.tsx
  78. 0 135
      dashboard-v6/backup/components/article/NavigateButton.tsx
  79. 0 89
      dashboard-v6/backup/components/article/PaliTextToc.tsx
  80. 0 173
      dashboard-v6/backup/components/article/ProTabs.tsx
  81. 0 222
      dashboard-v6/backup/components/article/RightPanel.tsx
  82. 0 63
      dashboard-v6/backup/components/article/RightToolsSwitch.tsx
  83. 0 25
      dashboard-v6/backup/components/article/TermShell.tsx
  84. 0 295
      dashboard-v6/backup/components/article/TocTree.tsx
  85. 0 92
      dashboard-v6/backup/components/article/Token.tsx
  86. 0 60
      dashboard-v6/backup/components/article/TokenModal.tsx
  87. 0 38
      dashboard-v6/backup/components/article/ToolButton.tsx
  88. 0 159
      dashboard-v6/backup/components/article/ToolButtonDiscussion.tsx
  89. 0 227
      dashboard-v6/backup/components/article/ToolButtonNav.tsx
  90. 0 47
      dashboard-v6/backup/components/article/ToolButtonNavMore.tsx
  91. 0 41
      dashboard-v6/backup/components/article/ToolButtonNavSliceTitle.tsx
  92. 0 145
      dashboard-v6/backup/components/article/ToolButtonPr.tsx
  93. 0 62
      dashboard-v6/backup/components/article/ToolButtonSearch.tsx
  94. 0 24
      dashboard-v6/backup/components/article/ToolButtonSetting.tsx
  95. 0 22
      dashboard-v6/backup/components/article/ToolButtonTag.tsx
  96. 0 87
      dashboard-v6/backup/components/article/ToolButtonToc.tsx
  97. 0 401
      dashboard-v6/backup/components/article/TreeText.tsx
  98. 0 71
      dashboard-v6/backup/components/article/TypeAnthology.tsx
  99. 0 101
      dashboard-v6/backup/components/article/TypeArticle.tsx
  100. 0 335
      dashboard-v6/backup/components/article/TypeArticleReader.tsx

+ 0 - 12
dashboard-v6/backup/components/README.md

@@ -1,12 +0,0 @@
-# 组件
-
-## 目录
-
-某栏目的专用组件放在以该栏目命名的目录下
-
-- `libray` 前台(阅读)的栏目
-  - `栏目名称` 某栏目的专用组件
-  - `组件1.tsx` libray 下面的栏目的公共组件
-- `studio` 用户后台(课程发布等)
-
-nut目录为练习用途。里面的内容可能会被删除。**线上不要使用**。

+ 0 - 59
dashboard-v6/backup/components/admin/HeadBar.tsx

@@ -1,59 +0,0 @@
-import { Link } from "react-router";
-import { Input, Layout, Space } from "antd";
-
-import img_banner from "../../assets/studio/images/wikipali_banner.svg";
-import UiLangSelect from "../general/UiLangSelect";
-import SignInAvatar from "../auth/SignInAvatar";
-import ToLibrary from "../auth/ToLibrary";
-import ThemeSelect from "../general/ThemeSelect";
-
-const { Search } = Input;
-const { Header } = Layout;
-
-const onSearch = (value: string) => console.log(value);
-
-const HeadBarWidget = () => {
-  return (
-    <Header
-      className="header"
-      style={{
-        lineHeight: "44px",
-        height: 44,
-        paddingLeft: 10,
-        paddingRight: 10,
-      }}
-    >
-      <div
-        style={{
-          display: "flex",
-          width: "100%",
-          justifyContent: "space-between",
-        }}
-      >
-        <div style={{ width: 80 }}>
-          <Link to="/">
-            <img alt="code" style={{ height: 36 }} src={img_banner} />
-          </Link>
-        </div>
-        <div style={{ width: 500, lineHeight: 44 }}>
-          <Search
-            disabled
-            placeholder="input search text"
-            onSearch={onSearch}
-            style={{ width: "100%" }}
-          />
-        </div>
-        <div>
-          <Space>
-            <ToLibrary />
-            <SignInAvatar />
-            <UiLangSelect />
-            <ThemeSelect />
-          </Space>
-        </div>
-      </div>
-    </Header>
-  );
-};
-
-export default HeadBarWidget;

+ 0 - 94
dashboard-v6/backup/components/admin/LeftSider.tsx

@@ -1,94 +0,0 @@
-import { Link } from "react-router";
-import type { MenuProps } from "antd";
-import { Affix, Layout } from "antd";
-import { Menu } from "antd";
-import { AppstoreOutlined, HomeOutlined } from "@ant-design/icons";
-
-const { Sider } = Layout;
-
-const onClick: MenuProps["onClick"] = (e) => {
-  console.log("click ", e);
-};
-
-type IWidgetHeadBar = {
-  selectedKeys?: string;
-};
-const LeftSiderWidget = ({ selectedKeys = "" }: IWidgetHeadBar) => {
-  const items: MenuProps["items"] = [
-    {
-      label: "API",
-      key: "api",
-      icon: <HomeOutlined />,
-      children: [
-        {
-          label: <Link to="/admin/api/dashboard">dashboard</Link>,
-          key: "dashboard",
-        },
-      ],
-    },
-    {
-      label: "管理",
-      key: "manage",
-      icon: <HomeOutlined />,
-      children: [
-        {
-          label: <Link to="/admin/relation/list">Relation</Link>,
-          key: "relation",
-        },
-        {
-          label: <Link to="/admin/nissaya-ending/list">Nissaya Ending</Link>,
-          key: "nissaya-ending",
-        },
-        {
-          label: <Link to="/admin/ai/list">AI</Link>,
-          key: "ai",
-        },
-        {
-          label: "Dictionary",
-          key: "dict",
-          children: [
-            {
-              label: <Link to="/admin/dictionary/list">List</Link>,
-              key: "list",
-            },
-            {
-              label: <Link to="/admin/dictionary/preference">Preference</Link>,
-              key: "preference",
-            },
-          ],
-        },
-        {
-          label: <Link to="/admin/users/list">users</Link>,
-          key: "users",
-        },
-        {
-          label: <Link to="/admin/invite/list">invite</Link>,
-          key: "invite",
-        },
-      ],
-    },
-    {
-      label: "统计",
-      key: "advance",
-      icon: <AppstoreOutlined />,
-      children: [],
-    },
-  ];
-
-  return (
-    <Affix offsetTop={0}>
-      <Sider width={200} breakpoint="lg" className="site-layout-background">
-        <Menu
-          theme="light"
-          onClick={onClick}
-          defaultSelectedKeys={[selectedKeys]}
-          defaultOpenKeys={["basic", "advance", "collaboration"]}
-          mode="inline"
-          items={items}
-        />
-      </Sider>
-    </Affix>
-  );
-};
-
-export default LeftSiderWidget;

+ 0 - 64
dashboard-v6/backup/components/admin/api/ApiDelayHour.tsx

@@ -1,64 +0,0 @@
-import React, { useEffect, useState } from "react";
-import { Column } from "@ant-design/plots";
-import { put } from "../../../request";
-import { StatisticCard } from "@ant-design/pro-components";
-
-interface IApiDelay {
-  date: string;
-  value: number;
-}
-interface IApiDelayResponse {
-  ok: boolean;
-  message: string;
-  data: IApiDelay[];
-}
-interface IApiRequest {
-  api: string;
-  item: string;
-}
-
-interface IWidget {
-  title?: React.ReactNode;
-  type: "average" | "count" | "delay";
-  api?: string;
-}
-
-const ApiDelayHourWidget = ({ title, type, api = "all" }: IWidget) => {
-  const [delayData, setDelayData] = useState<IApiDelay[]>([]);
-
-  useEffect(() => {
-    put<IApiRequest, IApiDelayResponse>("/v2/api/10", {
-      api: api,
-      item: type,
-    }).then((json) => {
-      console.log("data", json.data);
-      setDelayData(json.data);
-    });
-  }, []);
-
-  const config = {
-    data: delayData,
-    xField: "date",
-    yField: "value",
-    seriesField: "",
-    xAxis: {
-      label: {
-        autoHide: true,
-        autoRotate: false,
-      },
-    },
-  };
-
-  return (
-    <StatisticCard
-      statistic={{
-        title: title,
-        value: "",
-        suffix: "/ ms",
-      }}
-      chart={<Column {...config} height={300} />}
-    />
-  );
-};
-
-export default ApiDelayHourWidget;

+ 0 - 83
dashboard-v6/backup/components/admin/api/ApiGauge.tsx

@@ -1,83 +0,0 @@
-import { useEffect, useRef, useState } from "react";
-import { Gauge } from "@ant-design/plots";
-import { get } from "../../../request";
-import { StatisticCard } from "@ant-design/pro-components";
-
-interface IApiResponse {
-  ok: boolean;
-  message: string;
-  data: number;
-}
-const ApiGaugeWidget = () => {
-  const min = 0;
-  const max = 1;
-  const [percent, setPercent] = useState<number>(0);
-  const [delay, setDelay] = useState<number>(0);
-  const maxAxis = 5000; //最大量程-毫秒
-
-  useEffect(() => {
-    const timer = setInterval(() => {
-      get<IApiResponse>("/v2/api/10?item=average").then((json) => {
-        setPercent(json.data / maxAxis);
-        setDelay(json.data);
-      });
-    }, 1000 * 5);
-    return () => {
-      clearInterval(timer);
-    };
-  }, []);
-
-  const graphRef: any = useRef(null);
-
-  const config = {
-    percent: percent,
-    range: {
-      ticks: [min, max],
-      color: ["l(0) 0:#30BF78 0.5:#FAAD14 1:#F4664A"],
-    },
-    indicator: {
-      pointer: {
-        style: {
-          stroke: "#D0D0D0",
-        },
-      },
-      pin: {
-        style: {
-          stroke: "#D0D0D0",
-        },
-      },
-    },
-    axis: {
-      label: {
-        formatter(v: any) {
-          return Number(v) * maxAxis;
-        },
-      },
-      subTickLine: {
-        count: 3,
-      },
-    },
-  };
-
-  return (
-    <StatisticCard
-      style={{ width: 400 }}
-      statistic={{
-        title: "平均相应时间",
-        value: delay,
-        suffix: "/ ms",
-      }}
-      chart={
-        <Gauge
-          ref={graphRef}
-          {...config}
-          onReady={(chart) => {
-            graphRef.current = chart;
-          }}
-        />
-      }
-    />
-  );
-};
-
-export default ApiGaugeWidget;

+ 0 - 42
dashboard-v6/backup/components/admin/relation/CaseSelect.tsx

@@ -1,42 +0,0 @@
-import { ProFormSelect } from "@ant-design/pro-components";
-
-import { useIntl } from "react-intl";
-
-interface IWidget {
-  name?: string;
-  width?: number | "md" | "sm" | "xl" | "xs" | "lg";
-}
-const CaseSelectWidget = ({ name = "case", width = "md" }: IWidget) => {
-  const intl = useIntl();
-  const _case = [
-    "nom",
-    "acc",
-    "gen",
-    "dat",
-    "inst",
-    "abl",
-    "loc",
-    "ger",
-    "adv",
-  ];
-  const caseOptions = _case.map((item) => {
-    return {
-      value: item,
-      label: intl.formatMessage({
-        id: `dict.fields.type.${item}.label`,
-        defaultMessage: item,
-      }),
-    };
-  });
-
-  return (
-    <ProFormSelect
-      options={caseOptions}
-      width={width}
-      name={name}
-      label={intl.formatMessage({ id: "forms.fields.case.label" })}
-    />
-  );
-};
-
-export default CaseSelectWidget;

+ 0 - 84
dashboard-v6/backup/components/admin/relation/GrammarSelect.tsx

@@ -1,84 +0,0 @@
-import { ProFormSelect } from "@ant-design/pro-components";
-import type { JSX } from "react";
-
-import { useIntl } from "react-intl";
-
-interface IWidget {
-  name: string;
-  trigger?: JSX.Element;
-  id?: string;
-  hidden?: boolean;
-  onSuccess?: () => void;
-}
-const GrammarSelectWidget = ({ name, hidden = false }: IWidget) => {
-  const intl = useIntl();
-  const _verb = [
-    "n",
-    "ti",
-    "v",
-    "v:ind",
-    "ind",
-    "sg",
-    "pl",
-    "nom",
-    "acc",
-    "gen",
-    "dat",
-    "inst",
-    "voc",
-    "abl",
-    "loc",
-    "base",
-    "imp",
-    "opt",
-    "pres",
-    "aor",
-    "fut",
-    "1p",
-    "2p",
-    "3p",
-    "prp",
-    "pp",
-    "grd",
-    "fpp",
-    "vdn",
-    "ger",
-    "inf",
-    "adj",
-    "pron",
-    "caus",
-    "num",
-    "adv",
-    "conj",
-    "pre",
-    "suf",
-    "ti:base",
-    "n:base",
-    "v:base",
-    "vdn",
-  ];
-  const verbOptions = _verb.map((item) => {
-    return {
-      value: item,
-      label: intl.formatMessage({
-        id: `dict.fields.type.${item}.label`,
-        defaultMessage: item,
-      }),
-    };
-  });
-  return (
-    <ProFormSelect
-      hidden={hidden}
-      options={verbOptions}
-      fieldProps={{
-        mode: "tags",
-      }}
-      width="md"
-      name={name}
-      allowClear={false}
-      label={intl.formatMessage({ id: "forms.fields.case.label" })}
-    />
-  );
-};
-
-export default GrammarSelectWidget;

+ 0 - 128
dashboard-v6/backup/components/admin/relation/NissayaEndingEdit.tsx

@@ -1,128 +0,0 @@
-import { ModalForm, ProForm, ProFormText } from "@ant-design/pro-components";
-import { Form, message } from "antd";
-
-import { useState } from "react";
-import { useIntl } from "react-intl";
-import type {
-  INissayaEnding,
-  INissayaEndingRequest,
-  INissayaEndingResponse,
-} from "../../../pages/admin/nissaya-ending/list";
-import { get, post, put } from "../../../request";
-import LangSelect from "../../general/LangSelect";
-import GrammarSelect from "./GrammarSelect";
-
-interface IWidget {
-  trigger?: JSX.Element;
-  id?: string;
-  onSuccess?: Function;
-}
-const NissayaEndingWidget = ({
-  trigger = <>{"trigger"}</>,
-  id,
-  onSuccess,
-}: IWidget) => {
-  const [title, setTitle] = useState<string | undefined>(id ? "" : "新建");
-  const [form] = Form.useForm<INissayaEnding>();
-  const intl = useIntl();
-  return (
-    <ModalForm<INissayaEnding>
-      title={title}
-      trigger={trigger}
-      form={form}
-      autoFocusFirstInput
-      modalProps={{
-        destroyOnHidden: true,
-        onCancel: () => console.log("run"),
-      }}
-      submitTimeout={2000}
-      onFinish={async (values) => {
-        const data = values;
-        data.from = {
-          spell: values.fromSpell,
-          case: values.fromCase ? values.fromCase : undefined,
-        };
-        let res: INissayaEndingResponse;
-        if (typeof id === "undefined") {
-          res = await post<INissayaEndingRequest, INissayaEndingResponse>(
-            `/v2/nissaya-ending`,
-            data
-          );
-        } else {
-          res = await put<INissayaEndingRequest, INissayaEndingResponse>(
-            `/v2/nissaya-ending/${id}`,
-            data
-          );
-        }
-        console.log(res);
-        if (res.ok) {
-          message.success("提交成功");
-          if (typeof onSuccess !== "undefined") {
-            onSuccess();
-          }
-        } else {
-          message.error(res.message);
-        }
-
-        return true;
-      }}
-      request={
-        id
-          ? async () => {
-              const res = await get<INissayaEndingResponse>(
-                `/v2/nissaya-ending/${id}`
-              );
-              console.log("nissaya-ending get", res);
-              if (res.ok) {
-                setTitle(res.data.ending);
-
-                return {
-                  id: id,
-                  ending: res.data.ending,
-                  relation: res.data.relation,
-                  from: res.data.from,
-                  fromCase: res.data.from?.case,
-                  fromSpell: res.data.from?.spell,
-                  lang: res.data.lang,
-                };
-              } else {
-                return {
-                  id: undefined,
-                  ending: "",
-                  relation: "",
-                  lang: "",
-                };
-              }
-            }
-          : undefined
-      }
-    >
-      <ProForm.Group>
-        <ProFormText
-          width="md"
-          name="ending"
-          label={intl.formatMessage({ id: "forms.fields.ending.label" })}
-          tooltip="最长为 24 位"
-        />
-        <LangSelect width="md" />
-      </ProForm.Group>
-      <ProForm.Group>
-        <GrammarSelect name="fromCase" />
-        <ProFormText
-          width="md"
-          name="fromSpell"
-          label={intl.formatMessage({ id: "buttons.spell" })}
-        />
-      </ProForm.Group>
-      <ProForm.Group>
-        <ProFormText
-          width="md"
-          name="relation"
-          label={intl.formatMessage({ id: "forms.fields.relation.label" })}
-        />
-      </ProForm.Group>
-    </ModalForm>
-  );
-};
-
-export default NissayaEndingWidget;

+ 0 - 200
dashboard-v6/backup/components/admin/relation/RelationEdit.tsx

@@ -1,200 +0,0 @@
-import {
-  ModalForm,
-  ProForm,
-  type ProFormInstance,
-  ProFormSelect,
-  ProFormText,
-} from "@ant-design/pro-components";
-import { Form, message } from "antd";
-
-import { useRef, useState } from "react";
-import { useIntl } from "react-intl";
-import type {
-  IRelation,
-  IRelationRequest,
-  IRelationResponse,
-} from "../../../pages/admin/relation/list";
-import { get, post, put } from "../../../request";
-import GrammarSelect from "./GrammarSelect";
-
-export const _verb = [
-  "n",
-  "ti",
-  "v",
-  "v:ind",
-  "ind",
-  "sg",
-  "pl",
-  "nom",
-  "acc",
-  "gen",
-  "dat",
-  "inst",
-  "voc",
-  "abl",
-  "loc",
-  "base",
-  "imp",
-  "opt",
-  "pres",
-  "aor",
-  "fut",
-  "1p",
-  "2p",
-  "3p",
-  "prp",
-  "pp",
-  "grd",
-  "fpp",
-  "vdn",
-  "ger",
-  "inf",
-  "adj",
-  "pron",
-  "caus",
-  "num",
-  "adv",
-  "conj",
-  "pre",
-  "suf",
-  "ti:base",
-  "n:base",
-  "v:base",
-  "vdn",
-];
-interface IWidget {
-  trigger?: JSX.Element;
-  id?: string;
-  onSuccess?: Function;
-}
-const RelationEditWidget = ({
-  trigger = <>{"trigger"}</>,
-  id,
-  onSuccess,
-}: IWidget) => {
-  const [title, setTitle] = useState<string | undefined>(id ? "" : "新建");
-  const [form] = Form.useForm<IRelation>();
-  const formRef = useRef<ProFormInstance | undefined>(undefined);
-  const intl = useIntl();
-
-  return (
-    <ModalForm<IRelation>
-      title={title}
-      trigger={trigger}
-      formRef={formRef}
-      form={form}
-      autoFocusFirstInput
-      modalProps={{
-        destroyOnHidden: true,
-        onCancel: () => console.log("onCancel"),
-      }}
-      submitTimeout={3000}
-      onFinish={async (values) => {
-        const data = values;
-        data.from = { spell: values.fromSpell, case: values.fromCase };
-        data.to = { spell: values.toSpell, case: values.toCase };
-        let res: IRelationResponse;
-        if (typeof id === "undefined") {
-          res = await post<IRelationRequest, IRelationResponse>(
-            `/v2/relation`,
-            data
-          );
-        } else {
-          res = await put<IRelationRequest, IRelationResponse>(
-            `/v2/relation/${id}`,
-            data
-          );
-        }
-        console.log(res);
-        if (res.ok) {
-          message.success("提交成功");
-          if (typeof onSuccess !== "undefined") {
-            onSuccess();
-          }
-        } else {
-          message.error(res.message);
-        }
-
-        return true;
-      }}
-      request={
-        id
-          ? async () => {
-              const res = await get<IRelationResponse>(`/v2/relation/${id}`);
-              console.log("relation get", res);
-              if (res.ok) {
-                setTitle(res.data.name + "dd");
-
-                return {
-                  id: id,
-                  name: res.data.name,
-                  case: res.data.case,
-                  from: res.data.from,
-                  fromCase: res.data.from?.case,
-                  fromSpell: res.data.from?.spell,
-                  to: res.data.to,
-                  toCase: res.data.to?.case,
-                  toSpell: res.data.to?.spell,
-                  match: res.data.match ? res.data.match : undefined,
-                  category: res.data.category,
-                };
-              } else {
-                return {
-                  id: undefined,
-                  name: "",
-                };
-              }
-            }
-          : undefined
-      }
-    >
-      <ProForm.Group>
-        <ProFormText
-          width="md"
-          name="name"
-          label={intl.formatMessage({ id: "forms.fields.name.label" })}
-        />
-      </ProForm.Group>
-      <ProForm.Group title="从">
-        <GrammarSelect name="fromCase" />
-        <ProFormText
-          width="md"
-          name="fromSpell"
-          label={intl.formatMessage({ id: "buttons.spell" })}
-        />
-      </ProForm.Group>
-      <ProForm.Group title="连接到">
-        <GrammarSelect name="toCase" />
-        <ProFormText
-          width="md"
-          name="toSpell"
-          label={intl.formatMessage({ id: "buttons.spell" })}
-        />
-      </ProForm.Group>
-      <ProForm.Group>
-        <ProFormSelect
-          options={["gender", "number", "case"].map((item) => {
-            return {
-              value: item,
-              label: item,
-            };
-          })}
-          fieldProps={{
-            mode: "tags",
-          }}
-          width="md"
-          name="match"
-          allowClear={false}
-          label={intl.formatMessage({ id: "forms.fields.match.label" })}
-        />
-        <ProFormText
-          width="md"
-          name="category"
-          label={intl.formatMessage({ id: "forms.fields.category.label" })}
-        />
-      </ProForm.Group>
-    </ModalForm>
-  );
-};
-
-export default RelationEditWidget;

+ 0 - 80
dashboard-v6/backup/components/ai/AiAssistantSelect.tsx

@@ -1,80 +0,0 @@
-import {
-  ProFormSelect,
-  type RequestOptionsType,
-} from "@ant-design/pro-components";
-import { useIntl } from "react-intl";
-
-import { get } from "../../request";
-import type { IUserListResponse } from "../../api/Auth";
-
-interface IWidget {
-  name?: string;
-  width?: number | "md" | "sm" | "xl" | "xs" | "lg";
-  multiple?: boolean;
-  hidden?: boolean;
-  hiddenTitle?: boolean;
-  required?: boolean;
-  initialValue?: string | string[] | null;
-  options?: RequestOptionsType[];
-}
-const UserSelectWidget = ({
-  name = "user",
-  multiple = false,
-  width = "md",
-  hidden = false,
-  hiddenTitle = false,
-  required = true,
-  options = [],
-  initialValue,
-}: IWidget) => {
-  const intl = useIntl();
-  console.log("UserSelect options", options);
-  return (
-    <ProFormSelect
-      name={name}
-      label={
-        hiddenTitle
-          ? undefined
-          : intl.formatMessage({ id: "labels.ai-assistant" })
-      }
-      hidden={hidden}
-      width={width}
-      initialValue={initialValue}
-      showSearch
-      debounceTime={300}
-      fieldProps={{
-        mode: multiple ? "tags" : undefined,
-      }}
-      request={async ({ keyWords }) => {
-        console.log("keyWord", keyWords);
-
-        if (typeof keyWords === "string") {
-          const url = `/v2/ai-assistant?keyword=${keyWords}`;
-          console.info("ai assistant api request", url);
-          const json = await get<IUserListResponse>(url);
-          console.info("ai assistant api response ", json);
-          const userList: RequestOptionsType[] = json.data.rows.map((item) => {
-            return {
-              value: item.id,
-              label: `${item.nickName}`,
-            };
-          });
-          console.log("json", userList);
-          return userList;
-        } else {
-          const defaultOptions: RequestOptionsType[] = options.map((item) => {
-            return { label: item.label, value: item.value?.toString() };
-          });
-          return defaultOptions;
-        }
-      }}
-      rules={[
-        {
-          required: required,
-        },
-      ]}
-    />
-  );
-};
-
-export default UserSelectWidget;

+ 0 - 69
dashboard-v6/backup/components/ai/AiModelCreate.tsx

@@ -1,69 +0,0 @@
-import { useIntl } from "react-intl";
-import {
-  ProForm,
-  type ProFormInstance,
-  ProFormText,
-} from "@ant-design/pro-components";
-import { message } from "antd";
-import { post } from "../../request";
-import { useRef } from "react";
-import type { IAiModelRequest, IAiModelResponse } from "../../api/ai";
-
-interface IWidget {
-  studioName?: string;
-  onCreate?: Function;
-}
-const AiModelCreate = ({ studioName, onCreate }: IWidget) => {
-  const intl = useIntl();
-  const formRef = useRef<ProFormInstance | undefined>(undefined);
-
-  return (
-    <ProForm<IAiModelRequest>
-      formRef={formRef}
-      onFinish={async (values: IAiModelRequest) => {
-        if (typeof studioName === "undefined") {
-          return;
-        }
-        const url = `/v2/ai-model`;
-        console.info("api request", url, values);
-        const res = await post<IAiModelRequest, IAiModelResponse>(url, values);
-        console.info("api response", res);
-        if (res.ok) {
-          message.success(intl.formatMessage({ id: "flashes.success" }));
-          if (typeof onCreate !== "undefined") {
-            onCreate();
-            formRef.current?.resetFields();
-          }
-        } else {
-          message.error(res.message);
-        }
-      }}
-    >
-      <ProForm.Group>
-        <ProFormText
-          width="md"
-          name="name"
-          required
-          label={intl.formatMessage({ id: "channel.name" })}
-          rules={[
-            {
-              required: true,
-              message: intl.formatMessage({
-                id: "channel.create.message.noname",
-              }),
-            },
-          ]}
-        />
-        <ProFormText
-          width="md"
-          name="studio_name"
-          initialValue={studioName}
-          hidden
-          required
-        />
-      </ProForm.Group>
-    </ProForm>
-  );
-};
-
-export default AiModelCreate;

+ 0 - 114
dashboard-v6/backup/components/ai/AiModelEdit.tsx

@@ -1,114 +0,0 @@
-import { useIntl } from "react-intl";
-import {
-  ProForm,
-  type ProFormInstance,
-  ProFormText,
-  ProFormTextArea,
-} from "@ant-design/pro-components";
-import { message } from "antd";
-import { get, put } from "../../request";
-import { useRef } from "react";
-import type { IAiModelRequest, IAiModelResponse } from "../../api/ai";
-import Publicity from "../studio/Publicity";
-
-interface IWidget {
-  studioName?: string;
-  modelId?: string;
-  onChange?: Function;
-}
-const AiModelEdit = ({ studioName, modelId, onChange }: IWidget) => {
-  const intl = useIntl();
-  const formRef = useRef<ProFormInstance | undefined>(undefined);
-
-  return (
-    <ProForm<IAiModelRequest>
-      formRef={formRef}
-      onFinish={async (values: IAiModelRequest) => {
-        if (typeof studioName === "undefined") {
-          return;
-        }
-        const url = `/v2/ai-model/${modelId}`;
-        console.info("api request", url, values);
-        const res = await put<IAiModelRequest, IAiModelResponse>(url, values);
-        console.info("api response", res);
-        if (res.ok) {
-          message.success(intl.formatMessage({ id: "flashes.success" }));
-          onChange && onChange();
-        } else {
-          message.error(res.message);
-        }
-      }}
-      request={async () => {
-        const url = `/v2/ai-model/${modelId}`;
-        console.info("api request", url);
-        const res = await get<IAiModelResponse>(url);
-        console.info("api response", res);
-        return res.data;
-      }}
-    >
-      <ProForm.Group>
-        <ProFormText
-          width="md"
-          name="name"
-          required
-          label={intl.formatMessage({ id: "forms.fields.name.label" })}
-          rules={[
-            {
-              required: true,
-              message: intl.formatMessage({
-                id: "channel.create.message.noname",
-              }),
-            },
-          ]}
-        />
-        <ProFormText
-          width="md"
-          name="studio_name"
-          initialValue={studioName}
-          hidden
-          required
-        />
-      </ProForm.Group>
-      <ProForm.Group>
-        <ProFormText
-          width="md"
-          name="url"
-          label={intl.formatMessage({ id: "forms.fields.url.label" })}
-        />
-      </ProForm.Group>
-      <ProForm.Group>
-        <ProFormText
-          width="md"
-          name="model"
-          label={intl.formatMessage({ id: "forms.fields.model.label" })}
-        />
-      </ProForm.Group>
-      <ProForm.Group>
-        <ProFormText
-          width="md"
-          name="key"
-          label={intl.formatMessage({ id: "forms.fields.key.label" })}
-        />
-      </ProForm.Group>
-      <ProForm.Group>
-        <Publicity name="privacy" disable={["public_no_list", "blocked"]} />
-      </ProForm.Group>
-      <ProForm.Group>
-        <ProFormTextArea
-          width="md"
-          name="description"
-          label={intl.formatMessage({ id: "forms.fields.description.label" })}
-        />
-      </ProForm.Group>
-      <ProForm.Group>
-        <ProFormTextArea
-          width="md"
-          name="system_prompt"
-          label={"system_prompt"}
-        />
-      </ProForm.Group>
-    </ProForm>
-  );
-};
-
-export default AiModelEdit;

+ 0 - 150
dashboard-v6/backup/components/ai/AiModelList.tsx

@@ -1,150 +0,0 @@
-import { Link } from "react-router";
-import { useIntl } from "react-intl";
-import { Button, Popover, Tag, Space } from "antd";
-import { type ActionType, ProList } from "@ant-design/pro-components";
-import { PlusOutlined } from "@ant-design/icons";
-
-import { get } from "../../request";
-
-import { useRef, useState } from "react";
-
-import { getSorterUrl } from "../../utils";
-import type { IAiModel, IAiModelListResponse } from "../../api/ai";
-import AiModelCreate from "./AiModelCreate";
-import PublicityIcon from "../studio/PublicityIcon";
-import ShareModal from "../share/ShareModal";
-import { EResType } from "../share/Share";
-import User from "../auth/User";
-
-interface IWidget {
-  studioName?: string;
-}
-const AiModelList = ({ studioName }: IWidget) => {
-  const intl = useIntl(); //i18n
-
-  const [openCreate, setOpenCreate] = useState(false);
-
-  const ref = useRef<ActionType | null>(null);
-
-  return (
-    <>
-      <ProList<IAiModel>
-        actionRef={ref}
-        onRow={(_record) => ({
-          onClick: () => {},
-        })}
-        metas={{
-          title: {
-            dataIndex: "name",
-            render(_dom, entity, _index, _action, _schema) {
-              return (
-                <Space>
-                  <PublicityIcon value={entity.privacy} />
-                  <Link
-                    to={`/studio/${studioName}/ai/models/${entity.uid}/edit`}
-                  >
-                    {entity.name}
-                  </Link>
-                </Space>
-              );
-            },
-          },
-          description: {
-            dataIndex: "url",
-          },
-          subTitle: {
-            render(_dom, entity, _index, _action, _schema) {
-              return <Tag>{entity.model}</Tag>;
-            },
-          },
-          content: {
-            render(_dom, entity, _index, _action, _schema) {
-              return entity.description;
-            },
-          },
-          avatar: {
-            render(_dom, entity, _index, _action, _schema) {
-              return <User {...entity.user} showName={false} />;
-            },
-          },
-          actions: {
-            render(_dom, entity, _index, _action, _schema) {
-              return (
-                <Space>
-                  <Link
-                    to={`/studio/${studioName}/ai/models/${entity.uid}/logs`}
-                  >
-                    logs
-                  </Link>
-                  <ShareModal
-                    trigger={
-                      <Button type="link" size="small">
-                        {intl.formatMessage({
-                          id: "buttons.share",
-                        })}
-                      </Button>
-                    }
-                    resId={entity.uid}
-                    resType={EResType.modal}
-                  />
-                </Space>
-              );
-            },
-          },
-        }}
-        request={async (params = {}, sorter, filter) => {
-          console.log(params, sorter, filter);
-          let url = `/v2/ai-model?view=studio&name=${studioName}`;
-          const offset = ((params.current ?? 1) - 1) * (params.pageSize ?? 20);
-          url += `&limit=${params.pageSize}&offset=${offset}`;
-          url += params.keyword ? "&search=" + params.keyword : "";
-          url += getSorterUrl(sorter);
-
-          console.info("api request", url);
-          const res = await get<IAiModelListResponse>(url);
-          console.info("api response", res);
-          return {
-            total: res.data.total,
-            succcess: res.ok,
-            data: res.data.rows,
-          };
-        }}
-        rowKey="id"
-        bordered
-        pagination={{
-          showQuickJumper: true,
-          showSizeChanger: true,
-        }}
-        search={false}
-        options={{
-          search: true,
-        }}
-        toolBarRender={() => [
-          <Popover
-            content={
-              <AiModelCreate
-                studioName={studioName}
-                onCreate={() => {
-                  setOpenCreate(false);
-                  ref.current?.reload();
-                }}
-              />
-            }
-            placement="bottomRight"
-            trigger="click"
-            open={openCreate}
-            onOpenChange={(open: boolean) => {
-              setOpenCreate(open);
-            }}
-          >
-            <Button key="button" icon={<PlusOutlined />} type="primary">
-              {intl.formatMessage({ id: "buttons.create" })}
-            </Button>
-          </Popover>,
-        ]}
-      />
-    </>
-  );
-};
-
-export default AiModelList;

+ 0 - 154
dashboard-v6/backup/components/ai/AiModelLogList.tsx

@@ -1,154 +0,0 @@
-import { ProList } from "@ant-design/pro-components";
-import { Space, Tabs, Tag, Typography } from "antd";
-import type { Key } from "react";
-import { useState } from "react";
-
-import { CheckOutlined, WarningOutlined } from "@ant-design/icons";
-
-import type { IAiModelLogData, IAiModelLogListResponse } from "../../api/ai";
-import { get } from "../../request";
-import moment from "moment";
-
-const { Text } = Typography;
-
-interface IWidget {
-  modelId?: string;
-}
-const AiModelLogList = ({ modelId }: IWidget) => {
-  const [expandedRowKeys, setExpandedRowKeys] = useState<readonly Key[]>([]);
-
-  return (
-    <ProList<IAiModelLogData>
-      rowKey="title"
-      headerTitle="logs"
-      expandable={{ expandedRowKeys, onExpandedRowsChange: setExpandedRowKeys }}
-      metas={{
-        title: {},
-        subTitle: {
-          render: (_dom, entity, _index, _action, _schema) => {
-            return (
-              <Space size={0}>
-                <Tag color="blue">{entity.status}</Tag>
-              </Space>
-            );
-          },
-        },
-        description: {
-          render: (_dom, entity, _index, _action, _schema) => {
-            const jsonView = (text?: string | null) => {
-              return (
-                <div>
-                  <pre>
-                    {text ? JSON.stringify(JSON.parse(text), null, 2) : ""}
-                  </pre>
-                </div>
-              );
-            };
-            const info = (headers: string, data: string) => {
-              return (
-                <div>
-                  <Text strong>Headers</Text>
-                  <div
-                    style={{
-                      backgroundColor: "rgb(246, 248, 250)",
-                      border: "1px solid gray",
-                      padding: 6,
-                    }}
-                  >
-                    {jsonView(headers)}
-                  </div>
-                  <Text strong>Payload</Text>
-                  <div
-                    style={{
-                      backgroundColor: "rgb(246, 248, 250)",
-                      border: "1px solid gray",
-                      padding: 6,
-                    }}
-                  >
-                    {jsonView(data)}
-                  </div>
-                </div>
-              );
-            };
-            return (
-              <>
-                <Tabs
-                  items={[
-                    {
-                      label: "request",
-                      key: "request",
-                      children: (
-                        <div>
-                          {info(entity.request_headers, entity.request_data)}
-                        </div>
-                      ),
-                    },
-                    {
-                      label: "response",
-                      key: "response",
-                      children: (
-                        <div>
-                          {info(
-                            entity.response_headers ?? "",
-                            entity.response_data ?? ""
-                          )}
-                        </div>
-                      ),
-                    },
-                  ]}
-                />
-              </>
-            );
-          },
-        },
-        avatar: {
-          render(_dom, entity, _index, _action, _schema) {
-            return (
-              <>
-                {entity.success ? (
-                  <CheckOutlined style={{ color: "green" }} />
-                ) : (
-                  <WarningOutlined color="error" />
-                )}
-              </>
-            );
-          },
-        },
-        actions: {
-          render: (_dom, entity, _index, _action, _schema) => {
-            const date = moment(entity.created_at).toLocaleString();
-            return <Text type="secondary">{date}</Text>;
-          },
-        },
-      }}
-      bordered
-      pagination={{
-        showQuickJumper: true,
-        showSizeChanger: true,
-        pageSize: 20,
-      }}
-      search={false}
-      options={{
-        search: true,
-      }}
-      request={async (params = {}, sorter, filter) => {
-        console.log(params, sorter, filter);
-        let url = `/v2/model-log?view=model&id=${modelId}`;
-        const offset = ((params.current ?? 1) - 1) * (params.pageSize ?? 20);
-        url += `&limit=${params.pageSize}&offset=${offset}`;
-        url += params.keyword ? "&search=" + params.keyword : "";
-
-        console.info("ai model log api request", url);
-        const res = await get<IAiModelLogListResponse>(url);
-        console.info("ai model log api response", res);
-        return {
-          total: res.data.total,
-          succcess: res.ok,
-          data: res.data.rows,
-        };
-      }}
-    />
-  );
-};
-
-export default AiModelLogList;

+ 0 - 78
dashboard-v6/backup/components/ai/AiTranslate.tsx

@@ -1,78 +0,0 @@
-import { Button, Typography } from "antd";
-import { useEffect, useState } from "react";
-import { LoadingOutlined } from "@ant-design/icons";
-
-import Marked from "../general/Marked";
-import { get } from "../../request";
-import type { IAiTranslateResponse } from "../../api/ai";
-
-const { Text } = Typography;
-
-interface IAiTranslateWidget {
-  origin?: string;
-  paragraph?: string;
-  autoLoad?: boolean;
-  trigger?: boolean;
-}
-
-const AiTranslate = ({
-  paragraph,
-  autoLoad = false,
-  trigger = false,
-}: IAiTranslateWidget) => {
-  const [loading, setLoading] = useState(false);
-  const [translation, setTranslation] = useState<string>();
-  const [error, setError] = useState<string>();
-  const url = "/v2/ai-translate";
-
-  useEffect(() => {
-    if (typeof paragraph === "undefined") {
-      return;
-    }
-    if (!autoLoad) {
-      return;
-    }
-    //onTranslatePara();
-  }, [paragraph, autoLoad]);
-
-  const onTranslatePara = () => {
-    const _url = `${url}/${paragraph}`;
-    console.info("api request", _url);
-    setLoading(true);
-    get<IAiTranslateResponse>(_url)
-      .then((json) => {
-        console.debug("api response", json);
-        if (json.ok) {
-          setTranslation(json.data.choices[0].message.content);
-        } else {
-          setError(json.message);
-        }
-      })
-      .finally(() => setLoading(false));
-  };
-
-  if (translation) {
-    return <Marked text={translation} />;
-  } else if (loading) {
-    return <LoadingOutlined />;
-  } else if (error) {
-    return (
-      <div>
-        <Text type="danger">{error}</Text>
-        <Button type="link" onClick={() => onTranslatePara()}>
-          再试一次
-        </Button>
-      </div>
-    );
-  } else if (trigger) {
-    return (
-      <Button type="link" onClick={() => onTranslatePara()}>
-        AI 翻译
-      </Button>
-    );
-  } else {
-    return <></>;
-  }
-};
-
-export default AiTranslate;

+ 0 - 145
dashboard-v6/backup/components/ai/ModelSelector.tsx

@@ -1,145 +0,0 @@
-import { Button, Dropdown, Typography, Tag } from "antd";
-import {
-  DownOutlined,
-  ReloadOutlined,
-  GlobalOutlined,
-} from "@ant-design/icons";
-import type { MenuProps } from "antd";
-
-const { Text } = Typography;
-
-const ModelSelector = () => {
-  const modelItems: MenuProps["items"] = [
-    {
-      key: "auto",
-      label: (
-        <div className="py-2">
-          <div className="font-medium text-gray-900">Auto</div>
-        </div>
-      ),
-    },
-    {
-      key: "gpt-4o",
-      label: (
-        <div className="py-2">
-          <div className="font-medium text-gray-900">
-            GPT-4o<Tag>翻译</Tag>
-          </div>
-          <Text type="secondary">适用于大多数任务</Text>
-        </div>
-      ),
-    },
-    {
-      key: "o4-mini",
-      label: (
-        <div className="py-2">
-          <div className="font-medium text-gray-900">o4-mini</div>
-          <Text type="secondary">快速进行高级推理</Text>
-        </div>
-      ),
-    },
-    {
-      key: "gpt-4.1-mini",
-      label: (
-        <div className="py-2">
-          <div className="font-medium text-gray-900">GPT-4.1-mini</div>
-          <Text type="secondary">适合处理日常任务</Text>
-        </div>
-      ),
-    },
-    {
-      type: "divider",
-    },
-    {
-      key: "refresh",
-      label: (
-        <div className="py-2 flex items-center space-x-2 text-gray-700">
-          <ReloadOutlined />
-          <span>重试</span>
-          <div className="ml-auto">
-            <Text type="secondary">GPT-4o</Text>
-          </div>
-        </div>
-      ),
-    },
-    {
-      key: "search",
-      label: (
-        <div className="py-2 flex items-center space-x-2 text-gray-700">
-          <GlobalOutlined />
-          <span>搜索网页</span>
-        </div>
-      ),
-    },
-  ];
-
-  const handleMenuClick: MenuProps["onClick"] = ({ key }) => {
-    if (key === "refresh") {
-      console.log("重试操作");
-      return;
-    }
-    if (key === "search") {
-      console.log("搜索网页");
-      return;
-    }
-  };
-
-  return (
-    <div className="bg-gray-50 min-h-screen">
-      <div className="max-w-md mx-auto">
-        <Dropdown
-          menu={{
-            items: modelItems,
-            onClick: handleMenuClick,
-            className: "w-64",
-          }}
-          trigger={["click"]}
-          placement="bottomLeft"
-          overlayClassName="model-selector-dropdown"
-        >
-          <Button
-            className="flex items-center justify-between w-48 h-12 px-4 border border-gray-300 rounded-lg bg-white hover:bg-gray-50 shadow-sm"
-            type="text"
-          >
-            <div className="flex items-center space-x-2">
-              <span className="font-medium text-gray-900 model_name">
-                {"AI"}
-              </span>
-              <DownOutlined className="text-gray-500 text-sm" />
-            </div>
-          </Button>
-        </Dropdown>
-      </div>
-
-      <style>{`
-        .model-selector-dropdown .ant-dropdown-menu {
-          padding: 8px;
-          border-radius: 12px;
-          box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
-          border: 1px solid #e5e7eb;
-        }
-
-        .model-selector-dropdown .ant-dropdown-menu-item {
-          padding: 0;
-          margin: 2px 0;
-          border-radius: 8px;
-        }
-
-        .model-selector-dropdown .ant-dropdown-menu-item:hover {
-          background-color: #f3f4f6;
-        }
-
-        .model-selector-dropdown .ant-dropdown-menu-item-selected {
-          background-color: #eff6ff;
-        }
-
-        .model-selector-dropdown .ant-dropdown-menu-divider {
-          margin: 8px 0;
-          background-color: #e5e7eb;
-        }
-      `}</style>
-    </div>
-  );
-};
-
-export default ModelSelector;

+ 0 - 83
dashboard-v6/backup/components/anthology/AnthologyCreate.tsx

@@ -1,83 +0,0 @@
-import {
-  ProForm,
-  type ProFormInstance,
-  ProFormText,
-} from "@ant-design/pro-components";
-import { useIntl } from "react-intl";
-import { message } from "antd";
-
-import LangSelect from "../general/LangSelect";
-import type {
-  IAnthologyCreateRequest,
-  IAnthologyResponse,
-} from "../../api/Article";
-import { post } from "../../request";
-import { useRef } from "react";
-
-interface IFormData {
-  title: string;
-  lang: string;
-  studio: string;
-}
-
-interface IWidget {
-  studio?: string;
-  onSuccess?: Function;
-}
-const AnthologyCreateWidget = ({ studio, onSuccess }: IWidget) => {
-  const intl = useIntl();
-  const formRef = useRef<ProFormInstance | undefined>(undefined);
-
-  return (
-    <ProForm<IFormData>
-      formRef={formRef}
-      onFinish={async (values: IFormData) => {
-        if (typeof studio === "undefined") {
-          return;
-        }
-        values.studio = studio;
-        const url = `/v2/anthology`;
-        console.info("api request", url, values);
-        const res = await post<IAnthologyCreateRequest, IAnthologyResponse>(
-          url,
-          values
-        );
-        console.debug("api response", res);
-        if (res.ok) {
-          message.success(intl.formatMessage({ id: "flashes.success" }));
-          if (typeof onSuccess !== "undefined") {
-            onSuccess();
-            formRef.current?.resetFields(["title"]);
-          }
-        } else {
-          message.error(res.message);
-        }
-      }}
-    >
-      <ProForm.Group>
-        <ProFormText
-          width="md"
-          name="title"
-          required
-          label={intl.formatMessage({
-            id: "forms.fields.title.label",
-          })}
-          rules={[
-            {
-              required: true,
-              message: intl.formatMessage({
-                id: "forms.message.title.required",
-              }),
-              max: 255,
-            },
-          ]}
-        />
-      </ProForm.Group>
-      <ProForm.Group>
-        <LangSelect />
-      </ProForm.Group>
-    </ProForm>
-  );
-};
-
-export default AnthologyCreateWidget;

+ 0 - 396
dashboard-v6/backup/components/anthology/AnthologyList.tsx

@@ -1,396 +0,0 @@
-import { type ActionType, ProTable } from "@ant-design/pro-components";
-import { useIntl } from "react-intl";
-import { Link } from "react-router";
-import { message, Modal, Typography } from "antd";
-import { PlusOutlined } from "@ant-design/icons";
-import { Button, Dropdown, Popover } from "antd";
-import {
-  ExclamationCircleOutlined,
-  TeamOutlined,
-  DeleteOutlined,
-  EyeOutlined,
-} from "@ant-design/icons";
-
-import AnthologyCreate from "./AnthologyCreate";
-import type {
-  IAnthologyListResponse,
-  IDeleteResponse,
-} from "../../api/Article";
-import { delete_, get } from "../../request";
-import { PublicityValueEnum } from "../studio/table";
-import { useEffect, useRef, useState } from "react";
-import Share, { EResType } from "../share/Share";
-
-import StudioName, { type IStudio } from "../auth/Studio";
-import { type IResNumberResponse, renderBadge } from "../channel/ChannelTable";
-import { fullUrl, getSorterUrl } from "../../utils";
-
-const { Text } = Typography;
-
-interface IItem {
-  sn: number;
-  id: string;
-  title: string;
-  subtitle: string;
-  publicity: number;
-  articles: number;
-  studio?: IStudio;
-  updated_at: string;
-}
-interface IWidget {
-  title?: string;
-  studioName?: string;
-  showCol?: string[];
-  showCreate?: boolean;
-  showOption?: boolean;
-  onTitleClick?: (id: string) => void;
-}
-const AnthologyListWidget = ({
-  title,
-  studioName,
-  showCreate = true,
-  showOption = true,
-  onTitleClick,
-}: IWidget) => {
-  const intl = useIntl();
-  const [openCreate, setOpenCreate] = useState(false);
-
-  const [activeKey, setActiveKey] = useState<React.Key | undefined>("my");
-  const [myNumber, setMyNumber] = useState<number>(0);
-  const [collaborationNumber, setCollaborationNumber] = useState<number>(0);
-
-  useEffect(() => {
-    /**
-     * 获取各种课程的数量
-     */
-    const url = `/v2/anthology-my-number?studio=${studioName}`;
-    console.log("url", url);
-    get<IResNumberResponse>(url).then((json) => {
-      if (json.ok) {
-        setMyNumber(json.data.my);
-        setCollaborationNumber(json.data.collaboration);
-      }
-    });
-  }, [studioName]);
-
-  const showDeleteConfirm = (id: string, title: string) => {
-    Modal.confirm({
-      icon: <ExclamationCircleOutlined />,
-      title:
-        intl.formatMessage({
-          id: "message.delete.confirm",
-        }) +
-        intl.formatMessage({
-          id: "message.irrevocable",
-        }),
-
-      content: title,
-      okText: intl.formatMessage({
-        id: "buttons.delete",
-      }),
-      okType: "danger",
-      cancelText: intl.formatMessage({
-        id: "buttons.no",
-      }),
-      onOk() {
-        console.log("delete", id);
-        return delete_<IDeleteResponse>(`/v2/anthology/${id}`)
-          .then((json) => {
-            if (json.ok) {
-              message.success("删除成功");
-              ref.current?.reload();
-            } else {
-              message.error(json.message);
-            }
-          })
-          .catch((e) => console.log("Oops errors!", e));
-      },
-    });
-  };
-
-  const [isModalOpen, setIsModalOpen] = useState(false);
-  const [shareResId, setShareResId] = useState<string>("");
-  const [shareResType, setShareResType] = useState<EResType>(
-    EResType.collection
-  );
-  const showShareModal = (resId: string, resType: EResType) => {
-    setShareResId(resId);
-    setShareResType(resType);
-    setIsModalOpen(true);
-  };
-
-  const handleOk = () => {
-    setIsModalOpen(false);
-  };
-
-  const handleCancel = () => {
-    setIsModalOpen(false);
-  };
-
-  const ref = useRef<ActionType | null>(null);
-  return (
-    <>
-      <ProTable<IItem>
-        headerTitle={title}
-        actionRef={ref}
-        columns={[
-          {
-            title: intl.formatMessage({
-              id: "dict.fields.sn.label",
-            }),
-            dataIndex: "sn",
-            key: "sn",
-            width: 50,
-            search: false,
-          },
-          {
-            title: intl.formatMessage({
-              id: "forms.fields.title.label",
-            }),
-            dataIndex: "title",
-            key: "title",
-            tooltip: "过长会自动收缩",
-            ellipsis: true,
-            render: (_text, row, index) => {
-              return (
-                <div key={index}>
-                  <div>
-                    <Typography.Link
-                      onClick={() => {
-                        if (typeof onTitleClick !== "undefined") {
-                          onTitleClick(row.id);
-                        }
-                      }}
-                    >
-                      {row.title}
-                    </Typography.Link>
-                  </div>
-                  <Text type="secondary">{row.subtitle}</Text>
-                </div>
-              );
-            },
-          },
-          {
-            title: intl.formatMessage({
-              id: "forms.fields.owner.label",
-            }),
-            dataIndex: "studio",
-            key: "studio",
-            render: (_text, row) => {
-              return <StudioName data={row.studio} />;
-            },
-          },
-          {
-            title: intl.formatMessage({
-              id: "forms.fields.publicity.label",
-            }),
-            dataIndex: "publicity",
-            key: "publicity",
-            width: 100,
-            search: false,
-            filters: true,
-            onFilter: true,
-            valueEnum: PublicityValueEnum(),
-          },
-          {
-            title: intl.formatMessage({
-              id: "article.fields.article.count.label",
-            }),
-            dataIndex: "articles",
-            key: "articles",
-            width: 100,
-            search: false,
-          },
-          {
-            title: intl.formatMessage({
-              id: "forms.fields.updated-at.label",
-            }),
-            key: "updated_at",
-            width: 100,
-            search: false,
-            dataIndex: "updated_at",
-            valueType: "date",
-            sorter: true,
-          },
-          {
-            title: intl.formatMessage({ id: "buttons.option" }),
-            key: "option",
-            width: 120,
-            hideInTable: !showOption,
-            valueType: "option",
-            render: (_text, row, index) => [
-              <Dropdown.Button
-                key={index}
-                type="link"
-                trigger={["click", "contextMenu"]}
-                menu={{
-                  items: [
-                    {
-                      key: "open",
-                      label: (
-                        <Link to={`/anthology/${row.id}`}>
-                          {intl.formatMessage({
-                            id: "buttons.open.in.library",
-                          })}
-                        </Link>
-                      ),
-                      icon: <EyeOutlined />,
-                    },
-                    {
-                      key: "share",
-                      label: intl.formatMessage({
-                        id: "buttons.share",
-                      }),
-                      icon: <TeamOutlined />,
-                    },
-                    {
-                      key: "remove",
-                      label: intl.formatMessage({
-                        id: "buttons.delete",
-                      }),
-                      icon: <DeleteOutlined />,
-                      danger: true,
-                    },
-                  ],
-                  onClick: (e) => {
-                    switch (e.key) {
-                      case "open":
-                        window.open(fullUrl(`/anthology/${row.id}`), "_blank");
-                        break;
-                      case "share":
-                        console.log("share");
-                        showShareModal(row.id, EResType.collection);
-                        break;
-                      case "remove":
-                        showDeleteConfirm(row.id, row.title);
-                        break;
-                    }
-                  },
-                }}
-              >
-                <Link to={`/anthology/${row.id}`} target="_blank">
-                  {intl.formatMessage({
-                    id: "buttons.view",
-                  })}
-                </Link>
-              </Dropdown.Button>,
-            ],
-          },
-        ]}
-        request={async (params = {}, sorter, filter) => {
-          console.log(params, sorter, filter);
-          let url = `/v2/anthology?view=studio&view2=${activeKey}&name=${studioName}`;
-          const offset =
-            ((params.current ? params.current : 1) - 1) *
-            (params.pageSize ? params.pageSize : 20);
-          url += `&limit=${params.pageSize}&offset=${offset}`;
-          url += params.keyword ? "&search=" + params.keyword : "";
-
-          url += getSorterUrl(sorter);
-
-          const res = await get<IAnthologyListResponse>(url);
-          const items: IItem[] = res.data.rows.map((item, id) => {
-            return {
-              sn: id + offset + 1,
-              id: item.uid,
-              title: item.title,
-              subtitle: item.subtitle,
-              publicity: item.status,
-              articles: item.childrenNumber,
-              studio: item.studio,
-              updated_at: item.updated_at,
-            };
-          });
-          console.log(items);
-          return {
-            total: res.data.count,
-            succcess: true,
-            data: items,
-          };
-        }}
-        rowKey="id"
-        bordered
-        pagination={{
-          showQuickJumper: true,
-          showSizeChanger: true,
-          pageSize: 10,
-        }}
-        search={false}
-        options={{
-          search: true,
-        }}
-        toolBarRender={() => [
-          showCreate ? (
-            <Popover
-              content={
-                <AnthologyCreate
-                  studio={studioName}
-                  onSuccess={() => {
-                    setOpenCreate(false);
-                    ref.current?.reload();
-                  }}
-                />
-              }
-              placement="bottomRight"
-              trigger="click"
-              open={openCreate}
-              onOpenChange={(open: boolean) => {
-                setOpenCreate(open);
-              }}
-            >
-              <Button key="button" icon={<PlusOutlined />} type="primary">
-                {intl.formatMessage({ id: "buttons.create" })}
-              </Button>
-            </Popover>
-          ) : undefined,
-        ]}
-        toolbar={{
-          menu: {
-            activeKey,
-            items: [
-              {
-                key: "my",
-                label: (
-                  <span>
-                    {intl.formatMessage({ id: "labels.this-studio" })}
-                    {renderBadge(myNumber, activeKey === "my")}
-                  </span>
-                ),
-              },
-              {
-                key: "collaboration",
-                label: (
-                  <span>
-                    {intl.formatMessage({ id: "labels.collaboration" })}
-                    {renderBadge(
-                      collaborationNumber,
-                      activeKey === "collaboration"
-                    )}
-                  </span>
-                ),
-              },
-            ],
-            onChange(key) {
-              console.log("show course", key);
-              setActiveKey(key);
-              ref.current?.reload();
-            },
-          },
-        }}
-      />
-
-      <Modal
-        destroyOnHidden={true}
-        width={700}
-        title={intl.formatMessage({ id: "labels.collaboration" })}
-        open={isModalOpen}
-        onOk={handleOk}
-        onCancel={handleCancel}
-      >
-        <Share resId={shareResId} resType={shareResType} />
-      </Modal>
-    </>
-  );
-};
-
-export default AnthologyListWidget;

+ 0 - 67
dashboard-v6/backup/components/anthology/AnthologyModal.tsx

@@ -1,67 +0,0 @@
-import { useState } from "react";
-import { Modal } from "antd";
-import AnthologyList from "./AnthologyList";
-
-interface IWidget {
-  studioName?: string;
-  trigger?: React.ReactNode;
-  open?: boolean;
-  onClose?: (closed: boolean) => void;
-  onSelect?: (selected: string) => void;
-  onCancel?: () => void;
-}
-const AnthologyModalWidget = ({
-  studioName,
-  trigger,
-  open,
-  onClose,
-  onSelect,
-}: IWidget) => {
-  const [innerOpen, setInnerOpen] = useState(false);
-
-  const isModalOpen = open ?? innerOpen;
-
-  const openModal = () => {
-    if (open === undefined) {
-      setInnerOpen(true);
-    } else {
-      onClose?.(true);
-    }
-  };
-
-  const closeModal = () => {
-    onClose?.(false);
-    if (open === undefined) {
-      setInnerOpen(false);
-    }
-  };
-
-  return (
-    <>
-      <span role="button" tabIndex={0} onClick={openModal}>
-        {trigger}
-      </span>
-
-      <Modal
-        width="80%"
-        title="加入文集"
-        open={isModalOpen}
-        onOk={closeModal}
-        onCancel={closeModal}
-      >
-        <AnthologyList
-          title="选择文集"
-          studioName={studioName}
-          showCreate={false}
-          showOption={false}
-          onTitleClick={(id) => {
-            onSelect?.(id);
-            closeModal();
-          }}
-        />
-      </Modal>
-    </>
-  );
-};
-
-export default AnthologyModalWidget;

+ 0 - 52
dashboard-v6/backup/components/anthology/AnthologySelect.tsx

@@ -1,52 +0,0 @@
-import { Select } from "antd";
-import { useEffect, useState } from "react";
-import { get } from "../../request";
-import type { IAnthologyListResponse } from "../../api/Article";
-
-interface IOptions {
-  value: string;
-  label: string;
-}
-interface IWidget {
-  studioName?: string;
-  onSelect?: Function;
-}
-const AnthologyTocTreeWidget = ({ studioName, onSelect }: IWidget) => {
-  const [anthology, setAnthology] = useState<IOptions[]>([
-    { value: "all", label: "全部" },
-    { value: "none", label: "没有加入文集的" },
-  ]);
-  useEffect(() => {
-    const url = `/v2/anthology?view=studio&name=${studioName}`;
-    get<IAnthologyListResponse>(url).then((json) => {
-      if (json.ok) {
-        const data = json.data.rows.map((item) => {
-          return {
-            value: item.uid,
-            label: item.title,
-          };
-        });
-        setAnthology([
-          { value: "all", label: "全部" },
-          { value: "none", label: "没有加入文集的" },
-          ...data,
-        ]);
-      }
-    });
-  }, [studioName]);
-  return (
-    <Select
-      defaultValue="all"
-      style={{ width: 180 }}
-      onChange={(value: string) => {
-        console.log(`selected ${value}`);
-        if (typeof onSelect !== "undefined") {
-          onSelect(value);
-        }
-      }}
-      options={anthology}
-    />
-  );
-};
-
-export default AnthologyTocTreeWidget;

+ 0 - 87
dashboard-v6/backup/components/anthology/AnthologyTocTree.tsx

@@ -1,87 +0,0 @@
-import { useEffect, useState } from "react";
-
-import { get } from "../../request";
-import type { IArticleMapListResponse } from "../../api/Article";
-import type { ListNodeData } from "../article/EditableTree";
-import TocTree from "../article/TocTree";
-
-interface IWidget {
-  anthologyId?: string;
-  channels?: string[];
-  onClick?: (
-    anthologyId: string,
-    id: string,
-    target: "_blank" | "self"
-  ) => void;
-  onArticleSelect?: (anthologyId: string, keys: string[]) => void;
-}
-const AnthologyTocTreeWidget = ({
-  anthologyId,
-  channels,
-  onClick,
-  onArticleSelect,
-}: IWidget) => {
-  const [tocData, setTocData] = useState<ListNodeData[]>([]);
-  const [expandedKeys, setExpandedKeys] = useState<string[]>();
-
-  useEffect(() => {
-    if (typeof anthologyId === "undefined") {
-      return;
-    }
-    let url = `/v2/article-map?view=anthology&id=${anthologyId}&lazy=1`;
-    url += channels && channels.length > 0 ? "&channel=" + channels[0] : "";
-    console.log("url", url);
-    get<IArticleMapListResponse>(url).then((json) => {
-      if (json.ok) {
-        const toc: ListNodeData[] = json.data.rows.map((item) => {
-          return {
-            key: item.article_id ? item.article_id : item.title,
-            title: item.title_text ? item.title_text : item.title,
-            level: item.level,
-            children: item.children,
-            status: item.status,
-            deletedAt: item.deleted_at,
-          };
-        });
-        setTocData(toc);
-        if (json.data.rows.length === json.data.count) {
-          setExpandedKeys(
-            json.data.rows
-              .filter((value) => value.level === 1)
-              .map((item) => (item.article_id ? item.article_id : item.title))
-          );
-        } else {
-          setExpandedKeys(undefined);
-        }
-      }
-    });
-  }, [anthologyId, channels]);
-  return (
-    <TocTree
-      treeData={tocData}
-      expandedKeys={expandedKeys}
-      onSelect={(keys: string[]) => {
-        if (
-          typeof onArticleSelect !== "undefined" &&
-          typeof anthologyId !== "undefined"
-        ) {
-          onArticleSelect(anthologyId, keys);
-        }
-      }}
-      onClick={(
-        id: string,
-        e: React.MouseEvent<HTMLSpanElement, MouseEvent>
-      ) => {
-        const target = e.ctrlKey || e.metaKey ? "_blank" : "self";
-        if (
-          typeof onClick !== "undefined" &&
-          typeof anthologyId !== "undefined"
-        ) {
-          onClick(anthologyId, id, target);
-        }
-      }}
-    />
-  );
-};
-
-export default AnthologyTocTreeWidget;

+ 0 - 215
dashboard-v6/backup/components/anthology/EditableTocTree.tsx

@@ -1,215 +0,0 @@
-import { Button, message } from "antd";
-import { useEffect, useState } from "react";
-import { FolderOpenOutlined } from "@ant-design/icons";
-
-import { get as getUiLang } from "../../locales";
-
-import { get, post, put } from "../../request";
-import type {
-  IAnthologyDataResponse,
-  IArticleCreateRequest,
-  IArticleDataResponse,
-  IArticleMapAddResponse,
-  IArticleMapListResponse,
-  IArticleMapRequest,
-  IArticleMapUpdateRequest,
-  IArticleResponse,
-} from "../../api/Article";
-import ArticleListModal from "../article/ArticleListModal";
-import EditableTree, {
-  type ListNodeData,
-  type TreeNodeData,
-} from "../article/EditableTree";
-import ArticleDrawer from "../article/ArticleDrawer";
-import { fullUrl, randomString } from "../../utils";
-
-interface IWidget {
-  anthologyId?: string;
-  studioName?: string;
-  myStudioName?: string;
-  anthology?: IAnthologyDataResponse;
-}
-const EditableTocTreeWidget = ({
-  anthologyId,
-  anthology,
-  studioName,
-  myStudioName,
-}: IWidget) => {
-  const [tocData, setTocData] = useState<ListNodeData[]>([]);
-  const [addArticle, setAddArticle] = useState<TreeNodeData>();
-  const [updatedArticle, setUpdatedArticle] = useState<TreeNodeData>();
-  const [openViewer, setOpenViewer] = useState(false);
-  const [viewArticle, setViewArticle] = useState<TreeNodeData>();
-
-  const save = (data?: ListNodeData[]) => {
-    console.debug("onSave", data);
-    if (typeof data === "undefined") {
-      console.warn("data === undefined");
-      return;
-    }
-    const url = `/v2/article-map/${anthologyId}`;
-    console.info("url", url);
-    const newData: IArticleMapRequest[] = data.map((item) => {
-      let title = "";
-      if (typeof item.title === "string") {
-        title = item.title;
-      }
-      //TODO 整一个string title
-      return {
-        article_id: item.key,
-        level: item.level,
-        title: title,
-        children: item.children,
-        status: item.status,
-        deleted_at: item.deletedAt,
-      };
-    });
-
-    put<IArticleMapUpdateRequest, IArticleMapAddResponse>(url, {
-      data: newData,
-      operation: "anthology",
-    })
-      .finally(() => {})
-      .then((json) => {
-        if (json.ok) {
-          message.success(json.data);
-        } else {
-          message.error(json.message);
-        }
-      })
-      .catch((e) => message.error(e));
-  };
-
-  useEffect(() => {
-    get<IArticleMapListResponse>(
-      `/v2/article-map?view=anthology&id=${anthologyId}`
-    ).then((json) => {
-      if (json.ok) {
-        const toc: ListNodeData[] = json.data.rows.map((item) => {
-          return {
-            key: item.article_id ? item.article_id : item.title,
-            title: item.title,
-            title_text: item.title_text ? item.title_text : item.title,
-            level: item.level,
-            status: item.status,
-            deletedAt: item.deleted_at,
-          };
-        });
-        setTocData(toc);
-      }
-    });
-  }, [anthologyId]);
-
-  return (
-    <div>
-      <EditableTree
-        treeData={tocData}
-        addOnArticle={addArticle}
-        addFileButton={
-          <ArticleListModal
-            studioName={myStudioName}
-            trigger={<Button icon={<FolderOpenOutlined />}>添加</Button>}
-            multiple={false}
-            onSelect={(id: string, title: string) => {
-              console.log("add article", id);
-              const newNode: TreeNodeData = {
-                key: randomString(),
-                id: id,
-                title: title,
-                title_text: title,
-                children: [],
-                level: 1,
-              };
-              setAddArticle(newNode);
-            }}
-          />
-        }
-        updatedNode={updatedArticle}
-        onChange={(data: ListNodeData[]) => {
-          save(data);
-        }}
-        onSave={(data: ListNodeData[]) => {
-          save(data);
-        }}
-        onAppend={async (
-          node: TreeNodeData
-        ): Promise<TreeNodeData | undefined> => {
-          /**
-           * 在某节点下append新的节点
-           */
-          if (typeof studioName === "undefined") {
-            console.log("studio", studioName);
-            return;
-          }
-          const res = await post<IArticleCreateRequest, IArticleResponse>(
-            `/v2/article`,
-            {
-              title: "new article",
-              lang: anthology?.lang ?? getUiLang(),
-              studio: studioName,
-              anthologyId: anthologyId,
-              status: anthology?.status ?? undefined,
-            }
-          );
-
-          console.log(res);
-          if (res.ok) {
-            return {
-              key: randomString(),
-              id: res.data.uid,
-              title: res.data.title,
-              title_text: res.data.title,
-              children: [],
-              level: node.level + 1,
-            };
-          } else {
-            return;
-          }
-        }}
-        onTitleClick={(
-          e: React.MouseEvent<HTMLElement, MouseEvent>,
-          node: TreeNodeData
-        ) => {
-          if (e.ctrlKey || e.metaKey) {
-            window.open(fullUrl(`/article/article/${node.id}`), "_blank");
-          } else {
-            setViewArticle(node);
-            setOpenViewer(true);
-          }
-        }}
-      />
-      <ArticleDrawer
-        articleId={viewArticle?.id}
-        anthologyId={anthologyId}
-        type="article"
-        open={openViewer}
-        title={viewArticle?.title_text}
-        onClose={() => setOpenViewer(false)}
-        onArticleEdit={(value: IArticleDataResponse) => {
-          setUpdatedArticle({
-            key: randomString(),
-            id: value.uid,
-            title: value.title,
-            title_text: value.title_text,
-            level: 0,
-            children: [],
-          });
-        }}
-        onTitleChange={(value: string) => {
-          if (viewArticle?.id) {
-            setUpdatedArticle({
-              key: randomString(),
-              id: viewArticle?.id,
-              title: value,
-              title_text: value,
-              level: 0,
-              children: [],
-            });
-          }
-        }}
-      />
-    </div>
-  );
-};
-
-export default EditableTocTreeWidget;

+ 0 - 42
dashboard-v6/backup/components/anthology/TextBookToc.tsx

@@ -1,42 +0,0 @@
-import { useEffect, useState } from "react";
-import AnthologyTocTree from "./AnthologyTocTree";
-import { get } from "../../request";
-import type { ICourseResponse } from "../../api/Course";
-
-interface IWidget {
-  courseId?: string | null;
-  channels?: string[];
-  onClick?: (article: string, target: string) => void;
-}
-const TextBookTocWidget = ({ courseId, channels, onClick }: IWidget) => {
-  const [anthologyId, setAnthologyId] = useState<string>();
-
-  useEffect(() => {
-    if (!courseId) {
-      return;
-    }
-    const url = `/v2/course/${courseId}`;
-    console.debug("course url", url);
-    get<ICourseResponse>(url).then((json) => {
-      console.debug("course data", json.data);
-      if (json.ok) {
-        setAnthologyId(json.data.anthology_id);
-      }
-    });
-  }, [courseId]);
-
-  return (
-    <AnthologyTocTree
-      anthologyId={anthologyId}
-      channels={channels}
-      onClick={(_anthology: string, article: string, target: string) => {
-        console.debug("AnthologyTocTree onClick", article);
-        if (typeof onClick !== "undefined") {
-          onClick(article, target);
-        }
-      }}
-    />
-  );
-};
-
-export default TextBookTocWidget;

+ 0 - 246
dashboard-v6/backup/components/api/Article.ts

@@ -1,246 +0,0 @@
-import type { IStudio } from "../auth/Studio"
-import type { IUser } from "../auth/User"
-import type { IChannel } from "../channel/Channel"
-import type { ITocPathNode } from "../corpus/TocPath"
-import type { IStudioApiResponse, TRole } from "./Auth";
-
-export interface IArticleListApiResponse {
-  article: string;
-  title: string;
-  level: string;
-  children: number;
-}
-export interface IAnthologyDataRequest {
-  title: string;
-  subtitle: string;
-  summary?: string;
-  article_list?: IArticleListApiResponse[];
-  lang: string;
-  status: number;
-  default_channel?: string | null;
-}
-export interface IAnthologyDataResponse {
-  uid: string;
-  title: string;
-  subtitle: string;
-  summary: string;
-  article_list: IArticleListApiResponse[];
-  studio: IStudio;
-  default_channel?: IChannel;
-  lang: string;
-  status: number;
-  childrenNumber: number;
-  created_at: string;
-  updated_at: string;
-}
-export interface IAnthologyResponse {
-  ok: boolean;
-  message: string;
-  data: IAnthologyDataResponse;
-}
-export interface IAnthologyListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: IAnthologyDataResponse[];
-    count: number;
-  };
-}
-
-export interface IAnthologyStudioListApiResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    count: number;
-    rows: IAnthologyStudioListDataApiResponse[];
-  };
-}
-export interface IAnthologyStudioListDataApiResponse {
-  count: number;
-  studio: IStudioApiResponse;
-}
-
-export interface IArticleDataRequest {
-  uid: string;
-  title: string;
-  subtitle: string;
-  summary?: string | null;
-  content?: string;
-  content_type?: string;
-  status: number;
-  lang: string;
-  to_tpl?: boolean;
-  anthology_id?: string;
-}
-export interface IChapterToc {
-  key?: string;
-  book: number;
-  paragraph: number;
-  level: number;
-  pali_title: string /**巴利文标题 */;
-  title?: string /**译文文标题 */;
-  progress?: number[];
-}
-export interface IArticleDataResponse {
-  uid: string;
-  title: string;
-  title_text?: string;
-  subtitle: string;
-  summary: string | null;
-  _summary?: string;
-  content?: string;
-  content_type?: string;
-  toc?: IChapterToc[];
-  html?: string;
-  path?: ITocPathNode[];
-  status: number;
-  lang: string;
-  anthology_count?: number;
-  anthology_first?: { uid: string; title: string };
-  role?: TRole;
-  studio?: IStudio;
-  editor?: IUser;
-  created_at: string;
-  updated_at: string;
-  from?: number;
-  to?: number;
-  mode?: string;
-  paraId?: string;
-  parent_uid?: string;
-  channels?: string;
-}
-export interface IArticleResponse {
-  ok: boolean;
-  message: string;
-  data: IArticleDataResponse;
-}
-export interface IArticleListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: IArticleDataResponse[];
-    count: number;
-  };
-}
-
-export interface IArticleCreateRequest {
-  title: string;
-  lang: string;
-  studio: string;
-  anthologyId?: string;
-  parentId?: string;
-  status?: number;
-}
-
-export interface IAnthologyCreateRequest {
-  title: string;
-  lang: string;
-  studio: string;
-}
-
-export interface IArticleMapRequest {
-  id?: string;
-  collect_id?: string;
-  collection?: { id: string; title: string };
-  article_id?: string;
-  level: number;
-  title: string;
-  title_text?: string;
-  editor?: IUser;
-  children?: number;
-  status?: number;
-  deleted_at?: string | null;
-  created_at?: string;
-  updated_at?: string;
-}
-export interface IArticleMapListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: IArticleMapRequest[];
-    count: number;
-  };
-}
-export interface IArticleMapAddRequest {
-  anthology_id: string;
-  article_id: string[];
-  operation: string;
-}
-export interface IArticleMapUpdateRequest {
-  data: IArticleMapRequest[];
-  operation: string;
-}
-export interface IArticleMapAddResponse {
-  ok: boolean;
-  message: string;
-  data: number;
-}
-export interface IDeleteResponse {
-  ok: boolean;
-  message: string;
-  data: number;
-}
-export interface IArticleNavResponse {
-  ok: boolean;
-  data: IArticleNavData;
-  message: string;
-}
-
-export interface IArticleNavData {
-  curr?: IArticleMapRequest;
-  prev?: IArticleMapRequest;
-  next?: IArticleMapRequest;
-}
-
-export interface IPageNavResponse {
-  ok: boolean;
-  data: IPageNavData;
-  message: string;
-}
-
-export interface IPageNavData {
-  curr: IPageNavItem;
-  prev: IPageNavItem;
-  next: IPageNavItem;
-}
-
-export interface IPageNavItem {
-  id: number;
-  type: string;
-  volume: number;
-  page: number;
-  book: number;
-  paragraph: number;
-  wid: number;
-  pcd_book_id: number;
-  created_at: string;
-  updated_at: string;
-}
-
-export interface ICSParaNavResponse {
-  ok: boolean;
-  data: ICSParaNavData;
-  message: string;
-}
-
-export interface ICSParaNavData {
-  curr: ICSParaNavItem;
-  prev?: ICSParaNavItem;
-  next?: ICSParaNavItem;
-  end: number;
-}
-
-export interface ICSParaNavItem {
-  book: number;
-  start: number;
-  content: string;
-}
-
-export interface IArticleFtsListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: IArticleDataResponse[];
-    page: { size: number; current: number; total: number };
-  };
-}

+ 0 - 38
dashboard-v6/backup/components/api/Attachments.ts

@@ -1,38 +0,0 @@
-export interface IAttachmentRequest {
-  id: string;
-  name: string;
-  filename: string;
-  title: string;
-  size: number;
-  content_type: string;
-  url: string;
-  thumbnail?: { small: string; middle: string };
-  created_at?: string;
-  updated_at?: string;
-}
-export interface IAttachmentUpdate {
-  title: string;
-}
-export interface IAttachmentResponse {
-  ok: boolean;
-  message: string;
-  data: IAttachmentRequest;
-}
-
-export interface IAttachmentListResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: IAttachmentRequest[]; count: number };
-}
-
-export interface IResAttachmentData {
-  uid: string;
-  sentence_id: string;
-  attachment_id: string;
-  attachment: IAttachmentRequest;
-}
-export interface IResAttachmentListResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: IResAttachmentData[]; count: number };
-}

+ 0 - 124
dashboard-v6/backup/components/api/Auth.ts

@@ -1,124 +0,0 @@
-import type { IUser } from "../auth/User"
-
-export type TRole =
-  | "owner"
-  | "manager"
-  | "editor"
-  | "member"
-  | "reader"
-  | "student"
-  | "assistant"
-  | "unknown";
-
-export interface ISignUpRequest {
-  token: string;
-  username: string;
-  nickname: string;
-  email: string;
-  password: string;
-  lang: string;
-}
-export interface ISignUpVerifyResponse {
-  ok: boolean;
-  message: string | { email: boolean; username: boolean };
-  data: string;
-}
-export interface ISignInResponse {
-  ok: boolean;
-  message: string;
-  data: string;
-}
-export interface IUserRequest {
-  id?: string;
-  userName?: string;
-  nickName?: string;
-  email?: string;
-  avatar?: string;
-  roles?: string[];
-}
-
-export interface IUserListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: IUser[];
-    count: number;
-  };
-}
-export interface IUserListResponse2 {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: IUserApiData[];
-    count: number;
-  };
-}
-export interface IUserResponse {
-  ok: boolean;
-  message: string;
-  data: IUserApiData;
-}
-
-export interface IUserApiData {
-  id: string;
-  userName: string;
-  nickName: string;
-  email: string;
-  avatar?: string;
-  avatarName?: string;
-  role: string[];
-  created_at?: string;
-  updated_at?: string;
-}
-
-export interface IStudioApiResponse {
-  id: string;
-  nickName: string;
-  studioName?: string;
-  realName: string;
-  avatar?: string;
-  owner: IUser;
-}
-
-export interface IInviteRequest {
-  email: string;
-  lang: string;
-  studio: string;
-  subject?: string;
-  dashboard?: string;
-}
-export interface IInviteResponse {
-  ok: boolean;
-  message: string;
-  data: IInviteData;
-}
-
-export interface IInviteData {
-  id: string;
-  user_uid: string;
-  email: string;
-  status: string;
-  created_at: string;
-  updated_at: string;
-}
-export interface IInviteListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: IInviteData[];
-    count: number;
-  };
-}
-export interface IInviteResponse {
-  ok: boolean;
-  message: string;
-  data: IInviteData;
-}
-
-export interface IEmailCertificationResponse {
-  ok: boolean;
-  message: string;
-  data: number;
-}
-
-export type TSoftwareEdition = "basic" | "pro";

+ 0 - 74
dashboard-v6/backup/components/api/Channel.ts

@@ -1,74 +0,0 @@
-import type { IStudio } from "../auth/Studio"
-import type { TRole } from "./Auth"
-export type TChannelType =
-  | "translation"
-  | "nissaya"
-  | "original"
-  | "wbw"
-  | "commentary"
-  | "similar";
-export interface IChannelApiData {
-  id: string;
-  name: string;
-  type?: TChannelType;
-}
-
-export interface ChannelInfoProps {
-  channel: IChannelApiData;
-  studio: IStudio;
-  count?: number;
-}
-/**
- * 句子完成情况
- * [句子字符数,是否完成]
- *
- */
-export type IFinal = [number, boolean];
-export interface IApiResponseChannelData {
-  uid: string;
-  name: string;
-  summary: string;
-  type: TChannelType;
-  studio: IStudio;
-  lang: string;
-  status: number;
-  is_system: boolean;
-  progress?: number;
-  created_at: string;
-  updated_at: string;
-  role?: TRole;
-  final?: IFinal[];
-  content_created_at: string;
-  content_updated_at: string;
-}
-export interface IApiResponseChannel {
-  ok: boolean;
-  message: string;
-  data: IApiResponseChannelData;
-}
-export interface IApiResponseChannelList {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: IApiResponseChannelData[];
-    count: number;
-  };
-}
-
-export interface ISentInChapterListResponse {
-  ok: boolean;
-  data: ISentInChapterListData;
-  message: string;
-}
-
-export interface ISentInChapterListData {
-  rows: ISentInChapterListDataRow[];
-  count: number;
-}
-
-export interface ISentInChapterListDataRow {
-  book: number;
-  paragraph: number;
-  word_begin: number;
-  word_end: number;
-}

+ 0 - 88
dashboard-v6/backup/components/api/Comment.ts

@@ -1,88 +0,0 @@
-import type { IUser } from "../auth/User"
-import type { TDiscussionType } from "../discussion/Discussion"
-import type { TContentType } from "../discussion/DiscussionCreate"
-import type { TResType } from "../discussion/DiscussionListCard"
-import type { ITagMapData } from "./Tag"
-
-export interface ICommentRequest {
-  id?: string;
-  res_id?: string;
-  res_type?: string;
-  type?: TDiscussionType;
-  title?: string;
-  content?: string;
-  content_type?: TContentType;
-  parent?: string;
-  topicId?: string;
-  tpl_id?: string;
-  status?: "active" | "close";
-  editor?: IUser;
-  created_at?: string;
-  updated_at?: string;
-}
-
-export interface ICommentApiData {
-  id: string;
-  res_id: string;
-  res_type: TResType;
-  type: TDiscussionType;
-  title?: string;
-  content?: string;
-  content_type?: TContentType;
-  html?: string;
-  summary?: string;
-  parent?: string;
-  tpl_id?: string;
-  status?: "active" | "close";
-  children_count?: number;
-  editor: IUser;
-  created_at?: string;
-  updated_at?: string;
-}
-
-export interface ICommentResponse {
-  ok: boolean;
-  message: string;
-  data: ICommentApiData;
-}
-
-export interface ICommentListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: ICommentApiData[];
-    count: number;
-    active: number;
-    close: number;
-    can_create: boolean;
-    can_reply: boolean;
-  };
-}
-export interface ICommentAnchorResponse {
-  ok: boolean;
-  message: string;
-  data: string;
-}
-
-export interface IDiscussionCountRequest {
-  course_id?: string | null;
-  sentences: string[][];
-}
-
-export interface IDiscussionCountWbw {
-  book_id: number;
-  paragraph: number;
-  wid: number;
-}
-export interface IDiscussionCountData {
-  id: string;
-  res_id: string;
-  type: string;
-  editor_uid: string;
-  wbw?: IDiscussionCountWbw;
-}
-export interface IDiscussionCountResponse {
-  ok: boolean;
-  message: string;
-  data: { discussions: IDiscussionCountData[]; tags: ITagMapData[] };
-}

+ 0 - 326
dashboard-v6/backup/components/api/Corpus.ts

@@ -1,326 +0,0 @@
-import type { IStudio } from "../auth/Studio"
-import type { IUser } from "../auth/User"
-import type { IChannel } from "../channel/Channel"
-import type { TContentType } from "../discussion/DiscussionCreate"
-import type { ISuggestionCount, IWidgetSentEditInner } from "../template/SentEdit"
-import type { TChannelType } from "./Channel"
-import type { TagNode } from "./Tag"
-
-export interface IApiPaliChapterList {
-  id: string;
-  book: number;
-  paragraph: number;
-  level: number;
-  toc: string;
-  title: string;
-  lenght: number;
-  chapter_len: number;
-  next_chapter: number;
-  prev_chapter: number;
-  parent: number;
-  chapter_strlen: number;
-  path: string;
-  progress_line?: number[];
-}
-
-export interface IPaliChapterListResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: IApiPaliChapterList[]; count: number };
-}
-export interface IApiResponsePaliChapter {
-  ok: boolean;
-  message: string;
-  data: IApiPaliChapterList;
-}
-
-export interface IPaliPara {
-  book: number;
-  paragraph: number;
-  level: number;
-  class: string;
-  toc: string;
-  text: string;
-  html: string;
-  lenght: number;
-  chapter_len: number;
-  next_chapter: number;
-  prev_chapter: number;
-  parent: number;
-  chapter_strlen: number;
-  path: string;
-  uid: string;
-}
-
-export interface IPaliParagraphResponse {
-  ok: boolean;
-  message: string;
-  data: IPaliPara;
-}
-export interface IPaliListResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: IPaliPara[]; count: number };
-}
-
-/**
- * progress?view=chapter_channels&book=98&par=22
- */
-export interface IChapterChannelData {
-  book: number;
-  para: number;
-  uid: string;
-  channel_id: string;
-  progress: number;
-  progress_line?: number[];
-  updated_at: string;
-  views: number;
-  likes: number[];
-  channel: {
-    type: TChannelType;
-    owner_uid: string;
-    editor_id: number;
-    name: string;
-    summary: string;
-    lang: string;
-    status: number;
-    created_at: string;
-    updated_at: string;
-    uid: string;
-  };
-  studio: IStudio;
-}
-
-export interface IChapterChannelListResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: IChapterChannelData[]; count: number };
-}
-
-export interface IApiChapterTag {
-  id: string;
-  name: string;
-  count: number;
-}
-export interface IApiResponseChapterTagList {
-  ok: boolean;
-  message: string;
-  data: { rows: IApiChapterTag[]; count: number };
-}
-
-export interface IApiResponseChannelListData {
-  channel_id: string;
-  count: number;
-  channel: {
-    id: number;
-    type: TChannelType;
-    owner_uid: string;
-    editor_id: number;
-    name: string;
-    summary: string;
-    lang: string;
-    status: number;
-    setting: string;
-    created_at: string;
-    updated_at: string;
-    uid: string;
-  };
-  studio: IStudio;
-}
-export interface IApiResponseChannelList {
-  ok: boolean;
-  message: string;
-  data: { rows: IApiResponseChannelListData[]; count: number };
-}
-
-export interface ISentenceDiffRequest {
-  sentences: string[];
-  channels: string[];
-}
-export interface ISentenceDiffData {
-  book_id: number;
-  paragraph: number;
-  word_start: number;
-  word_end: number;
-  channel_uid: string;
-  content: string | null;
-  content_type: string;
-  editor_uid: string;
-  updated_at: string;
-}
-export interface ISentenceDiffResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: ISentenceDiffData[]; count: number };
-}
-
-export interface ISentenceRequest {
-  book: number;
-  para: number;
-  wordStart: number;
-  wordEnd: number;
-  channel: string;
-  content: string | null;
-  contentType?: TContentType;
-  prEditor?: string;
-  prId?: string;
-  prUuid?: string;
-  prEditAt?: string;
-  channels?: string;
-  html?: boolean;
-  token?: string | null;
-}
-
-export interface ISentenceData {
-  id?: string;
-  book: number;
-  paragraph: number;
-  word_start: number;
-  word_end: number;
-  content: string;
-  content_type?: TContentType;
-  html: string;
-  editor: IUser;
-  channel: IChannel;
-  studio: IStudio;
-  updated_at: string;
-  acceptor?: IUser;
-  pr_edit_at?: string;
-  fork_at?: string;
-  suggestionCount?: ISuggestionCount;
-}
-
-export interface ISentenceResponse {
-  ok: boolean;
-  message: string;
-  data: ISentenceData;
-}
-export interface ISentenceListResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: ISentenceData[]; count: number };
-}
-export interface ISentenceNewRequest {
-  sentences: ISentenceDiffData[];
-  channel?: string;
-  copy?: boolean;
-  fork_from?: string;
-}
-
-export interface IPaliToc {
-  book: number;
-  paragraph: number;
-  level: string;
-  toc: string;
-  translation?: string;
-}
-
-export interface IPaliTocListResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: IPaliToc[]; count: number };
-}
-
-export interface IChapterToc {
-  book: number;
-  paragraph: number;
-  level: number;
-  text: string | null;
-  chapter_len: number;
-  chapter_strlen: number;
-  parent: number;
-}
-
-export interface IChapterTocListResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: IChapterToc[]; count: number };
-}
-
-export interface IPaliBookListResponse {
-  name: string;
-  tag: string[];
-  children?: IPaliBookListResponse[];
-}
-
-export interface IChapterData {
-  title: string;
-  toc: string;
-  book: number;
-  para: number;
-  path: string;
-  tags: TagNode[];
-  channel: { name: string; owner_uid: string };
-  studio: IStudio;
-  channel_id: string;
-  summary: string;
-  view: number;
-  like: number;
-  status?: number;
-  progress: number;
-  progress_line?: number[];
-  created_at: string;
-  updated_at: string;
-}
-export interface IChapterListResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: IChapterData[]; count: number };
-}
-
-export interface ILangList {
-  lang: string;
-  count: number;
-}
-export interface IChapterLangListResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: ILangList[]; count: number };
-}
-
-export interface ISentencePrRequest {
-  book?: number;
-  para?: number;
-  begin?: number;
-  end?: number;
-  channel?: string;
-  text: string;
-}
-export interface ISentencePrResponseData {
-  book_id: number;
-  paragraph: number;
-  word_start: number;
-  word_end: number;
-  channel_uid: string;
-}
-export interface ISentencePrResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    new: ISentencePrResponseData;
-    count: number;
-    webhook: { message: string; ok: boolean };
-  };
-}
-
-export interface ISimSent {
-  sent: string;
-  sim: number;
-}
-export interface ISentenceSimListResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: ISimSent[]; count: number };
-}
-
-export interface ISentenceWbwListResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: IWidgetSentEditInner[]; count: number };
-}
-
-export interface IEditableSentence {
-  ok: boolean;
-  message: string;
-  data: IWidgetSentEditInner;
-}

+ 0 - 227
dashboard-v6/backup/components/api/Course.ts

@@ -1,227 +0,0 @@
-import type { IStudio } from "../auth/Studio"
-import type { IUser } from "../auth/User"
-import type { IChannel } from "../channel/Channel"
-import type { _TRole } from "./Auth"
-
-export interface ICourseListApiResponse {
-  article: string;
-  title: string;
-  level: string;
-  children: number;
-}
-
-export interface ICourseDataRequest {
-  id?: string; //课程ID
-  title: string; //标题
-  subtitle?: string; //副标题
-  summary?: string; //副标题
-  content?: string | null;
-  sign_up_message?: string | null;
-  cover?: string; //封面图片文件名
-  teacher_id?: string; //UserID
-  publicity: number; //类型-公开/内部
-  anthology_id?: string; //文集ID
-  channel_id?: string; //标准答案channel
-  start_at?: string; //课程开始时间
-  end_at?: string; //课程结束时间
-  sign_up_start_at: string | null; //报名开始时间
-  sign_up_end_at: string | null; //报名结束时间
-  join: string;
-  request_exp: string;
-  number: number;
-}
-export type TCourseRole =
-  | "owner"
-  | "teacher"
-  | "manager"
-  | "assistant"
-  | "student";
-export type TCourseJoinMode = "invite" | "manual" | "open";
-export type TCourseExpRequest = "none" | "begin-end" | "daily";
-
-export interface IMember {
-  role: TCourseRole;
-  status: TCourseMemberStatus;
-}
-export interface ICourseDataResponse {
-  id: string; //课程ID
-  title: string; //标题
-  subtitle: string; //副标题
-  summary?: string; //副标题
-  sign_up_message?: string | null; //报名弹窗消息
-  teacher?: IUser; //UserID
-  course_count?: number; //课程数
-  publicity: number; //类型-公开/内部
-  anthology_id?: string; //文集ID
-  anthology_title?: string; //文集标题
-  anthology_owner?: IStudio; //文集拥有者
-  channel_id: string; //标准答案ID
-  channel_name?: string; //文集标题
-  channel_owner?: IStudio; //文集拥有者
-  studio?: IStudio; //课程拥有者
-  start_at: string; //课程开始时间
-  end_at: string; //课程结束时间
-  sign_up_start_at: string; //报名开始时间
-  sign_up_end_at: string; //报名结束时间
-  content: string; //简介
-  cover: string; //封面图片文件名
-  cover_url?: string[]; //封面图片文件名
-  member_count: number;
-  join: TCourseJoinMode; //报名方式
-  request_exp: TCourseExpRequest;
-  my_status?: TCourseMemberStatus;
-  my_status_id?: string;
-  count_progressing?: number;
-  number: number;
-  members?: IMember[];
-  my_role?: TCourseRole;
-  created_at: string; //创建时间
-  updated_at: string; //修改时间
-}
-export interface ICourseResponse {
-  ok: boolean;
-  message: string;
-  data: ICourseDataResponse;
-}
-export interface ICourseListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: ICourseDataResponse[];
-    count: number;
-  };
-}
-
-export interface ICourseCreateRequest {
-  title: string;
-  lang: string;
-  studio: string;
-}
-
-export interface IAnthologyCreateRequest {
-  title: string;
-  lang: string;
-  studio: string;
-}
-export interface ICourseNumberResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    create: number;
-    teach: number;
-    study: number;
-  };
-}
-
-export type TCourseMemberStatus =
-  | "none" /*无*/
-  | "normal" /*开放课程直接加入*/
-  | "joined" /*开放课程已经加入*/
-  | "applied" /**学生已经报名 管理员尚未审核 */
-  | "canceled" /**学生取消报名 */
-  | "agreed" /**学生/助教已经接受邀请 */
-  | "disagreed" /**学生/助教已经拒绝邀请 */
-  | "left" /**学生自己退出 */
-  | "invited" /**管理员已经邀请学生加入 */
-  | "revoked" /**管理员撤销邀请 */
-  | "accepted" /**已经被管理员录取 */
-  | "rejected" /**报名已经被管理员拒绝 */
-  | "blocked"; /**被管理员清退 */
-
-export type TCourseMemberAction =
-  | "join" /*加入自学课程*/
-  | "apply" /**学生报名 */
-  | "cancel" /**学生取消报名 */
-  | "agree" /**学生/助教接受邀请 */
-  | "disagree" /**学生/助教拒绝邀请 */
-  | "leave" /**学生/助教自己退出 */
-  | "invite" /**管理员邀请学生加入 */
-  | "revoke" /**管理员撤销邀请 */
-  | "accept" /**管理员录取 */
-  | "reject" /**管理员拒绝 */
-  | "block"; /**管理员清退 */
-
-interface IActionMap {
-  action: TCourseMemberAction;
-  status: TCourseMemberStatus;
-}
-export const actionMap = (action: TCourseMemberAction) => {
-  const data: IActionMap[] = [
-    { action: "join", status: "joined" },
-    { action: "apply", status: "applied" },
-    { action: "cancel", status: "canceled" },
-    { action: "agree", status: "agreed" },
-    { action: "disagree", status: "disagreed" },
-    { action: "leave", status: "left" },
-    { action: "invite", status: "invited" },
-    { action: "revoke", status: "revoked" },
-    { action: "accept", status: "accepted" },
-    { action: "reject", status: "rejected" },
-    { action: "block", status: "blocked" },
-  ];
-
-  const current = data.find((value) => value.action === action);
-  return current?.status;
-};
-
-export interface ICourseMemberData {
-  id?: string;
-  user_id: string;
-  course_id: string;
-  course?: ICourseDataResponse;
-  channel_id?: string;
-  channel?: IChannel;
-  role?: TCourseRole;
-  operating?: "invite" | "sign_up";
-  user?: IUser;
-  editor?: IUser;
-  status?: TCourseMemberStatus;
-  created_at?: string;
-  updated_at?: string;
-}
-export interface ICourseMemberResponse {
-  ok: boolean;
-  message: string;
-  data: ICourseMemberData;
-}
-export interface ICourseMemberListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: ICourseMemberData[];
-    role: TCourseRole;
-    count: number;
-  };
-}
-
-export interface ICourseMemberDeleteResponse {
-  ok: boolean;
-  message: string;
-  data: boolean;
-}
-
-export interface ICourseUser {
-  role: TCourseRole;
-  channel_id?: string | null;
-}
-export interface ICourseCurrUserResponse {
-  ok: boolean;
-  message: string;
-  data: ICourseUser;
-}
-
-export interface IExerciseListData {
-  user: IUser;
-  wbw: number;
-  translation: number;
-  question: number;
-  html: string;
-}
-export interface ICourseExerciseResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: IExerciseListData[];
-    count: number;
-  };
-}

+ 0 - 141
dashboard-v6/backup/components/api/Dict.ts

@@ -1,141 +0,0 @@
-import type { IStudio } from "../auth/Studio"
-import type { IUser } from "../auth/User"
-import type { ICaseListData } from "../dict/CaseList"
-
-export interface IDictRequest {
-  id?: number;
-  word: string;
-  type?: string | null;
-  grammar?: string | null;
-  mean?: string | null;
-  parent?: string | null;
-  parent2?: string | null;
-  note?: string | null;
-  factors?: string | null;
-  factormean?: string | null;
-  confidence: number;
-  dict_id?: string;
-  dict_name?: string;
-  language?: string;
-  creator_id?: number;
-  editor?: IUser;
-  studio?: IStudio;
-  status?: number;
-  updated_at?: string;
-}
-export interface IUserDictCreate {
-  data: string;
-  view: string;
-}
-export interface IDictResponse {
-  ok: boolean;
-  message: string;
-  data: number[];
-}
-export interface IDictInfo {
-  id: string;
-  name: string;
-  shortname: string;
-}
-export interface IApiResponseDictData {
-  id: string;
-  sn?: number;
-  word: string;
-  type?: string | null;
-  grammar?: string | null;
-  mean?: string | null;
-  parent?: string | null;
-  note?: string | null;
-  factors?: string | null;
-  factormean?: string | null;
-  source: string | null;
-  language: string;
-  dict?: IDictInfo;
-  dict_id: string;
-  dict_name?: string;
-  dict_shortname?: string;
-  shortname?: string;
-  confidence: number;
-  creator_id: number;
-  updated_at: string;
-  exp?: number;
-  editor?: IUser;
-  status?: number;
-  count?: number;
-  created_at?: string;
-}
-export interface IApiResponseDict {
-  ok: boolean;
-  message: string;
-  data: IApiResponseDictData;
-}
-export interface IApiResponseDictList {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: IApiResponseDictData[];
-    count: number;
-    time?: number;
-  };
-}
-
-export interface IVocabularyData {
-  word: string;
-  count: number;
-  meaning?: string;
-  strlen: number;
-}
-export interface IVocabularyListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: IVocabularyData[];
-    count: number;
-  };
-}
-
-export interface IUserDictDeleteRequest {
-  id: string;
-}
-
-export interface ICaseItem {
-  word: string;
-  case: ICaseListData[];
-  count: number;
-}
-export interface ICaseListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: ICaseItem[];
-    count: number;
-  };
-}
-
-export interface IFirstMeaning {
-  word?: string;
-  meaning?: string;
-}
-export interface IDictFirstMeaningResponse {
-  ok: boolean;
-  message: string;
-  data: IFirstMeaning[];
-}
-
-export interface IPreferenceListResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: IApiResponseDictData[]; count: number };
-}
-export interface IPreferenceRequest {
-  id?: string;
-  word?: string;
-  factors?: string | null;
-  parent?: string | null;
-  confidence?: number;
-}
-export interface IPreferenceResponse {
-  ok: boolean;
-  message: string;
-  data: IApiResponseDictData;
-}

+ 0 - 25
dashboard-v6/backup/components/api/Exp.ts

@@ -1,25 +0,0 @@
-export interface IUserOperationDailyRequest {
-  date_int: number;
-  duration: number;
-  hit?: number;
-}
-
-export interface IUserOperationDailyResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: IUserOperationDailyRequest[]; count: number };
-}
-
-export interface IUserStatistic {
-  exp: { sum: number };
-  wbw: { count: number };
-  lookup: { count: number };
-  translation: { count: number; count_pub: number };
-  term: { count: number; count_with_note: number };
-  dict: { count: number };
-}
-export interface IUserStatisticResponse {
-  ok: boolean;
-  message: string;
-  data: IUserStatistic;
-}

+ 0 - 80
dashboard-v6/backup/components/api/Group.ts

@@ -1,80 +0,0 @@
-import type { IStudio } from "../auth/Studio"
-import type { IUser } from "../auth/User"
-import type { TRole } from "./Auth"
-
-export interface IGroupRequest {
-  id?: string;
-  name: string;
-  description?: string;
-  studio_name?: string;
-}
-
-export interface IGroupDataRequest {
-  uid: string;
-  name: string;
-  description: string;
-  owner: string;
-  studio: IStudio;
-  role: TRole;
-  created_at: string;
-  updated_at: string;
-}
-
-export interface IGroupResponse {
-  ok: boolean;
-  message: string;
-  data: IGroupDataRequest;
-}
-export interface IGroupListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: IGroupDataRequest[];
-    count: number;
-  };
-}
-export interface IGroupMemberRequest {
-  id?: number;
-  user_id: string;
-  group_id: string;
-  power?: number;
-  level?: number;
-  status?: number;
-}
-export interface IGroupMemberData {
-  id?: number;
-  user_id: string;
-  group_id: string;
-  group: IStudio;
-  power?: number;
-  level?: number;
-  status?: number;
-  user: IUser;
-  created_at?: string;
-  updated_at?: string;
-}
-export interface IGroupMemberResponse {
-  ok: boolean;
-  message: string;
-  data: IGroupMemberData;
-}
-export interface IGroupMemberListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: IGroupMemberData[];
-    role: TRole;
-    count: number;
-  };
-}
-
-export interface IGroupMemberDeleteResponse {
-  ok: boolean;
-  message: string;
-  data: boolean;
-}
-export interface IDeleteResponse {
-  ok: boolean;
-  message: string;
-  data: number;
-}

+ 0 - 5
dashboard-v6/backup/components/api/Guide.ts

@@ -1,5 +0,0 @@
-export interface IGuideResponse {
-  ok: boolean;
-  message: string;
-  data: string;
-}

+ 0 - 46
dashboard-v6/backup/components/api/Share.ts

@@ -1,46 +0,0 @@
-import type { IUser } from "../auth/User"
-import type { IGroup } from "../group/Group"
-import type { TRole } from "./Auth"
-
-export interface IShareRequest {
-  res_id: string;
-  res_type: number;
-  role: TRole;
-  user_id: string[];
-  user_type: string;
-}
-export interface IShareUpdateRequest {
-  role: TRole;
-}
-export interface IShareData {
-  id?: string;
-  res_id: string;
-  res_type: string;
-  power?: number;
-  res_name: string;
-  user?: IUser;
-  group?: IGroup;
-  owner?: IUser;
-  role?: TRole;
-  created_at?: string;
-  updated_at?: string;
-}
-export interface IShareResponse {
-  ok: boolean;
-  message: string;
-  data: IShareData;
-}
-export interface IShareListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: IShareData[];
-    role: TRole;
-    count: number;
-  };
-}
-export interface IShareDeleteResponse {
-  ok: boolean;
-  message: string;
-  data: number;
-}

+ 0 - 30
dashboard-v6/backup/components/api/Suggestion.ts

@@ -1,30 +0,0 @@
-import type { IUser } from "../auth/User"
-import type { IChannelApiData } from "./Channel"
-
-export interface ISuggestionData {
-  id: string;
-  uid: string;
-  book: number;
-  paragraph: number;
-  word_start: number;
-  word_end: number;
-  channel: IChannelApiData;
-  content: string;
-  html: string;
-  editor: IUser;
-  created_at: string;
-  updated_at: string;
-}
-export interface ISuggestionResponse {
-  ok: boolean;
-  message: string;
-  data: ISuggestionData;
-}
-export interface ISuggestionListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: ISuggestionData[];
-    count: number;
-  };
-}

+ 0 - 84
dashboard-v6/backup/components/api/Tag.ts

@@ -1,84 +0,0 @@
-import type { IStudio } from "../auth/Studio"
-import type { IUser } from "../auth/User"
-
-export interface TagNode {
-  id: string;
-  name: string;
-  description?: string;
-}
-
-export interface ITagRequest {
-  id?: string;
-  name?: string;
-  description?: string | null;
-  color?: number;
-  studio?: string;
-  created_at?: string;
-  updated_at?: string;
-}
-export interface ITag {
-  id?: string;
-  name?: string;
-  description?: string | null;
-  color?: number;
-  owner?: IStudio;
-  created_at?: string;
-  updated_at?: string;
-}
-export interface ITagData {
-  id: string;
-  name: string;
-  description?: string | null;
-  color: number;
-  owner?: IStudio;
-  created_at: string;
-  updated_at: string;
-}
-
-export interface ITagResponse {
-  ok: boolean;
-  message: string;
-  data: ITagData;
-}
-
-export interface ITagResponseList {
-  ok: boolean;
-  message: string;
-  data: { rows: ITagData[]; count: number };
-}
-
-export interface ITagMapRequest {
-  id?: string;
-  table_name?: string;
-  anchor_id?: string;
-  tag_id?: string;
-  studio?: string;
-  course?: string;
-}
-
-export interface ITagMapData {
-  id: string;
-  table_name: string;
-  anchor_id: string;
-  tag_id: string;
-  name?: string | null;
-  color?: number | null;
-  description?: string | null;
-  title?: string;
-  editor?: IUser;
-  owner?: IStudio;
-  created_at?: string;
-  updated_at?: string;
-}
-
-export interface ITagMapResponse {
-  ok: boolean;
-  message: string;
-  data: ITagMapData;
-}
-
-export interface ITagMapResponseList {
-  ok: boolean;
-  message: string;
-  data: { rows: ITagMapData[]; count: number };
-}

+ 0 - 83
dashboard-v6/backup/components/api/Term.ts

@@ -1,83 +0,0 @@
-import type { IStudio } from "../auth/Studio"
-import type { IUser } from "../auth/User"
-import type { IChannel } from "../channel/Channel"
-import type { TRole } from "./Auth"
-
-export interface ITermDataRequest {
-  id?: string;
-  word: string;
-  tag?: string;
-  meaning: string;
-  other_meaning?: string;
-  note?: string;
-  channel?: string;
-  studioName?: string;
-  studioId?: string;
-  language?: string;
-  parent_channel_id?: string;
-  save_as?: boolean;
-  copy_channel?: string;
-  copy_lang?: string;
-  pr?: boolean;
-}
-export interface ITermDataResponse {
-  id: number;
-  guid: string;
-  word: string;
-  tag: string;
-  meaning: string;
-  other_meaning: string;
-  note: string | null;
-  html?: string;
-  channal: string;
-  channel?: IChannel;
-  studio: IStudio;
-  editor: IUser;
-  role?: TRole;
-  exp?: number;
-  language: string;
-  community?: boolean;
-  summary?: string;
-  summary_is_community?: boolean;
-  created_at: string;
-  updated_at: string;
-}
-export interface ITermResponse {
-  ok: boolean;
-  message: string;
-  data: ITermDataResponse;
-}
-export interface ITermListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: ITermDataResponse[];
-    count: number;
-  };
-}
-
-interface IMeaningCount {
-  meaning: string;
-  count: number;
-}
-interface IStudioChannel {
-  name: string;
-  uid: string;
-}
-export interface ITermCreate {
-  word: string;
-  meaningCount: IMeaningCount[];
-  studioChannels: IStudioChannel[];
-  language: string;
-  studio: IStudio;
-}
-export interface ITermCreateResponse {
-  ok: boolean;
-  message: string;
-  data: ITermCreate;
-}
-
-export interface ITermDeleteRequest {
-  uuid: boolean;
-  id: string[];
-}

+ 0 - 45
dashboard-v6/backup/components/api/Transfer.ts

@@ -1,45 +0,0 @@
-import type { IStudio } from "../auth/Studio"
-import type { IUser } from "../auth/User"
-import type { IChannel } from "../channel/Channel"
-import type { TResType } from "../discussion/DiscussionListCard"
-
-export type ITransferStatus = "transferred" | "accept" | "refuse" | "cancel";
-export interface ITransferRequest {
-  res_type?: TResType;
-  res_id?: string[];
-  new_owner?: string;
-  status?: ITransferStatus;
-}
-export interface ITransferResponseData {
-  id: string;
-  origin_owner: IStudio;
-  res_type: TResType;
-  res_id: string;
-  channel?: IChannel;
-  transferor: IUser;
-  new_owner: IStudio;
-  status: ITransferStatus;
-  editor?: IUser | null;
-  created_at: string;
-  updated_at: string;
-}
-export interface ITransferCreateResponse {
-  ok: boolean;
-  message: string;
-  data: number;
-}
-export interface ITransferResponse {
-  ok: boolean;
-  message: string;
-  data: ITransferResponseData;
-}
-export interface ITransferResponseList {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: ITransferResponseData[];
-    count: number;
-    out: number;
-    in: number;
-  };
-}

+ 0 - 105
dashboard-v6/backup/components/api/ai.ts

@@ -1,105 +0,0 @@
-import type { IStudio } from "../auth/Studio";
-import type { IUser } from "../auth/User";
-
-export type TPrivacy = "private" | "public" | "disable";
-
-export interface IKimiResponse {
-  id: string;
-  object: string;
-  created: number;
-  model: string;
-  choices: AiChoice[];
-  usage: AiUsage;
-}
-
-export interface AiUsage {
-  prompt_tokens: number;
-  completion_tokens: number;
-  total_tokens: number;
-}
-
-export interface AiChoice {
-  index: number;
-  message: AiMessage;
-  logprobs?: string | null; //volcengine
-  finish_reason: string;
-}
-
-export interface AiMessage {
-  role: string;
-  content: string;
-}
-
-export interface IAiTranslateRequest {
-  origin: string;
-}
-
-export interface IAiTranslateResponse {
-  ok: boolean;
-  message: string;
-  data: IKimiResponse;
-}
-
-export interface IAiModel {
-  uid: string;
-  name: string;
-  description?: string | null;
-  url?: string | null;
-  model?: string;
-  key?: string;
-  privacy: TPrivacy;
-  owner: IStudio;
-  editor: IUser;
-  user: IUser;
-  created_at: string;
-  updated_at: string;
-}
-
-export interface IAiModelRequest {
-  name: string;
-  description?: string | null;
-  system_prompt?: string | null;
-  url?: string | null;
-  model?: string;
-  key?: string;
-  privacy: TPrivacy;
-  studio_name?: string;
-}
-
-export interface IAiModelListResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: IAiModel[]; total: number };
-}
-
-export interface IAiModelResponse {
-  ok: boolean;
-  message: string;
-  data: IAiModel;
-}
-
-export interface IAiModelLogData {
-  id: string;
-  uid: string;
-  model_id: string;
-  request_headers: string;
-  request_data: string;
-  response_headers?: string | null;
-  response_data?: string | null;
-  status: number;
-  success: boolean;
-  request_at: string;
-  created_at: string;
-  updated_at: string;
-}
-
-export interface IAiModelLogListResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: IAiModelLogData[]; total: number };
-}
-
-export interface IAiModelSystem {
-  view: string;
-  models: string[];
-}

+ 0 - 56
dashboard-v6/backup/components/api/like.ts

@@ -1,56 +0,0 @@
-import type { IUser } from "../auth/User"
-
-export type TLikeType = "like" | "dislike" | "favorite" | "watch";
-export interface ILikeData {
-  id: string;
-  type: TLikeType;
-  target_id: string;
-  target_type?: string;
-  user: IUser;
-  context?: string | null;
-  selected?: boolean;
-  my_id?: string;
-  count?: number;
-  updated_at?: string;
-  created_at?: string;
-}
-export interface ILikeCount {
-  type: TLikeType;
-  selected?: boolean;
-  my_id?: string;
-  count?: number;
-  user: IUser;
-}
-
-export interface ILikeListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: ILikeData[];
-    count: number;
-  };
-}
-
-export interface ILikeResponse {
-  ok: boolean;
-  message: string;
-  data: ILikeData;
-}
-
-export interface ILikeCountListResponse {
-  ok: boolean;
-  message: string;
-  data: ILikeCount[];
-}
-export interface ILikeCountResponse {
-  ok: boolean;
-  message: string;
-  data: ILikeCount;
-}
-
-export interface ILikeRequest {
-  type: TLikeType;
-  target_id: string;
-  target_type: string;
-  user_id?: string;
-}

+ 0 - 43
dashboard-v6/backup/components/api/notification.ts

@@ -1,43 +0,0 @@
-import type { IUser } from "../auth/User"
-import type { IChannel } from "../channel/Channel"
-
-export interface INotificationPutResponse {
-  ok: boolean;
-  data: {
-    unread: number;
-  };
-  message: string;
-}
-
-export interface INotificationListResponse {
-  ok: boolean;
-  data: INotificationListData;
-  message: string;
-}
-
-export interface INotificationListData {
-  rows: INotificationData[];
-  count: number;
-  unread: number;
-}
-
-interface INotificationData {
-  id: string;
-  from: IUser;
-  to: IUser;
-  channel: IChannel;
-  url?: string;
-  title?: string;
-  book_title?: string;
-  content: string;
-  content_type: string;
-  res_type: string;
-  res_id: string;
-  status: string;
-  deleted_at?: string;
-  created_at: string;
-  updated_at: string;
-}
-export interface INotificationRequest {
-  status: string;
-}

+ 0 - 260
dashboard-v6/backup/components/api/task.ts

@@ -1,260 +0,0 @@
-/**
- *             $table->text('description',512)->nullable();
-            $table->jsonb('assignees')->index()->nullable();
-            $table->jsonb('roles')->index()->nullable();
-            $table->uuid('executor')->index()->nullable();
-            $table->uuid('executor_relation_task')->index()->nullable();
-            $table->uuid('parent')->index()->nullable();
-            $table->jsonb('pre_task')->index()->nullable();
-            $table->uuid('owner')->index();
-            $table->uuid('editor')->index();
-            $table->string('status',32)->index()->default('pending');
-            $table->timestamps();
- */
-
-import type { IStudio } from "../auth/Studio"
-import type { IUser } from "../auth/User"
-import type { TPrivacy } from "./ai"
-
-export type TTaskStatus =
-  | "pending"
-  | "published"
-  | "running"
-  | "done"
-  | "restarted"
-  | "requested_restart"
-  | "closed"
-  | "canceled"
-  | "expired"
-  | "queue"
-  | "stop"
-  | "quit"
-  | "pause";
-export const StatusButtons: TTaskStatus[] = [
-  "pending",
-  "published",
-  "running",
-  "done",
-  "restarted",
-  "requested_restart",
-  "quit",
-];
-export type TTaskType = "instance" | "workflow" | "group";
-
-export interface IProject {
-  id: string;
-  sn: number;
-  title: string;
-  description: string | null;
-  weight: number;
-}
-
-export type TTaskCategory =
-  | "translate"
-  | "suggest"
-  | "vocabulary"
-  | "team"
-  | "review"
-  | "proofread";
-export const ATaskCategory: TTaskCategory[] = [
-  "translate",
-  "suggest",
-  "vocabulary",
-  "team",
-  "review",
-  "proofread",
-];
-export interface ITaskData {
-  id: string;
-  title: string;
-  description?: string | null;
-  category?: TTaskCategory | null;
-  progress?: number;
-  html?: string | null;
-  type: TTaskType;
-  order?: number;
-  assignees?: IUser[] | null;
-  assignees_id?: string[] | null;
-  parent?: ITaskData | null;
-  parent_id?: string | null;
-  roles?: string[] | null;
-  executor?: IUser | null;
-  executor_id?: string | null;
-  executor_relation_task?: ITaskData | null;
-  executor_relation_task_id?: string | null;
-  pre_task?: ITaskData[] | null;
-  pre_task_id?: string | null;
-  next_task?: ITaskData[] | null;
-  next_task_id?: string | null;
-  is_milestone: boolean;
-  project?: IProject | null;
-  project_id?: string | null;
-  owner?: IStudio;
-  owner_id?: string | null;
-  editor?: IUser;
-  editor_id?: string | null;
-  status?: TTaskStatus;
-  created_at?: string;
-  updated_at?: string;
-  started_at?: string | null;
-  finished_at?: string | null;
-  children?: ITaskData[];
-}
-
-export interface ITaskUpdateRequest {
-  id: string;
-  studio_name: string;
-  title?: string;
-  description?: string | null;
-  category?: TTaskCategory | null;
-  type?: TTaskType;
-  assignees_id?: string[] | null;
-  parent_id?: string | null;
-  project_id?: string | null;
-  roles?: string[] | null;
-  executor_id?: string | null;
-  executor_relation_task_id?: string | null;
-  pre_task_id?: string | null;
-  next_task_id?: string | null;
-  is_milestone?: boolean;
-  status?: string;
-}
-
-export interface ITaskListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: ITaskData[];
-    count: number;
-  };
-}
-
-export interface ITaskCreateRequest {
-  title: string;
-  studio: string;
-  type: TTaskType;
-}
-
-export interface ITaskResponse {
-  ok: boolean;
-  message: string;
-  data: ITaskData;
-}
-
-/**
- *            $table->uuid('id')->primary()->default(DB::raw('uuid_generate_v1mc()'));
-            $table->string('title',512)->index();
-            $table->boolean('is_template')->index()->default(false);
-            $table->text('description')->nullable();
-            $table->jsonb('executors')->index()->nullable();
-            $table->uuid('parent')->index()->nullable();
-            $table->jsonb('milestone')->index()->nullable();
-            $table->uuid('owner')->index();
-            $table->uuid('editor')->index();
-            $table->jsonb('status')->index();
-            $table->timestamps();
- */
-
-export interface IProjectData {
-  id: string;
-  title: string;
-  type: TProjectType;
-  weight: number;
-  description: string | null;
-  parent?: IProjectData | null;
-  parent_id?: string | null;
-  path?: IProjectData[] | null;
-  executors?: IUser[] | null;
-  milestone?: IMilestoneInProject[] | null;
-  owner: IStudio;
-  editor: IUser;
-  status: ITaskStatusInProject[];
-  privacy: TPrivacy;
-  created_at: string;
-  updated_at: string;
-  deleted_at?: string | null;
-  started_at?: string | null;
-  finished_at?: string | null;
-  children?: IProjectData[];
-}
-
-export interface IProjectUpdateRequest {
-  id?: string;
-  studio_name?: string;
-  title: string;
-  type: TProjectType;
-  privacy?: TPrivacy;
-  weight?: number;
-  description?: string | null;
-  parent_id?: string | null;
-  res_id?: string;
-}
-
-export interface IProjectListResponse {
-  data: { rows: IProjectData[]; count: number };
-  message: string;
-  ok: boolean;
-}
-export interface IProjectResponse {
-  data: IProjectData;
-  message: string;
-  ok: boolean;
-}
-export type TProjectType = "instance" | "workflow" | "endpoint";
-export interface IProjectCreateRequest {
-  title: string;
-  type: TProjectType;
-  studio_name: string;
-}
-
-export interface IMilestoneData {
-  id: string;
-  title: string;
-}
-
-export interface IMilestoneCount {
-  value: number;
-  total: number;
-}
-export interface IMilestoneInProject {
-  milestone: IMilestoneData;
-  projects: IMilestoneCount;
-  chars: IMilestoneCount;
-}
-
-export interface ITaskStatusInProject {
-  status: string;
-  count: number;
-  percent: number;
-}
-
-export interface ITaskGroupInsertRequest {
-  data: ITaskGroupInsertData[];
-}
-export interface ITaskGroupInsertData {
-  project_id: string;
-  tasks: ITaskData[];
-}
-
-export interface ITaskGroupResponse {
-  ok: boolean;
-  message: string;
-  data: { taskCount: number; taskRelationCount: number };
-}
-export interface IProjectTreeInsertRequest {
-  studio_name: string;
-  parent_id?: string | null;
-  title?: string;
-  data: IProjectUpdateRequest[];
-}
-
-export interface IProjectTreeData {
-  id: string;
-  resId?: string;
-  isLeaf: boolean;
-}
-export interface IProjectTreeResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: IProjectTreeData[]; count: number };
-}

+ 0 - 26
dashboard-v6/backup/components/api/token.ts

@@ -1,26 +0,0 @@
-export interface IPayload {
-  res_type: string;
-  res_id: string;
-  book?: number;
-  para_start?: number;
-  para_end?: number;
-  power: TPower;
-}
-
-export type TPower = "readonly" | "edit";
-
-export interface ITokenCreate {
-  payload: IPayload[];
-}
-export interface ITokenData {
-  payload: IPayload;
-  token: string;
-}
-export interface ITokenCreateResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: ITokenData[];
-    count: number;
-  };
-}

+ 0 - 51
dashboard-v6/backup/components/api/view.ts

@@ -1,51 +0,0 @@
-import type { ArticleType } from "../article/Article"
-
-export interface IViewRequest {
-  target_type: ArticleType;
-  book: number;
-  para: number;
-  channel: string;
-  mode: string;
-}
-export interface IMetaChapter {
-  book: number;
-  para: number;
-  channel: string;
-  mode: string;
-}
-export interface IViewData {
-  id: string;
-  target_id: string;
-  target_type: ArticleType;
-  updated_at: string;
-  title: string;
-  org_title: string;
-  meta: string;
-}
-export interface IViewStoreResponse {
-  ok: boolean;
-  message: string;
-  data: number;
-}
-export interface IViewResponse {
-  ok: boolean;
-  message: string;
-  data: IViewData;
-}
-export interface IViewListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: IViewData[];
-    count: number;
-  };
-}
-
-export interface IView {
-  id: string;
-  title: string;
-  subtitle: string;
-  type: ArticleType;
-  updatedAt: string;
-  meta: IMetaChapter;
-}

+ 0 - 45
dashboard-v6/backup/components/api/webhook.ts

@@ -1,45 +0,0 @@
-import type { IUser } from "../auth/User"
-import type { TResType } from "../discussion/DiscussionListCard"
-import type { IWebhookEvent } from "../webhook/WebhookTpl"
-
-export type TReceiverType = "wechat" | "dingtalk";
-
-export interface IWebhookRequest {
-  res_type: TResType;
-  res_id: string;
-  url: string;
-  receiver: TReceiverType;
-  event?: string[] | null;
-  event2?: IWebhookEvent[] | null;
-  status?: string;
-}
-
-export interface IWebhookApiData {
-  id: string;
-  res_type: TResType;
-  res_id: string;
-  url: string;
-  receiver: TReceiverType;
-  event: string[] | null;
-  event2?: IWebhookEvent[] | null;
-  fail: number;
-  success: number;
-  status: string;
-  editor: IUser;
-  created_at: string | null;
-  updated_at: string | null;
-}
-
-export interface IWebhookResponse {
-  ok: boolean;
-  message: string;
-  data: IWebhookApiData;
-}
-export interface IWebhookListResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    rows: IWebhookApiData[];
-    count: number;
-  };
-}

+ 0 - 82
dashboard-v6/backup/components/article/AddToAnthology.tsx

@@ -1,82 +0,0 @@
-import { message } from "antd";
-import React, { useState } from "react";
-import { post } from "../../request";
-import AnthologyModal from "../anthology/AnthologyModal";
-import type {
-  IArticleMapAddRequest,
-  IArticleMapAddResponse,
-} from "../../api/Article";
-import { useAppSelector } from "../../hooks";
-import { currentUser } from "../../reducers/current-user";
-
-interface IWidget {
-  trigger?: React.ReactNode;
-  studioName?: string;
-  articleIds?: string[];
-  open?: boolean; // 外部控制
-  onClose?: (open: boolean) => void;
-  onFinally?: () => void;
-}
-
-const AddToAnthologyWidget = ({
-  trigger,
-  studioName,
-  open,
-  articleIds,
-  onClose,
-  onFinally,
-}: IWidget) => {
-  const user = useAppSelector(currentUser);
-
-  /** 是否受控 */
-  const isControlled = open !== undefined;
-
-  /** 内部状态(仅非受控使用) */
-  const [innerOpen, setInnerOpen] = useState(false);
-
-  /** 最终状态来源 */
-  const isOpen = isControlled ? open : innerOpen;
-
-  /** 状态修改统一入口 */
-  const setOpen = (next: boolean) => {
-    if (!isControlled) {
-      setInnerOpen(next);
-    }
-    onClose?.(next);
-  };
-
-  /** 选择文集 */
-  const handleSelect = (id: string) => {
-    if (!articleIds) return;
-
-    post<IArticleMapAddRequest, IArticleMapAddResponse>("/v2/article-map", {
-      anthology_id: id,
-      article_id: articleIds,
-      operation: "add",
-    })
-      .then((json) => {
-        if (json.ok) {
-          message.success(json.data);
-          setOpen(false); // 成功后关闭
-        } else {
-          message.error(json.message);
-        }
-      })
-      .catch(console.error)
-      .finally(() => {
-        onFinally?.();
-      });
-  };
-
-  return (
-    <AnthologyModal
-      studioName={studioName ?? user?.realName}
-      trigger={trigger && <span onClick={() => setOpen(true)}>{trigger}</span>}
-      open={isOpen}
-      onClose={setOpen}
-      onSelect={handleSelect}
-    />
-  );
-};
-
-export default AddToAnthologyWidget;

+ 0 - 91
dashboard-v6/backup/components/article/AnchorNav.tsx

@@ -1,91 +0,0 @@
-import { Anchor } from "antd";
-import { useEffect, useState, useRef } from "react";
-import { convertToPlain } from "../../utils";
-
-const { Link } = Anchor;
-
-interface HeadingNode {
-  key: string;
-  label: string;
-  level: number;
-  children?: HeadingNode[];
-}
-
-interface Props {
-  open?: boolean;
-  containerSelector?: string; // 可指定扫描范围
-}
-
-/** 构建树结构 */
-function buildTree(list: HeadingNode[]): HeadingNode[] {
-  const root: HeadingNode = { key: "root", label: "", level: 0, children: [] };
-  const stack = [root];
-
-  for (const node of list) {
-    while (stack.length && stack[stack.length - 1].level >= node.level) {
-      stack.pop();
-    }
-
-    const parent = stack[stack.length - 1];
-    parent.children ??= [];
-    parent.children.push(node);
-
-    stack.push(node);
-  }
-
-  return root.children ?? [];
-}
-
-/** 递归渲染 */
-function renderLinks(nodes: HeadingNode[]): React.ReactNode {
-  return nodes.map((node) => (
-    <Link key={node.key} href={node.key} title={node.label}>
-      {node.children && renderLinks(node.children)}
-    </Link>
-  ));
-}
-
-const AnchorNavWidget = ({ open = false, containerSelector }: Props) => {
-  const [tree, setTree] = useState<HeadingNode[]>([]);
-  const containerRef = useRef<HTMLElement | null>(null);
-
-  /** 获取容器 */
-  useEffect(() => {
-    containerRef.current = containerSelector
-      ? document.querySelector(containerSelector)
-      : document.body;
-  }, [containerSelector]);
-
-  /** 扫描 heading */
-  useEffect(() => {
-    if (!open || !containerRef.current) return;
-
-    const headings = Array.from(
-      containerRef.current.querySelectorAll("h1,h2,h3,h4,h5,h6")
-    );
-
-    const list: HeadingNode[] = headings
-      .map((el) => {
-        if (!el.id) return null;
-
-        return {
-          key: `#${el.id}`,
-          label: convertToPlain(el.innerHTML).slice(0, 30),
-          level: Number(el.tagName[1]),
-        };
-      })
-      .filter(Boolean) as HeadingNode[];
-
-    setTree(buildTree(list));
-  }, [open]);
-
-  if (!open || tree.length === 0) return null;
-
-  return (
-    <div className="article_anchor paper_zh">
-      <Anchor offsetTop={50}>{renderLinks(tree)}</Anchor>
-    </div>
-  );
-};
-
-export default AnchorNavWidget;

+ 0 - 72
dashboard-v6/backup/components/article/AnthologiesAtArticle.tsx

@@ -1,72 +0,0 @@
-import { Space, Typography, message } from "antd";
-import { useEffect, useState } from "react";
-import { get } from "../../request";
-import type { IArticleMapListResponse } from "../../api/Article";
-
-const { Link, Paragraph } = Typography;
-interface IList {
-  key?: string;
-  label?: string;
-}
-interface IWidget {
-  articleId?: string;
-  anthologyId?: string | null;
-  onClick?: Function;
-}
-const AnthologiesAtArticleWidget = ({
-  articleId,
-  anthologyId,
-  onClick,
-}: IWidget) => {
-  const [list, setList] = useState<IList[]>();
-  useEffect(() => {
-    //查询这个article 有多少文集
-    const url = `/v2/article-map?view=article&id=${articleId}`;
-    console.log("url", url);
-    get<IArticleMapListResponse>(url).then((json) => {
-      if (json.ok) {
-        const anthologies: IList[] = json.data.rows.map((item) => {
-          return {
-            key: item.collection?.id,
-            label: item.collection?.title,
-          };
-        });
-        console.log("anthologies", anthologies);
-        setList(anthologies.filter((value) => value.key !== anthologyId));
-      } else {
-        message.error("获取文集列表失败");
-      }
-    });
-  }, [articleId]);
-
-  let title = "";
-  if (anthologyId) {
-    title = "其他文集";
-  } else {
-    title = "文集列表";
-  }
-
-  return (
-    <Paragraph style={{ display: list && list.length > 0 ? "block" : "none" }}>
-      <Space>
-        {title}
-        {list?.map((item, index) => {
-          return (
-            <Link
-              key={index}
-              onClick={(e) => {
-                if (typeof onClick !== "undefined") {
-                  onClick(item.key, e);
-                }
-              }}
-            >
-              {item.label}
-            </Link>
-          );
-        })}
-      </Space>
-    </Paragraph>
-  );
-};
-
-export default AnthologiesAtArticleWidget;

+ 0 - 68
dashboard-v6/backup/components/article/AnthologyCard.tsx

@@ -1,68 +0,0 @@
-import { Link } from "react-router";
-import { Row, Col } from "antd";
-import { Card } from "antd";
-import { Typography } from "antd";
-
-import StudioName from "../auth/Studio";
-import type { IStudio } from "../auth/Studio";
-import type { ListNodeData } from "./EditableTree";
-
-const { Title, Text } = Typography;
-
-export interface IArticleData {
-  id: string;
-  title: string;
-  subTitle: string;
-  summary: string;
-  created_at: string;
-  updated_at: string;
-}
-
-export interface IAnthologyData {
-  id: string;
-  title: string;
-  subTitle: string;
-  summary: string;
-  articles: ListNodeData[];
-  studio: IStudio;
-  created_at: string;
-  updated_at: string;
-}
-
-interface IWidgetAnthologyCard {
-  data: IAnthologyData;
-}
-
-const AnthologyCardWidget = (prop: IWidgetAnthologyCard) => {
-  const articleList = prop.data.articles.map((item, id) => {
-    return <div key={id}>{item.title}</div>;
-  });
-  return (
-    <>
-      <Card
-        hoverable
-        bordered={false}
-        style={{ width: "100%", borderRadius: 8 }}
-      >
-        <Title level={4}>
-          <Link to={`/anthology/${prop.data.id}`}>{prop.data.title}</Link>
-        </Title>
-        <div>
-          <Text type="secondary">{prop.data.subTitle}</Text>
-        </div>
-        <div>
-          <Text>{prop.data.summary}</Text>
-        </div>
-        <Link to={`/blog/${prop.data.studio.studioName}/anthology`}>
-          <StudioName data={prop.data.studio} />
-        </Link>
-        <Row>
-          <Col flex={"100px"}>Content</Col>
-          <Col flex={"auto"}>{articleList}</Col>
-        </Row>
-      </Card>
-    </>
-  );
-};
-
-export default AnthologyCardWidget;

+ 0 - 126
dashboard-v6/backup/components/article/AnthologyDetail.tsx

@@ -1,126 +0,0 @@
-import { useState, useEffect } from "react";
-import { Space, Typography, message } from "antd";
-import { get } from "../../request";
-import type {
-  IAnthologyDataResponse,
-  IAnthologyResponse,
-} from "../../api/Article";
-import type { IAnthologyData } from "./AnthologyCard";
-import StudioName from "../auth/Studio";
-import TimeShow from "../general/TimeShow";
-import Marked from "../general/Marked";
-import AnthologyTocTree from "../anthology/AnthologyTocTree";
-import { useIntl } from "react-intl";
-
-const { Title, Text, Paragraph } = Typography;
-
-interface Props {
-  aid?: string;
-  channels?: string[];
-  visible?: boolean;
-  onArticleClick?: (anthologyId: string, id: string, target: string) => void;
-  onTitle?: (title: string) => void;
-  onLoading?: (loading: boolean) => void;
-  onError?: (error: unknown, message?: string) => void;
-}
-
-const AnthologyDetailWidget = ({
-  aid,
-  channels,
-  visible = true,
-  onArticleClick,
-  onLoading,
-  onTitle,
-  onError,
-}: Props) => {
-  const [data, setData] = useState<IAnthologyData>();
-  const intl = useIntl();
-
-  useEffect(() => {
-    if (!aid) return;
-
-    let active = true;
-
-    const fetchData = async () => {
-      try {
-        onLoading?.(true);
-
-        const res = await get<IAnthologyResponse>(`/v2/anthology/${aid}`);
-
-        if (!active) return;
-
-        if (!res.ok) {
-          message.error(res.message);
-          onError?.(res.data, res.message);
-          return;
-        }
-
-        const item: IAnthologyDataResponse = res.data;
-
-        const parsed: IAnthologyData = {
-          id: item.uid,
-          title: item.title,
-          subTitle: item.subtitle,
-          summary: item.summary,
-          articles: [],
-          studio: item.studio,
-          created_at: item.created_at,
-          updated_at: item.updated_at,
-        };
-
-        setData(parsed);
-        onTitle?.(item.title);
-      } catch (err) {
-        if (active) {
-          console.error(err);
-          onError?.(err);
-        }
-      } finally {
-        if (active && onLoading) {
-          onLoading(false);
-        }
-      }
-    };
-
-    fetchData();
-
-    return () => {
-      active = false;
-    };
-  }, [aid, onError, onLoading, onTitle]);
-
-  if (!visible || !data) return null;
-
-  return (
-    <div style={{ padding: 12 }}>
-      <Title level={4}>{data.title}</Title>
-
-      <Text type="secondary">{data.subTitle}</Text>
-
-      <Paragraph>
-        <Space>
-          <StudioName data={data.studio} />
-          <TimeShow updatedAt={data.updated_at} />
-        </Space>
-      </Paragraph>
-
-      <Paragraph>
-        <Marked text={data.summary} />
-      </Paragraph>
-
-      <Title level={5}>
-        {intl.formatMessage({ id: "labels.table-of-content" })}
-      </Title>
-
-      <AnthologyTocTree
-        anthologyId={aid}
-        channels={channels}
-        onClick={(anthologyId, id, target) =>
-          onArticleClick?.(anthologyId, id, target)
-        }
-      />
-    </div>
-  );
-};
-
-export default AnthologyDetailWidget;

+ 0 - 198
dashboard-v6/backup/components/article/AnthologyInfoEdit.tsx

@@ -1,198 +0,0 @@
-import { Form, message } from "antd";
-import { useIntl } from "react-intl";
-import {
-  ProForm,
-  ProFormSelect,
-  ProFormText,
-  type RequestOptionsType,
-} from "@ant-design/pro-components";
-import MDEditor from "@uiw/react-md-editor";
-
-import { get, put } from "../../request";
-import type {
-  IAnthologyDataRequest,
-  IAnthologyDataResponse,
-  IAnthologyResponse,
-} from "../../api/Article";
-import LangSelect from "../general/LangSelect";
-import PublicitySelect from "../studio/PublicitySelect";
-import { useState } from "react";
-import type { DefaultOptionType } from "antd/lib/select";
-import type { IApiResponseChannelList } from "../../api/Channel";
-import { useAppSelector } from "../../hooks";
-import { currentUser } from "../../reducers/current-user";
-
-interface IFormData {
-  title: string;
-  subtitle: string;
-  summary?: string;
-  lang: string;
-  status: number;
-  defaultChannel?: string;
-}
-
-interface IWidget {
-  anthologyId?: string;
-  studioName?: string;
-  onLoad?: Function;
-}
-const AnthologyInfoEditWidget = ({
-  studioName,
-  anthologyId,
-  onLoad,
-}: IWidget) => {
-  const intl = useIntl();
-  const [channelOption, setChannelOption] = useState<DefaultOptionType[]>([]);
-  const [currChannel, setCurrChannel] = useState<RequestOptionsType>();
-  const [data, setData] = useState<IAnthologyDataResponse>();
-
-  const user = useAppSelector(currentUser);
-
-  return anthologyId ? (
-    <ProForm<IFormData>
-      onFinish={async (values: IFormData) => {
-        const url = `/v2/anthology/${anthologyId}`;
-        console.log("url", url);
-        console.log("values", values);
-        const res = await put<IAnthologyDataRequest, IAnthologyResponse>(url, {
-          title: values.title,
-          subtitle: values.subtitle,
-          summary: values.summary,
-          status: values.status,
-          lang: values.lang,
-          default_channel: values.defaultChannel,
-        });
-        console.log(res);
-        if (res.ok) {
-          if (typeof onLoad !== "undefined") {
-            onLoad(res.data);
-          }
-          message.success(
-            intl.formatMessage({
-              id: "flashes.success",
-            })
-          );
-        } else {
-          message.error(res.message);
-        }
-      }}
-      request={async () => {
-        const url = `/v2/anthology/${anthologyId}`;
-        console.log("url", url);
-        const res = await get<IAnthologyResponse>(url);
-        console.log("文集get", res);
-        if (res.ok) {
-          setData(res.data);
-          if (typeof onLoad !== "undefined") {
-            onLoad(res.data);
-          }
-          if (res.data.default_channel) {
-            const channel = {
-              value: res.data.default_channel.id,
-              label: res.data.default_channel.name,
-            };
-            setCurrChannel(channel);
-            setChannelOption([channel]);
-          }
-
-          return {
-            title: res.data.title,
-            subtitle: res.data.subtitle,
-            summary: res.data.summary ? res.data.summary : undefined,
-            lang: res.data.lang,
-            status: res.data.status,
-            defaultChannel: res.data.default_channel?.id,
-          };
-        } else {
-          return {
-            title: "",
-            subtitle: "",
-            summary: "",
-            lang: "",
-            status: 0,
-            defaultChannel: "",
-          };
-        }
-      }}
-    >
-      <ProForm.Group>
-        <ProFormText
-          width="md"
-          name="title"
-          required
-          label={intl.formatMessage({
-            id: "forms.fields.title.label",
-          })}
-          rules={[
-            {
-              required: true,
-              message: intl.formatMessage({
-                id: "forms.message.title.required",
-              }),
-            },
-          ]}
-        />
-        <ProFormText
-          width="md"
-          name="subtitle"
-          label={intl.formatMessage({
-            id: "forms.fields.subtitle.label",
-          })}
-        />
-      </ProForm.Group>
-
-      <ProForm.Group>
-        <LangSelect width="md" />
-        <PublicitySelect
-          width="md"
-          disable={["public_no_list"]}
-          readonly={
-            user?.roles?.includes("basic") ||
-            data?.studio.roles?.includes("basic")
-              ? true
-              : false
-          }
-        />
-      </ProForm.Group>
-      <ProForm.Group>
-        <ProFormSelect
-          options={channelOption}
-          width="md"
-          name="defaultChannel"
-          label={"默认版本"}
-          showSearch
-          debounceTime={300}
-          request={async ({ keyWords }) => {
-            console.log("keyWord", keyWords);
-            if (typeof keyWords === "undefined") {
-              return currChannel ? [currChannel] : [];
-            }
-            const url = `/v2/channel?view=studio&name=${studioName}`;
-            console.log("url", url);
-            const json = await get<IApiResponseChannelList>(url);
-            const textbookList = json.data.rows.map((item) => {
-              return {
-                value: item.uid,
-                label: `${item.studio.nickName}/${item.name}`,
-              };
-            });
-            console.log("json", textbookList);
-            return textbookList;
-          }}
-        />
-      </ProForm.Group>
-      <ProForm.Group>
-        <Form.Item
-          name="summary"
-          label={intl.formatMessage({ id: "forms.fields.summary.label" })}
-        >
-          <MDEditor />
-        </Form.Item>
-      </ProForm.Group>
-    </ProForm>
-  ) : (
-    <></>
-  );
-};
-
-export default AnthologyInfoEditWidget;

+ 0 - 87
dashboard-v6/backup/components/article/AnthologyList.tsx

@@ -1,87 +0,0 @@
-import { useState, useEffect } from "react";
-import { List } from "antd";
-
-import { get } from "../../request";
-import type { IAnthologyListResponse } from "../../api/Article";
-import AnthologyCard from "./AnthologyCard";
-import type { IAnthologyData } from "./AnthologyCard";
-
-interface IWidget {
-  studioName?: string;
-  searchKey?: string;
-}
-const AnthologyListWidget = ({ studioName, searchKey }: IWidget) => {
-  const [tableData, setTableData] = useState<IAnthologyData[]>([]);
-  const [total, setTotal] = useState<number>();
-  const [currPage, setCurrPage] = useState<number>(1);
-  const pageSize = 20;
-
-  useEffect(() => {
-    const offset = (currPage - 1) * pageSize;
-    let url = `/v2/anthology?view=public&offset=${offset}&limit=${pageSize}`;
-    if (typeof studioName !== "undefined") {
-      url += `&studio=${studioName}`;
-    }
-    if (typeof searchKey === "string" && searchKey.length > 0) {
-      url += `&search=${searchKey}`;
-    }
-
-    console.log("get-url", url);
-    get<IAnthologyListResponse>(url).then(function (json) {
-      if (json.ok) {
-        const newTree: IAnthologyData[] = json.data.rows.map((item) => {
-          return {
-            id: item.uid,
-            title: item.title,
-            subTitle: item.subtitle,
-            summary: item.summary,
-            articles: item.article_list.map((al) => {
-              return {
-                key: al.article,
-                title: al.title,
-                level: parseInt(al.level),
-              };
-            }),
-            studio: item.studio,
-            created_at: item.created_at,
-            updated_at: item.updated_at,
-          };
-        });
-        setTableData(newTree);
-        setTotal(json.data.count);
-      } else {
-        setTableData([]);
-        setTotal(0);
-      }
-    });
-  }, [currPage, searchKey, studioName]);
-
-  return (
-    <List
-      itemLayout="vertical"
-      size="large"
-      dataSource={tableData}
-      pagination={{
-        onChange: (page) => {
-          console.log(page);
-          setCurrPage(page);
-        },
-        showQuickJumper: true,
-        showSizeChanger: false,
-        pageSize: pageSize,
-        total: total,
-        position: "both",
-        showTotal: (total) => {
-          return `结果: ${total}`;
-        },
-      }}
-      renderItem={(item) => (
-        <List.Item>
-          <AnthologyCard data={item} />
-        </List.Item>
-      )}
-    />
-  );
-};
-
-export default AnthologyListWidget;

+ 0 - 56
dashboard-v6/backup/components/article/AnthologyStudioList.tsx

@@ -1,56 +0,0 @@
-import { Link } from "react-router";
-import { useState, useEffect } from "react";
-import { List, Space, Card } from "antd";
-
-import StudioName from "../auth/Studio";
-import type { IAnthologyStudioListApiResponse } from "../../api/Article";
-import type { IStudioApiResponse } from "../../api/Auth";
-import { get } from "../../request";
-
-interface IAnthologyStudioData {
-  count: number;
-  studio: IStudioApiResponse;
-}
-/*
-interface IWidgetAnthologyList {
-	data: IAnthologyData[];
-}
-*/
-const AnthologyStudioListWidget = () => {
-  const [tableData, setTableData] = useState<IAnthologyStudioData[]>([]);
-  useEffect(() => {
-    console.log("useEffect");
-    const url = `/v2/anthology?view=studio_list`;
-    get<IAnthologyStudioListApiResponse>(url).then(function (json) {
-      const newTree: IAnthologyStudioData[] = json.data.rows.map((item) => {
-        return {
-          count: item.count,
-          studio: item.studio,
-        };
-      });
-      setTableData(newTree);
-    });
-  }, []);
-
-  return (
-    <Card title="作者" size="small">
-      <List
-        itemLayout="vertical"
-        size="small"
-        dataSource={tableData}
-        renderItem={(item) => (
-          <List.Item>
-            <Link to={`/blog/${item.studio.realName}/anthology`}>
-              <Space>
-                <StudioName data={item.studio} />
-                <span>({item.count})</span>
-              </Space>
-            </Link>
-          </List.Item>
-        )}
-      />
-    </Card>
-  );
-};
-
-export default AnthologyStudioListWidget;

+ 0 - 226
dashboard-v6/backup/components/article/Article.tsx

@@ -1,226 +0,0 @@
-import type { IArticleDataResponse } from "../../api/Article";
-import TypeArticle from "./TypeArticle";
-import TypeAnthology from "./TypeAnthology";
-import TypeTerm from "./TypeTerm";
-import TypePali from "./TypePali";
-import "./article.css";
-import TypePage from "./TypePage";
-import TypeCSPara from "./TypeCSPara";
-import type { ISearchParams } from "../../pages/library/article/show";
-import TypeCourse from "./TypeCourse";
-import { useEffect, useState } from "react";
-import { fullUrl } from "../../utils";
-import TypeSeries from "./TypeSeries";
-import DiscussionCount from "../discussion/DiscussionCount";
-import TypeTask from "./TypeTask";
-
-interface IWidget {
-  type?: ArticleType;
-  articleId?: string;
-  mode?: ArticleMode | null;
-  channelId?: string | null;
-  parentChannels?: string[];
-  book?: string | null;
-  para?: string | null;
-  anthologyId?: string | null;
-  courseId?: string | null;
-  active?: boolean;
-  focus?: string | null;
-  hideInteractive?: boolean;
-  hideTitle?: boolean;
-  isSubWindow?: boolean;
-  onArticleChange?: (
-    type: ArticleType,
-    id: string,
-    target: string,
-    param?: ISearchParams[]
-  ) => void;
-  onLoad?: Function;
-  onAnthologySelect?: Function;
-  onTitle?: Function;
-  onArticleEdit?: Function;
-}
-const ArticleWidget = ({
-  type,
-  book,
-  para,
-  channelId,
-  parentChannels,
-  articleId,
-  anthologyId,
-  courseId,
-  mode = "read",
-  active = false,
-  focus,
-  hideInteractive = false,
-  hideTitle = false,
-  isSubWindow = false,
-  onArticleChange,
-  onLoad,
-  onAnthologySelect,
-  onTitle,
-  onArticleEdit,
-}: IWidget) => {
-  const [currId, setCurrId] = useState(articleId);
-  useEffect(() => setCurrId(articleId), [articleId]);
-
-  return (
-    <div>
-      <DiscussionCount courseId={type === "textbook" ? courseId : undefined} />
-      {type === "article" ? (
-        <TypeArticle
-          isSubWindow={isSubWindow}
-          type={type}
-          articleId={onArticleChange ? articleId : currId}
-          channelId={channelId}
-          parentChannels={parentChannels}
-          mode={mode}
-          anthologyId={anthologyId}
-          active={active}
-          hideInteractive={hideInteractive}
-          hideTitle={hideTitle}
-          onArticleEdit={(value: IArticleDataResponse) => {
-            if (typeof onArticleEdit !== "undefined") {
-              onArticleEdit(value);
-            }
-          }}
-          onArticleChange={onArticleChange}
-          onLoad={(data: IArticleDataResponse) => {
-            if (typeof onLoad !== "undefined") {
-              onLoad(data);
-            }
-            if (typeof onTitle !== "undefined") {
-              onTitle(data.title);
-            }
-          }}
-          onAnthologySelect={(id: string) => {
-            if (typeof onAnthologySelect !== "undefined") {
-              onAnthologySelect(id);
-            }
-          }}
-        />
-      ) : type === "anthology" ? (
-        <TypeAnthology
-          type={type}
-          articleId={onArticleChange ? articleId : currId}
-          channelId={channelId}
-          mode={mode}
-          onArticleChange={(type: ArticleType, id: string, target: string) => {
-            if (typeof onArticleChange !== "undefined") {
-              onArticleChange(type, id, target);
-            }
-          }}
-          onTitle={(value: string) => {
-            if (typeof onTitle !== "undefined") {
-              onTitle(value);
-            }
-          }}
-        />
-      ) : type === "term" ? (
-        <TypeTerm
-          articleId={onArticleChange ? articleId : currId}
-          channelId={channelId}
-          mode={mode}
-          onArticleChange={(type: ArticleType, id: string, target: string) => {
-            if (typeof onArticleChange !== "undefined") {
-              onArticleChange(type, id, target);
-            }
-          }}
-        />
-      ) : type === "chapter" || type === "para" ? (
-        <TypePali
-          type={type}
-          articleId={onArticleChange ? articleId : currId}
-          channelId={channelId}
-          mode={mode}
-          book={book}
-          para={para}
-          focus={focus}
-          onArticleChange={(
-            type: ArticleType,
-            id: string,
-            target: string,
-            param?: ISearchParams[]
-          ) => {
-            if (typeof onArticleChange !== "undefined") {
-              onArticleChange(type, id, target, param);
-            }
-          }}
-          onLoad={(data: IArticleDataResponse) => {
-            if (typeof onLoad !== "undefined") {
-              onLoad(data);
-            }
-          }}
-          onTitle={(value: string) => {
-            if (typeof onTitle !== "undefined") {
-              onTitle(value);
-            }
-          }}
-        />
-      ) : type === "series" ? (
-        <TypeSeries
-          articleId={onArticleChange ? articleId : currId}
-          channelId={channelId}
-          onArticleChange={(
-            type: ArticleType,
-            id: string,
-            target: string,
-            param: ISearchParams[]
-          ) => {
-            if (typeof onArticleChange !== "undefined") {
-              onArticleChange(type, id, target, param);
-            }
-          }}
-        />
-      ) : type === "page" ? (
-        <TypePage
-          articleId={onArticleChange ? articleId : currId}
-          channelId={channelId}
-          focus={focus}
-          mode={mode}
-          onArticleChange={(type: ArticleType, id: string, target: string) => {
-            if (typeof onArticleChange !== "undefined") {
-              onArticleChange(type, id, target);
-            } else {
-              if (target === "_blank") {
-                let url = `/article/page/${id}?mode=${mode}`;
-                if (channelId) {
-                  url += `&channel=${channelId}`;
-                }
-                window.open(fullUrl(url), "_blank");
-              } else {
-                setCurrId(id);
-              }
-            }
-          }}
-        />
-      ) : type === "cs-para" ? (
-        <TypeCSPara
-          articleId={onArticleChange ? articleId : currId}
-          channelId={channelId}
-          mode={mode}
-          onArticleChange={(type: ArticleType, id: string, target: string) => {
-            if (typeof onArticleChange !== "undefined") {
-              onArticleChange(type, id, target);
-            }
-          }}
-        />
-      ) : type === "textbook" ? (
-        <TypeCourse
-          type={type}
-          articleId={onArticleChange ? articleId : currId}
-          channelId={channelId}
-          courseId={courseId}
-          mode={mode}
-          onArticleChange={onArticleChange}
-        />
-      ) : type === "task" ? (
-        <TypeTask articleId={articleId} />
-      ) : (
-        <></>
-      )}
-    </div>
-  );
-};
-
-export default ArticleWidget;

+ 0 - 90
dashboard-v6/backup/components/article/ArticleCard.tsx

@@ -1,90 +0,0 @@
-import { useNavigate } from "react-router";
-import { Button, Card, Dropdown, Space } from "antd";
-import { MoreOutlined, ReloadOutlined } from "@ant-design/icons";
-import type { MenuProps } from "antd";
-
-import type { IWidgetArticleData } from "./ArticleView"
-import ArticleCardMainMenu from "./ArticleCardMainMenu";
-import ModeSwitch from "./ModeSwitch";
-
-interface IWidgetArticleCard {
-  type?: string;
-  articleId?: string;
-  data?: IWidgetArticleData;
-  children?: React.ReactNode;
-  onModeChange?: Function;
-  openInCol?: Function;
-  showCol?: Function;
-}
-const ArticleCardWidget = ({
-  type,
-  articleId,
-  data,
-  children,
-  onModeChange,
-  showCol,
-}: IWidgetArticleCard) => {
-  const navigate = useNavigate();
-
-  const onClick: MenuProps["onClick"] = (e) => {
-    console.log("click ", e);
-    switch (e.key) {
-      case "showCol":
-        if (typeof showCol !== "undefined") {
-          showCol();
-        }
-        break;
-
-      default:
-        break;
-    }
-  };
-
-  const items: MenuProps["items"] = [
-    {
-      key: "showCol",
-      label: "显示分栏",
-    },
-  ];
-
-  const contextMenu = (
-    <Dropdown menu={{ items, onClick }} placement="bottomRight">
-      <Button shape="circle" size="small" icon={<MoreOutlined />}></Button>
-    </Dropdown>
-  );
-  return (
-    <Card
-      size="small"
-      title={
-        <Space>
-          {<ArticleCardMainMenu type={type} articleId={articleId} />}
-          {data?.title}
-        </Space>
-      }
-      extra={
-        <Space>
-          <ModeSwitch
-            channel={null}
-            onModeChange={(mode: string) => {
-              if (typeof onModeChange !== "undefined") {
-                onModeChange(mode);
-              }
-              navigate(`/article/${type}/${articleId}/${mode}`);
-            }}
-          />
-          <Button
-            shape="circle"
-            size="small"
-            icon={<ReloadOutlined />}
-          ></Button>
-          {contextMenu}
-        </Space>
-      }
-      bodyStyle={{ height: `calc(100vh - 94px)`, overflowY: "scroll" }}
-    >
-      {children}
-    </Card>
-  );
-};
-
-export default ArticleCardWidget;

+ 0 - 78
dashboard-v6/backup/components/article/ArticleCardMainMenu.tsx

@@ -1,78 +0,0 @@
-import { Tabs, Button, Popover } from "antd";
-import { MenuOutlined, PushpinOutlined } from "@ant-design/icons";
-
-import PaliTextToc from "./PaliTextToc";
-import Find from "./Find";
-import Nav from "./Nav";
-import { useIntl } from "react-intl";
-
-interface IWidget {
-  type?: string;
-  articleId?: string;
-}
-const ArticleCardMainMenuWidget = ({ articleId }: IWidget) => {
-  const intl = useIntl();
-  const id = articleId?.split("_");
-  let tocWidget = <></>;
-  if (id && id.length > 0) {
-    const sentId = id[0].split("-");
-    if (sentId.length > 1) {
-      tocWidget = (
-        <PaliTextToc book={parseInt(sentId[0])} para={parseInt(sentId[1])} />
-      );
-    }
-  }
-  const styleTabBody: React.CSSProperties = {
-    width: 350,
-    height: "calc(100vh - 200px)",
-    overflowY: "scroll",
-  };
-  const mainMenuContent = (
-    <Tabs
-      size="small"
-      defaultActiveKey="1"
-      tabBarExtraContent={{
-        right: <Button type="text" size="small" icon={<PushpinOutlined />} />,
-      }}
-      items={[
-        {
-          label: intl.formatMessage({
-            id: "labels.table-of-content",
-          }),
-          key: "1",
-          children: <div style={styleTabBody}>{tocWidget}</div>,
-        },
-        {
-          label: `定位`,
-          key: "2",
-          children: (
-            <div style={styleTabBody}>
-              <Nav />
-            </div>
-          ),
-        },
-        {
-          label: `查找`,
-          key: "3",
-          children: (
-            <div style={styleTabBody}>
-              <Find />
-            </div>
-          ),
-        },
-      ]}
-    />
-  );
-  return (
-    <Popover
-      placement="bottomLeft"
-      arrow={{ pointAtCenter: true }}
-      content={mainMenuContent}
-      trigger="click"
-    >
-      <Button size="small" icon={<MenuOutlined />} />
-    </Popover>
-  );
-};
-
-export default ArticleCardMainMenuWidget;

+ 0 - 139
dashboard-v6/backup/components/article/ArticleCreate.tsx

@@ -1,139 +0,0 @@
-import { useIntl } from "react-intl";
-import {
-  ProForm,
-  type ProFormInstance,
-  ProFormSelect,
-  ProFormText,
-} from "@ant-design/pro-components";
-import { Alert, Space, message } from "antd";
-
-import { get, post } from "../../request";
-import type {
-  IAnthologyListResponse,
-  IArticleCreateRequest,
-  IArticleDataResponse,
-  IArticleResponse,
-} from "../../api/Article";
-import LangSelect from "../general/LangSelect";
-import { useEffect, useRef, useState } from "react";
-
-interface IFormData {
-  title: string;
-  lang: string;
-  studio: string;
-  anthologyId?: string;
-  parentId?: string;
-}
-
-interface IWidget {
-  studio?: string;
-  anthologyId?: string;
-  parentId?: string | null;
-  compact?: boolean;
-  onSuccess?: (data: IArticleDataResponse) => void;
-}
-const ArticleCreateWidget = ({
-  studio,
-  parentId,
-  compact = true,
-  onSuccess,
-}: IWidget) => {
-  const intl = useIntl();
-  const formRef = useRef<ProFormInstance | undefined>(undefined);
-  const [parent, setParent] = useState<IArticleDataResponse>();
-  console.log("parentId", parentId);
-  useEffect(() => {
-    if (parentId) {
-      get<IArticleResponse>(`/v2/article/${parentId}`).then((json) => {
-        console.log("article", json);
-
-        if (json.ok) {
-          setParent(json.data);
-        }
-      });
-    }
-  }, []);
-
-  return (
-    <Space orientation="vertical">
-      {parentId ? (
-        <Alert
-          title={`从文章 ${parent?.title} 创建子文章`}
-          type="info"
-          closable
-        />
-      ) : undefined}
-      <ProForm<IFormData>
-        formRef={formRef}
-        onFinish={async (values: IFormData) => {
-          console.log(values);
-          if (typeof studio === "undefined") {
-            return;
-          }
-          values.studio = studio;
-          values.parentId = parentId ? parentId : undefined;
-          const res = await post<IArticleCreateRequest, IArticleResponse>(
-            `/v2/article`,
-            values
-          );
-          console.log(res);
-          if (res.ok) {
-            message.success(intl.formatMessage({ id: "flashes.success" }));
-            if (typeof onSuccess !== "undefined") {
-              onSuccess(res.data);
-              formRef.current?.resetFields(["title"]);
-            }
-          } else {
-            message.error(res.message);
-          }
-        }}
-      >
-        <ProForm.Group>
-          <ProFormText
-            width="md"
-            name="title"
-            required
-            label={intl.formatMessage({ id: "channel.name" })}
-            rules={[
-              {
-                required: true,
-                message: intl.formatMessage({
-                  id: "channel.create.message.noname",
-                }),
-              },
-            ]}
-          />
-        </ProForm.Group>
-        <ProForm.Group>
-          <LangSelect />
-        </ProForm.Group>
-        <ProForm.Group>
-          <ProFormSelect
-            name={"anthologyId"}
-            label={"加入文集"}
-            hidden={compact}
-            width={"md"}
-            showSearch
-            debounceTime={300}
-            request={async ({ keyWords }) => {
-              console.log("keyWord", keyWords);
-              let url = `/v2/anthology?view=studio&view2=my&name=${studio}`;
-              url += keyWords ? "&search=" + keyWords : "";
-              const res = await get<IAnthologyListResponse>(url);
-              const result = res.data.rows.map((item) => {
-                return {
-                  value: item.uid,
-                  label: item.title,
-                };
-              });
-              console.log("json", result);
-              return result;
-            }}
-          />
-        </ProForm.Group>
-      </ProForm>
-    </Space>
-  );
-};
-
-export default ArticleCreateWidget;

+ 0 - 126
dashboard-v6/backup/components/article/ArticleDrawer.tsx

@@ -1,126 +0,0 @@
-import { Button, Drawer, Space, Typography } from "antd";
-import React, { useEffect, useState } from "react";
-import { Link } from "react-router";
-
-import Article from "./Article";
-import type { IArticleDataResponse } from "../../api/Article";
-const { Text } = Typography;
-
-interface IWidget {
-  trigger?: React.ReactNode;
-  title?: string;
-  type?: ArticleType;
-  book?: string;
-  para?: string;
-  channelId?: string;
-  articleId?: string;
-  anthologyId?: string;
-  mode?: ArticleMode;
-  open?: boolean;
-  onClose?: () => void;
-  onTitleChange?: (value: string) => void;
-  onArticleEdit?: (value: IArticleDataResponse) => void;
-}
-
-const ArticleDrawerWidget = ({
-  trigger,
-  title,
-  type,
-  book,
-  para,
-  channelId,
-  articleId,
-  anthologyId,
-  mode,
-  open,
-  onClose,
-  onTitleChange,
-  onArticleEdit,
-}: IWidget) => {
-  const [openDrawer, setOpenDrawer] = useState(open);
-  const [drawerTitle, setDrawerTitle] = useState(title);
-  useEffect(() => {
-    setOpenDrawer(open);
-  }, [open]);
-  useEffect(() => {
-    setDrawerTitle(title);
-  }, [title]);
-  const showDrawer = () => {
-    setOpenDrawer(true);
-  };
-
-  const onDrawerClose = () => {
-    setOpenDrawer(false);
-    if (document.getElementsByTagName("body")[0].hasAttribute("style")) {
-      document.getElementsByTagName("body")[0].removeAttribute("style");
-    }
-    if (typeof onClose !== "undefined") {
-      onClose();
-    }
-  };
-
-  const getUrl = (openMode?: string): string => {
-    let url = `/article/${type}/${articleId}?mode=`;
-    url += openMode ? openMode : mode ? mode : "read";
-    url += channelId ? `&channel=${channelId}` : "";
-    url += book ? `&book=${book}` : "";
-    url += para ? `&par=${para}` : "";
-    return url;
-  };
-
-  return (
-    <>
-      <span onClick={() => showDrawer()}>{trigger}</span>
-      <Drawer
-        title={
-          <Text
-            editable={{
-              onChange: (value: string) => {
-                setDrawerTitle(value);
-                if (typeof onTitleChange !== "undefined") {
-                  onTitleChange(value);
-                }
-              },
-            }}
-          >
-            {drawerTitle}
-          </Text>
-        }
-        width={1000}
-        placement="right"
-        onClose={onDrawerClose}
-        open={openDrawer}
-        destroyOnHidden={true}
-        extra={
-          <Space>
-            <Button>
-              <Link to={getUrl()}>在单页面中打开</Link>
-            </Button>
-            <Button>
-              <Link to={getUrl("edit")}>翻译模式</Link>
-            </Button>
-          </Space>
-        }
-      >
-        <Article
-          active={true}
-          type={type as ArticleType}
-          book={book}
-          para={para}
-          channelId={channelId}
-          articleId={articleId}
-          anthologyId={anthologyId}
-          mode={mode}
-          onArticleEdit={(value: IArticleDataResponse) => {
-            setDrawerTitle(value.title_text);
-            if (typeof onArticleEdit !== "undefined") {
-              onArticleEdit(value);
-            }
-          }}
-        />
-      </Drawer>
-    </>
-  );
-};
-
-export default ArticleDrawerWidget;

+ 0 - 302
dashboard-v6/backup/components/article/ArticleEdit.tsx

@@ -1,302 +0,0 @@
-import { useRef, useState } from "react";
-
-import { useIntl } from "react-intl";
-import {
-  ProForm,
-  type ProFormInstance,
-  ProFormSwitch,
-  ProFormText,
-  ProFormTextArea,
-} from "@ant-design/pro-components";
-
-import { Alert, Button, Form, message, Result } from "antd";
-
-import { get, put } from "../../request";
-import type {
-  IArticleDataRequest,
-  IArticleDataResponse,
-  IArticleResponse,
-} from "../../api/Article";
-import LangSelect from "../general/LangSelect";
-import PublicitySelect from "../studio/PublicitySelect";
-
-import MDEditor from "@uiw/react-md-editor";
-import ArticlePrevDrawer from "./ArticlePrevDrawer";
-import type { IStudio } from "../auth/Studio";
-import ArticleEditTools from "./ArticleEditTools";
-import { useAppSelector } from "../../hooks";
-import { currentUser } from "../../reducers/current-user";
-import "./article.css";
-
-interface IFormData {
-  uid: string;
-  title: string;
-  subtitle: string;
-  summary?: string | null;
-  content?: string;
-  content_type?: string;
-  status: number;
-  lang: string;
-  to_tpl?: boolean;
-}
-
-interface IWidget {
-  studioName?: string;
-  articleId?: string;
-  anthologyId?: string;
-  resetButton?: "reset" | "cancel";
-  onReady?: (
-    title: string,
-    readonly: boolean,
-    studioName?: string,
-    parentId?: string
-  ) => void;
-  onChange?: (data: IArticleDataResponse) => void;
-  onCancel?: () => void;
-  onSubmit?: (data: IArticleDataResponse) => void;
-}
-
-const ArticleEditWidget = ({
-  studioName,
-  articleId,
-  anthologyId,
-  resetButton = "reset",
-  onReady,
-  onChange,
-  onCancel,
-  onSubmit,
-}: IWidget) => {
-  const intl = useIntl();
-  const [unauthorized, setUnauthorized] = useState(false);
-  const [readonly, setReadonly] = useState(false);
-  const [content, setContent] = useState<string>();
-  const [owner, setOwner] = useState<IStudio>();
-  const formRef = useRef<ProFormInstance | undefined>(undefined);
-  const [title, setTitle] = useState<string>();
-  const user = useAppSelector(currentUser);
-
-  return unauthorized ? (
-    <Result
-      status="403"
-      title="无权访问"
-      subTitle="您无权访问该内容。您可能没有登录,或者内容的所有者没有给您所需的权限。"
-      extra={<></>}
-    />
-  ) : (
-    <>
-      {readonly ? (
-        <Alert
-          message={`该资源为只读,如果需要修改,请联络拥有者${owner?.nickName}分配权限。`}
-          type="warning"
-          closable
-          action={
-            <Button disabled size="small" type="text">
-              详情
-            </Button>
-          }
-        />
-      ) : undefined}
-      <div style={{ display: "flex", justifyContent: "space-between" }}>
-        <span></span>
-        <ArticleEditTools
-          studioName={studioName}
-          articleId={articleId}
-          title={title}
-        />
-      </div>
-      <ProForm<IFormData>
-        formRef={formRef}
-        submitter={{
-          // 完全自定义整个区域
-          render: (props) => {
-            console.log(props);
-            return [
-              <Button
-                key="rest"
-                onClick={() => {
-                  if (resetButton === "reset") {
-                    props.form?.resetFields();
-                  } else {
-                    if (typeof onCancel !== "undefined") {
-                      onCancel();
-                    }
-                  }
-                }}
-              >
-                {resetButton === "reset" ? "重置" : "取消"}
-              </Button>,
-              <Button
-                type="primary"
-                key="submit"
-                onClick={() => props.form?.submit?.()}
-              >
-                提交
-              </Button>,
-            ];
-          },
-        }}
-        onFinish={async (values: IFormData) => {
-          const request: IArticleDataRequest = {
-            uid: articleId ? articleId : "",
-            title: values.title,
-            subtitle: values.subtitle,
-            summary: values.summary,
-            content: values.content,
-            content_type: "markdown",
-            status: values.status,
-            lang: values.lang,
-            to_tpl: values.to_tpl,
-            anthology_id: anthologyId,
-          };
-          const url = `/v2/article/${articleId}`;
-          console.info("save url", url, request);
-          put<IArticleDataRequest, IArticleResponse>(url, request)
-            .then((res) => {
-              console.debug("save response", res);
-              if (res.ok) {
-                if (typeof onChange !== "undefined") {
-                  onChange(res.data);
-                }
-                if (typeof onSubmit !== "undefined") {
-                  onSubmit(res.data);
-                }
-                formRef.current?.setFieldValue("content", res.data.content);
-                message.success(intl.formatMessage({ id: "flashes.success" }));
-              } else {
-                message.error(res.message);
-              }
-            })
-            .catch((e: IArticleResponse) => {
-              message.error(e.message);
-            });
-        }}
-        request={async () => {
-          const url = `/v2/article/${articleId}`;
-          console.info("url", url);
-          const res = await get<IArticleResponse>(url);
-          console.log("article", res);
-          let mTitle: string,
-            mReadonly = false;
-          if (res.ok) {
-            setOwner(res.data.studio);
-            mReadonly = res.data.role === "editor" ? false : true;
-            setReadonly(mReadonly);
-            mTitle = res.data.title;
-            setContent(res.data.content);
-            setTitle(res.data.title);
-          } else {
-            setUnauthorized(true);
-            mTitle = "无权访问";
-          }
-          if (typeof onReady !== "undefined") {
-            onReady(
-              mTitle,
-              mReadonly,
-              res.data.studio?.realName,
-              res.data.parent_uid
-            );
-          }
-          return {
-            uid: res.data.uid,
-            title: res.data.title,
-            subtitle: res.data.subtitle,
-            summary: res.data.summary,
-            content: res.data.content,
-            content_type: res.data.content_type,
-            lang: res.data.lang,
-            status: res.data.status,
-            studio: res.data.studio,
-          };
-        }}
-      >
-        <ProForm.Group>
-          <ProFormText
-            width="md"
-            name="title"
-            required
-            label={intl.formatMessage({
-              id: "forms.fields.title.label",
-            })}
-            rules={[
-              {
-                required: true,
-                message: intl.formatMessage({
-                  id: "forms.message.title.required",
-                }),
-              },
-            ]}
-          />
-          <ProFormText
-            width="md"
-            name="subtitle"
-            label={intl.formatMessage({
-              id: "forms.fields.subtitle.label",
-            })}
-          />
-        </ProForm.Group>
-        <ProForm.Group>
-          <LangSelect width="md" />
-          <PublicitySelect
-            width="md"
-            disable={["public_no_list"]}
-            readonly={
-              user?.roles?.includes("basic") || owner?.roles?.includes("basic")
-                ? true
-                : false
-            }
-          />
-        </ProForm.Group>
-        <ProForm.Group>
-          <ProFormTextArea
-            name="summary"
-            width="lg"
-            label={intl.formatMessage({
-              id: "forms.fields.summary.label",
-            })}
-          />
-        </ProForm.Group>
-
-        <Form.Item
-          name="content"
-          style={{ width: "100%" }}
-          label={
-            <>
-              {intl.formatMessage({
-                id: "forms.fields.content.label",
-              })}
-              {articleId ? (
-                <ArticlePrevDrawer
-                  trigger={<Button>预览</Button>}
-                  articleId={articleId}
-                  content={content}
-                />
-              ) : undefined}
-            </>
-          }
-        >
-          <MDEditor
-            className="pcd_md_editor paper_zh"
-            onChange={(value: unknown) => {
-              if (typeof value === "string") {
-                setContent(value);
-              }
-            }}
-            height={450}
-            minHeight={200}
-            style={{ width: "100%" }}
-          />
-        </Form.Item>
-
-        <ProForm.Group>
-          <ProFormSwitch
-            name="to_tpl"
-            label="转换为模版"
-            disabled={anthologyId ? false : true}
-          />
-        </ProForm.Group>
-      </ProForm>
-    </>
-  );
-};
-
-export default ArticleEditWidget;

+ 0 - 74
dashboard-v6/backup/components/article/ArticleEditDrawer.tsx

@@ -1,74 +0,0 @@
-import { Drawer } from "antd";
-import React, { useEffect, useState } from "react";
-import type { IArticleDataResponse } from "../../api/Article";
-
-import ArticleEdit from "./ArticleEdit";
-
-interface IWidget {
-  trigger?: React.ReactNode;
-  articleId?: string;
-  anthologyId?: string;
-  open?: boolean;
-  onClose?: Function;
-  onChange?: Function;
-}
-
-const ArticleEditDrawerWidget = ({
-  trigger,
-  articleId,
-  anthologyId,
-  open,
-  onClose,
-  onChange,
-}: IWidget) => {
-  const [openDrawer, setOpenDrawer] = useState(open);
-  const [title, setTitle] = useState("loading");
-  const [readonly, setReadonly] = useState(false);
-  const [_studioName, setStudioName] = useState<string>();
-
-  useEffect(() => setOpenDrawer(open), [open]);
-  const showDrawer = () => {
-    setOpenDrawer(true);
-  };
-
-  const onDrawerClose = () => {
-    setOpenDrawer(false);
-    if (document.getElementsByTagName("body")[0].hasAttribute("style")) {
-      document.getElementsByTagName("body")[0].removeAttribute("style");
-    }
-    if (typeof onClose !== "undefined") {
-      onClose();
-    }
-  };
-
-  return (
-    <>
-      <span onClick={() => showDrawer()}>{trigger}</span>
-      <Drawer
-        title={title + (readonly ? "(只读)" : "")}
-        width={1000}
-        placement="right"
-        onClose={onDrawerClose}
-        open={openDrawer}
-        destroyOnHidden={true}
-      >
-        <ArticleEdit
-          anthologyId={anthologyId}
-          articleId={articleId}
-          onReady={(title: string, readonly: boolean, studio?: string) => {
-            setTitle(title);
-            setReadonly(readonly);
-            setStudioName(studio);
-          }}
-          onChange={(data: IArticleDataResponse) => {
-            if (typeof onChange !== "undefined") {
-              onChange(data);
-            }
-          }}
-        />
-      </Drawer>
-    </>
-  );
-};
-
-export default ArticleEditDrawerWidget;

+ 0 - 59
dashboard-v6/backup/components/article/ArticleEditTools.tsx

@@ -1,59 +0,0 @@
-import { Link } from "react-router";
-import { useIntl } from "react-intl";
-import { TeamOutlined } from "@ant-design/icons";
-import { Button, Space } from "antd";
-
-import { ArticleTplModal } from "../template/Builder/ArticleTpl";
-import ShareModal from "../share/ShareModal";
-import { EResType } from "../share/Share";
-import AddToAnthology from "./AddToAnthology";
-import Builder from "../template/Builder/Builder";
-
-interface IWidget {
-  studioName?: string;
-  articleId?: string;
-  title?: string;
-}
-const ArticleEditToolsWidget = ({
-  studioName,
-  articleId,
-  title = "title",
-}: IWidget) => {
-  const intl = useIntl();
-  return (
-    <Space>
-      <Builder trigger={<Button type="link">{"<t>"}</Button>} />
-      {articleId ? (
-        <AddToAnthology
-          trigger={<Button type="link">加入文集</Button>}
-          studioName={studioName}
-          articleIds={[articleId]}
-        />
-      ) : undefined}
-      {articleId ? (
-        <ShareModal
-          trigger={
-            <Button type="link" icon={<TeamOutlined />}>
-              {intl.formatMessage({
-                id: "buttons.share",
-              })}
-            </Button>
-          }
-          resId={articleId}
-          resType={EResType.article}
-        />
-      ) : undefined}
-      <Link to={`/article/article/${articleId}`} target="_blank">
-        {intl.formatMessage({ id: "buttons.open.in.tab" })}
-      </Link>
-      <ArticleTplModal
-        title={title}
-        type="article"
-        articleId={articleId}
-        trigger={<Button type="link">获取模版</Button>}
-      />
-    </Space>
-  );
-};
-
-export default ArticleEditToolsWidget;

+ 0 - 553
dashboard-v6/backup/components/article/ArticleList.tsx

@@ -1,553 +0,0 @@
-import { Link } from "react-router";
-import { useIntl } from "react-intl";
-import {
-  Button,
-  Popover,
-  Dropdown,
-  Typography,
-  Modal,
-  message,
-  Space,
-  Table,
-  Badge,
-} from "antd";
-import { type ActionType, ProTable } from "@ant-design/pro-components";
-import {
-  PlusOutlined,
-  DeleteOutlined,
-  TeamOutlined,
-  ExclamationCircleOutlined,
-  FolderAddOutlined,
-  ReconciliationOutlined,
-} from "@ant-design/icons";
-
-import ArticleCreate from "./ArticleCreate";
-import { delete_, get } from "../../request";
-import type { IArticleListResponse, IDeleteResponse } from "../../api/Article";
-import { PublicityValueEnum } from "../studio/table";
-import { useEffect, useRef, useState } from "react";
-import { ArticleTplModal } from "../template/Builder/ArticleTpl";
-import Share, { EResType } from "../share/Share";
-import AddToAnthology from "./AddToAnthology";
-import AnthologySelect from "../anthology/AnthologySelect";
-import StudioName, { type IStudio } from "../auth/Studio";
-import type { IUser } from "../auth/User";
-import { getSorterUrl } from "../../utils";
-import TransferCreate from "../transfer/TransferCreate";
-import { TransferOutLinedIcon } from "../../assets/icon";
-
-const { Text } = Typography;
-
-interface IArticleNumberResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    my: number;
-    collaboration: number;
-  };
-}
-
-const renderBadge = (count: number, active = false) => {
-  return (
-    <Badge
-      count={count}
-      style={{
-        marginBlockStart: -2,
-        marginInlineStart: 4,
-        color: active ? "#1890FF" : "#999",
-        backgroundColor: active ? "#E6F7FF" : "#eee",
-      }}
-    />
-  );
-};
-
-interface DataItem {
-  sn: number;
-  id: string;
-  title: string;
-  subtitle: string;
-  summary?: string | null;
-  anthologyCount?: number;
-  anthologyTitle?: string;
-  publicity: number;
-  studio?: IStudio;
-  editor?: IUser;
-  updated_at?: string;
-}
-
-interface IWidget {
-  studioName?: string;
-  editable?: boolean;
-  multiple?: boolean;
-  onSelect?: (
-    id: string,
-    title: string,
-    event: React.MouseEvent<HTMLElement, MouseEvent>
-  ) => void;
-}
-const ArticleListWidget = ({
-  studioName,
-  multiple = true,
-  editable = false,
-  onSelect,
-}: IWidget) => {
-  const intl = useIntl(); //i18n
-  const [openCreate, setOpenCreate] = useState(false);
-  const [anthologyId, setAnthologyId] = useState<string>();
-  const [activeKey, setActiveKey] = useState<React.Key | undefined>("my");
-  const [myNumber, setMyNumber] = useState<number>(0);
-  const [collaborationNumber, setCollaborationNumber] = useState<number>(0);
-  const [transfer, setTransfer] = useState<string[]>();
-  const [transferName, setTransferName] = useState<string>();
-  const [transferOpen, setTransferOpen] = useState(false);
-  const [pageSize, setPageSize] = useState(10);
-
-  useEffect(() => {
-    /**
-     * 获取各种课程的数量
-     */
-    const url = `/v2/article-my-number?studio=${studioName}`;
-    console.log("url", url);
-    get<IArticleNumberResponse>(url).then((json) => {
-      if (json.ok) {
-        setMyNumber(json.data.my);
-        setCollaborationNumber(json.data.collaboration);
-      }
-    });
-  }, [studioName]);
-
-  const showDeleteConfirm = (id: string, title: string) => {
-    Modal.confirm({
-      icon: <ExclamationCircleOutlined />,
-      title:
-        intl.formatMessage({
-          id: "message.delete.confirm",
-        }) +
-        intl.formatMessage({
-          id: "message.irrevocable",
-        }),
-
-      content: title,
-      okText: intl.formatMessage({
-        id: "buttons.delete",
-      }),
-      okType: "danger",
-      cancelText: intl.formatMessage({
-        id: "buttons.no",
-      }),
-      onOk() {
-        console.log("delete", id);
-        return delete_<IDeleteResponse>(`/v2/article/${id}`)
-          .then((json) => {
-            if (json.ok) {
-              message.success("删除成功");
-              ref.current?.reload();
-            } else {
-              message.error(json.message);
-            }
-          })
-          .catch((e) => console.log("Oops errors!", e));
-      },
-    });
-  };
-  const ref = useRef<ActionType | null>(null);
-
-  const [isModalOpen, setIsModalOpen] = useState(false);
-  const [shareResId, setShareResId] = useState<string>("");
-  const [shareResType, setShareResType] = useState<EResType>(EResType.article);
-  const showShareModal = (resId: string, resType: EResType) => {
-    setShareResId(resId);
-    setShareResType(resType);
-    setIsModalOpen(true);
-  };
-
-  const handleOk = () => {
-    setIsModalOpen(false);
-  };
-
-  const handleCancel = () => {
-    setIsModalOpen(false);
-  };
-
-  return (
-    <>
-      <ProTable<DataItem>
-        actionRef={ref}
-        columns={[
-          {
-            title: intl.formatMessage({
-              id: "dict.fields.sn.label",
-            }),
-            dataIndex: "sn",
-            key: "sn",
-            width: 50,
-            search: false,
-          },
-          {
-            title: intl.formatMessage({
-              id: "forms.fields.title.label",
-            }),
-            dataIndex: "title",
-            key: "title",
-            tooltip: "过长会自动收缩",
-            ellipsis: true,
-            render: (_text, row) => {
-              return (
-                <>
-                  <div key={1}>
-                    <Typography.Link
-                      onClick={(
-                        event: React.MouseEvent<HTMLElement, MouseEvent>
-                      ) => {
-                        if (typeof onSelect !== "undefined") {
-                          onSelect(row.id, row.title, event);
-                        }
-                      }}
-                    >
-                      {row.title}
-                    </Typography.Link>
-                  </div>
-                  <div key={2}>
-                    <Text type="secondary">{row.subtitle}</Text>
-                  </div>
-                  {activeKey !== "my" ? (
-                    <div key={3}>
-                      <Text type="secondary">
-                        <StudioName data={row.studio} />
-                      </Text>
-                    </div>
-                  ) : undefined}
-                </>
-              );
-            },
-          },
-          {
-            title: intl.formatMessage({
-              id: "columns.library.anthology.title",
-            }),
-            dataIndex: "subtitle",
-            key: "subtitle",
-            render: (_text, row) => {
-              return (
-                <Space>
-                  {row.anthologyTitle}
-                  {row.anthologyCount ? (
-                    <Badge color="geekblue" count={row.anthologyCount} />
-                  ) : undefined}
-                </Space>
-              );
-            },
-          },
-          {
-            title: intl.formatMessage({
-              id: "forms.fields.summary.label",
-            }),
-            dataIndex: "summary",
-            key: "summary",
-            tooltip: "过长会自动收缩",
-            ellipsis: true,
-          },
-          {
-            title: intl.formatMessage({
-              id: "forms.fields.publicity.label",
-            }),
-            dataIndex: "publicity",
-            key: "publicity",
-            width: 100,
-            search: false,
-            filters: true,
-            onFilter: true,
-            valueEnum: PublicityValueEnum(),
-          },
-          {
-            title: intl.formatMessage({
-              id: "forms.fields.updated-at.label",
-            }),
-            key: "updated_at",
-            width: 100,
-            search: false,
-            dataIndex: "updated_at",
-            valueType: "date",
-            sorter: true,
-          },
-          {
-            title: intl.formatMessage({ id: "buttons.option" }),
-            key: "option",
-            width: 120,
-            valueType: "option",
-            hideInTable: !editable,
-            render: (_text, row, index) => {
-              return [
-                <Dropdown.Button
-                  trigger={["click", "contextMenu"]}
-                  key={index}
-                  type="link"
-                  menu={{
-                    items: [
-                      {
-                        key: "tpl",
-                        label: (
-                          <ArticleTplModal
-                            title={row.title}
-                            type="article"
-                            articleId={row.id}
-                            trigger={<>模版</>}
-                          />
-                        ),
-                        icon: <ReconciliationOutlined />,
-                      },
-                      {
-                        key: "share",
-                        label: intl.formatMessage({
-                          id: "buttons.share",
-                        }),
-                        icon: <TeamOutlined />,
-                      },
-                      {
-                        key: "addToAnthology",
-                        label: (
-                          <AddToAnthology
-                            trigger={<Button type="link">加入文集</Button>}
-                            studioName={studioName}
-                            articleIds={[row.id]}
-                          />
-                        ),
-                        icon: <FolderAddOutlined />,
-                      },
-                      {
-                        key: "transfer",
-                        label: intl.formatMessage({
-                          id: "columns.studio.transfer.title",
-                        }),
-                        icon: <TransferOutLinedIcon />,
-                      },
-                      {
-                        key: "remove",
-                        label: intl.formatMessage({
-                          id: "buttons.delete",
-                        }),
-                        icon: <DeleteOutlined />,
-                        danger: true,
-                      },
-                    ],
-                    onClick: (e) => {
-                      switch (e.key) {
-                        case "share":
-                          showShareModal(row.id, EResType.article);
-                          break;
-                        case "remove":
-                          showDeleteConfirm(row.id, row.title);
-                          break;
-                        case "transfer":
-                          setTransfer([row.id]);
-                          setTransferName(row.title);
-                          setTransferOpen(true);
-                          break;
-                        default:
-                          break;
-                      }
-                    },
-                  }}
-                >
-                  <Link
-                    key={index}
-                    to={`/article/article/${row.id}`}
-                    target="_blank"
-                  >
-                    {intl.formatMessage({
-                      id: "buttons.view",
-                    })}
-                  </Link>
-                </Dropdown.Button>,
-              ];
-            },
-          },
-        ]}
-        rowSelection={
-          multiple
-            ? {
-                // 自定义选择项参考: https://ant.design/components/table-cn/#components-table-demo-row-selection-custom
-                // 注释该行则默认不显示下拉选项
-                selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
-              }
-            : undefined
-        }
-        tableAlertRender={({ selectedRowKeys, onCleanSelected }) => (
-          <Space size={24}>
-            <span>
-              {intl.formatMessage({ id: "buttons.selected" })}
-              {selectedRowKeys.length}
-              <Button type="link" onClick={onCleanSelected}>
-                {intl.formatMessage({ id: "buttons.unselect" })}
-              </Button>
-            </span>
-          </Space>
-        )}
-        tableAlertOptionRender={({ selectedRowKeys, onCleanSelected }) => {
-          return (
-            <Space>
-              <Button
-                type="link"
-                onClick={() => {
-                  const resId = selectedRowKeys.map((item) => item.toString());
-                  setTransfer(resId);
-                  setTransferName(resId.length + "个文章");
-                  setTransferOpen(true);
-                }}
-              >
-                转让
-              </Button>
-              <AddToAnthology
-                studioName={studioName}
-                trigger={<Button type="link">加入文集</Button>}
-                articleIds={selectedRowKeys.map((item) => item.toString())}
-                onFinally={() => {
-                  onCleanSelected();
-                }}
-              />
-            </Space>
-          );
-        }}
-        request={async (params = {}, sorter) => {
-          let url = `/v2/article?view=studio&view2=${activeKey}&name=${studioName}`;
-          const offset =
-            ((params.current ? params.current : 1) - 1) *
-            (params.pageSize ? params.pageSize : pageSize);
-          if (params.pageSize) {
-            setPageSize(params.pageSize);
-          }
-          url += `&limit=${params.pageSize}&offset=${offset}`;
-          url += params.keyword ? "&search=" + params.keyword : "";
-
-          if (typeof anthologyId !== "undefined") {
-            url += "&anthology=" + anthologyId;
-          }
-
-          url += getSorterUrl(sorter);
-          console.log("url", url);
-          const res = await get<IArticleListResponse>(url);
-          const items: DataItem[] = res.data.rows.map((item, id) => {
-            return {
-              sn: id + offset + 1,
-              id: item.uid,
-              title: item.title,
-              subtitle: item.subtitle,
-              summary: item.summary,
-              anthologyCount: item.anthology_count,
-              anthologyTitle: item.anthology_first?.title,
-              publicity: item.status,
-              updated_at: item.updated_at,
-              studio: item.studio,
-              editor: item.editor,
-            };
-          });
-          return {
-            total: res.data.count,
-            succcess: true,
-            data: items,
-          };
-        }}
-        rowKey="id"
-        bordered
-        pagination={{
-          showQuickJumper: true,
-          showSizeChanger: true,
-          pageSize: pageSize,
-        }}
-        search={false}
-        options={{
-          search: true,
-        }}
-        toolBarRender={() => [
-          activeKey === "my" ? (
-            <AnthologySelect
-              studioName={studioName}
-              onSelect={(value: string) => {
-                setAnthologyId(value);
-                ref.current?.reload();
-              }}
-            />
-          ) : undefined,
-          <Popover
-            content={
-              <ArticleCreate
-                studio={studioName}
-                anthologyId={anthologyId}
-                onSuccess={() => {
-                  setOpenCreate(false);
-                  ref.current?.reload();
-                }}
-              />
-            }
-            placement="bottomRight"
-            trigger="click"
-            open={openCreate}
-            onOpenChange={(open: boolean) => {
-              setOpenCreate(open);
-            }}
-          >
-            <Button key="button" icon={<PlusOutlined />} type="primary">
-              {intl.formatMessage({ id: "buttons.create" })}
-            </Button>
-          </Popover>,
-        ]}
-        toolbar={{
-          menu: {
-            activeKey,
-            items: [
-              {
-                key: "my",
-                label: (
-                  <span>
-                    {intl.formatMessage({ id: "labels.this-studio" })}
-                    {renderBadge(myNumber, activeKey === "my")}
-                  </span>
-                ),
-              },
-              {
-                key: "collaboration",
-                label: (
-                  <span>
-                    {intl.formatMessage({ id: "labels.collaboration" })}
-                    {renderBadge(
-                      collaborationNumber,
-                      activeKey === "collaboration"
-                    )}
-                  </span>
-                ),
-              },
-            ],
-            onChange(key) {
-              console.log("show course", key);
-              setActiveKey(key);
-              setAnthologyId(undefined);
-              ref.current?.reload();
-            },
-          },
-        }}
-      />
-
-      <Modal
-        destroyOnHidden={true}
-        width={700}
-        title={intl.formatMessage({ id: "labels.collaboration" })}
-        open={isModalOpen}
-        onOk={handleOk}
-        onCancel={handleCancel}
-      >
-        <Share resId={shareResId} resType={shareResType} />
-      </Modal>
-
-      <TransferCreate
-        studioName={studioName}
-        resId={transfer}
-        resType="article"
-        resName={transferName}
-        open={transferOpen}
-        onOpenChange={(visible: boolean) => setTransferOpen(visible)}
-      />
-    </>
-  );
-};
-
-export default ArticleListWidget;

+ 0 - 58
dashboard-v6/backup/components/article/ArticleListModal.tsx

@@ -1,58 +0,0 @@
-import { useState } from "react";
-import { Modal } from "antd";
-
-import ArticleList from "./ArticleList";
-
-interface IWidget {
-  studioName?: string;
-  trigger?: React.ReactNode;
-  multiple?: boolean;
-  onSelect?: Function;
-}
-const ArticleListModalWidget = ({
-  studioName,
-  trigger = "Article",
-  multiple = true,
-  onSelect,
-}: IWidget) => {
-  const [isModalOpen, setIsModalOpen] = useState(false);
-
-  const showModal = () => {
-    setIsModalOpen(true);
-  };
-
-  const handleOk = () => {
-    setIsModalOpen(false);
-  };
-
-  const handleCancel = () => {
-    setIsModalOpen(false);
-  };
-
-  return (
-    <>
-      <span onClick={showModal}>{trigger}</span>
-      <Modal
-        width={"80%"}
-        title="文章列表"
-        open={isModalOpen}
-        onOk={handleOk}
-        onCancel={handleCancel}
-      >
-        <ArticleList
-          studioName={studioName}
-          editable={false}
-          multiple={multiple}
-          onSelect={(id: string, title: string) => {
-            if (typeof onSelect !== "undefined") {
-              onSelect(id, title);
-            }
-            handleOk();
-          }}
-        />
-      </Modal>
-    </>
-  );
-};
-
-export default ArticleListModalWidget;

+ 0 - 109
dashboard-v6/backup/components/article/ArticleListPublic.tsx

@@ -1,109 +0,0 @@
-import { Link } from "react-router";
-import { useRef } from "react";
-import { Space } from "antd";
-import { type ActionType, ProList } from "@ant-design/pro-components";
-
-import { get } from "../../request";
-import type { IArticleListResponse } from "../../api/Article";
-
-import type { IStudio } from "../auth/Studio";
-import type { IUser } from "../auth/User";
-import TimeShow from "../general/TimeShow";
-
-interface DataItem {
-  sn: number;
-  id: string;
-  title: string;
-  subtitle: string;
-  summary?: string | null;
-  anthologyCount?: number;
-  anthologyTitle?: string;
-  publicity: number;
-  createdAt?: string;
-  updatedAt: string;
-  studio?: IStudio;
-  editor?: IUser;
-}
-
-interface IWidget {
-  search?: string;
-  studioName?: string;
-}
-const ArticleListWidget = ({ studioName }: IWidget) => {
-  const ref = useRef<ActionType | null>(null);
-
-  return (
-    <>
-      <ProList<DataItem>
-        rowKey="id"
-        actionRef={ref}
-        metas={{
-          title: {
-            render: (_text, row) => {
-              return <Link to={`/article/article/${row.id}`}>{row.title}</Link>;
-            },
-          },
-          description: {
-            dataIndex: "summary",
-          },
-          subTitle: {
-            render: (_text, row) => {
-              return (
-                <Space>
-                  {row.editor?.nickName}
-                  <TimeShow
-                    updatedAt={row.updatedAt}
-                    showLabel={false}
-                    showIcon={false}
-                  />
-                </Space>
-              );
-            },
-          },
-        }}
-        request={async (params = {}) => {
-          let url = `/v2/article?view=public`;
-          const offset =
-            ((params.current ? params.current : 1) - 1) *
-            (params.pageSize ? params.pageSize : 20);
-          url += `&limit=${params.pageSize}&offset=${offset}`;
-          url += params.keyword ? "&search=" + params.keyword : "";
-          url += studioName ? "&studio=" + studioName : "";
-          const res = await get<IArticleListResponse>(url);
-          const items: DataItem[] = res.data.rows.map((item, id) => {
-            return {
-              sn: id + 1,
-              id: item.uid,
-              title: item.title,
-              subtitle: item.subtitle,
-              summary: item.summary,
-              anthologyCount: item.anthology_count,
-              anthologyTitle: item.anthology_first?.title,
-              publicity: item.status,
-              updatedAt: item.updated_at,
-              studio: item.studio,
-              editor: item.editor,
-            };
-          });
-          return {
-            total: res.data.count,
-            succcess: true,
-            data: items,
-          };
-        }}
-        bordered
-        pagination={{
-          showQuickJumper: true,
-          showSizeChanger: true,
-          pageSize: 20,
-        }}
-        search={false}
-        options={{
-          search: true,
-        }}
-      />
-    </>
-  );
-};
-
-export default ArticleListWidget;

+ 0 - 94
dashboard-v6/backup/components/article/ArticlePrevDrawer.tsx

@@ -1,94 +0,0 @@
-import { Drawer, Typography } from "antd";
-import React, { useEffect, useState } from "react";
-
-import ArticleView from "./ArticleView";
-import {
-  IArticleDataResponse,
-  IArticleResponse,
-} from "../../../src/api/Article";
-import { put } from "../../../src/request";
-
-const { Paragraph } = Typography;
-
-interface IArticlePrevRequest {
-  content: string;
-}
-interface IWidget {
-  trigger?: React.ReactNode;
-  title?: React.ReactNode;
-  content?: string;
-  articleId: string;
-}
-
-const ArticlePrevDrawerWidget = ({
-  trigger,
-  title,
-  content,
-  articleId,
-}: IWidget) => {
-  const [articleData, setArticleData] = useState<IArticleDataResponse>();
-  const [open, setOpen] = useState(false);
-  const [errorMsg, setErrorMsg] = useState<string>();
-
-  const showDrawer = () => {
-    setOpen(true);
-  };
-
-  const onClose = () => {
-    setOpen(false);
-  };
-
-  useEffect(() => {
-    put<IArticlePrevRequest, IArticleResponse>(
-      `/v2/article-preview/${articleId}`,
-      {
-        content: content ? content : "",
-      }
-    )
-      .then((res) => {
-        console.log("save response", res);
-        if (res.ok) {
-          setArticleData(res.data);
-        } else {
-          setErrorMsg(res.message);
-        }
-      })
-      .catch((e: IArticleResponse) => {
-        setErrorMsg(e.message);
-      });
-  }, [articleId, content]);
-
-  return (
-    <>
-      <span onClick={() => showDrawer()}>{trigger}</span>
-      <Drawer
-        title={title}
-        width={900}
-        placement="right"
-        onClose={onClose}
-        open={open}
-        destroyOnHidden={true}
-      >
-        <Paragraph type="danger">{errorMsg}</Paragraph>
-        {articleData ? (
-          <ArticleView
-            id={articleData.uid}
-            title={articleData.title}
-            subTitle={articleData.subtitle}
-            summary={articleData.summary}
-            content={articleData.content ? articleData.content : ""}
-            html={articleData.html ? [articleData.html] : []}
-            path={articleData.path}
-            created_at={articleData.created_at}
-            updated_at={articleData.updated_at}
-            articleId={articleId}
-          />
-        ) : (
-          <></>
-        )}
-      </Drawer>
-    </>
-  );
-};
-
-export default ArticlePrevDrawerWidget;

+ 0 - 13
dashboard-v6/backup/components/article/ArticleSkeleton.tsx

@@ -1,13 +0,0 @@
-import { Divider, Skeleton } from "antd";
-
-const ArticleSkeletonWidget = () => {
-  return (
-    <div style={{ paddingTop: "1em" }}>
-      <Skeleton title={{ width: 200 }} paragraph={{ rows: 1 }} active />
-      <Divider />
-      <Skeleton title={{ width: 200 }} paragraph={{ rows: 10 }} active />
-    </div>
-  );
-};
-
-export default ArticleSkeletonWidget;

+ 0 - 153
dashboard-v6/backup/components/article/ArticleView.tsx

@@ -1,153 +0,0 @@
-import { Typography, Divider, Skeleton, Space } from "antd";
-
-import MdView from "../template/MdView";
-import TocPath, {
-  type ITocPathNode,
-} from "../../../src/components/tipitaka/TocPath";
-import PaliChapterChannelList from "../../../src/components/tipitaka/PaliChapterChannelList";
-import type { ArticleMode, ArticleType } from "./Article";
-import VisibleObserver from "../general/VisibleObserver";
-import type { IStudio } from "../auth/Studio";
-
-const { Paragraph, Title, Text } = Typography;
-export interface IFirstAnthology {
-  id: string;
-  title: string;
-  count: number;
-}
-export interface IWidgetArticleData {
-  id?: string;
-  title?: string;
-  subTitle?: string;
-  summary?: string | null;
-  content?: string;
-  html?: string[];
-  path?: ITocPathNode[];
-  mode?: ArticleMode | null;
-  created_at?: string;
-  updated_at?: string;
-  owner?: IStudio;
-  channels?: string[];
-  type?: ArticleType;
-  articleId?: string;
-  remains?: boolean;
-  anthology?: IFirstAnthology;
-  hideTitle?: boolean;
-  onEnd?: () => void;
-  onPathChange?: (
-    node: ITocPathNode,
-    e: React.MouseEvent<HTMLSpanElement | HTMLAnchorElement, MouseEvent>
-  ) => void;
-}
-
-const ArticleViewWidget = ({
-  title = "",
-  subTitle,
-  summary,
-  content,
-  html = [],
-  path = [],
-  channels,
-  type,
-  articleId,
-  hideTitle,
-  onEnd,
-  remains,
-  onPathChange,
-}: IWidgetArticleData) => {
-  console.log("ArticleViewWidget render");
-
-  let currChannelList = <></>;
-  switch (type) {
-    case "chapter": {
-      const chapterProps = articleId?.split("-");
-      if (Array.isArray(chapterProps) && chapterProps.length >= 2) {
-        const book = Number(chapterProps[0]);
-        const para = Number(chapterProps[1]);
-
-        if (!Number.isNaN(book) && !Number.isNaN(para)) {
-          currChannelList = (
-            <PaliChapterChannelList
-              para={{ book, para }}
-              channelId={channels}
-              openTarget="_self"
-            />
-          );
-        }
-      }
-
-      break;
-    }
-
-    default:
-      break;
-  }
-
-  return (
-    <>
-      <Space orientation="vertical">
-        {hideTitle ? (
-          <></>
-        ) : (
-          <TocPath
-            data={path}
-            channels={channels}
-            onChange={(
-              node: ITocPathNode,
-              e: React.MouseEvent<
-                HTMLSpanElement | HTMLAnchorElement,
-                MouseEvent
-              >
-            ) => {
-              if (typeof onPathChange !== "undefined") {
-                onPathChange(node, e);
-              }
-            }}
-          />
-        )}
-        {hideTitle ? (
-          <></>
-        ) : (
-          <Title level={4}>
-            <div
-              dangerouslySetInnerHTML={{
-                __html: title ? title : "",
-              }}
-            />
-          </Title>
-        )}
-
-        <Text type="secondary">{subTitle}</Text>
-        {currChannelList}
-        <Paragraph ellipsis={{ rows: 2, expandable: true, symbol: "more" }}>
-          {summary}
-        </Paragraph>
-        <Divider />
-      </Space>
-      {html
-        ? html.map((item, id) => {
-            return (
-              <div key={id}>
-                <MdView className="pcd_article paper paper_zh" html={item} />
-              </div>
-            );
-          })
-        : content}
-      {remains ? (
-        <>
-          <VisibleObserver
-            onVisible={(visible: boolean) => {
-              console.log("visible", visible);
-              if (visible && typeof onEnd !== "undefined") {
-                onEnd();
-              }
-            }}
-          />
-          <Skeleton title={{ width: 200 }} paragraph={{ rows: 5 }} active />
-        </>
-      ) : undefined}
-    </>
-  );
-};
-
-export default ArticleViewWidget;

+ 0 - 73
dashboard-v6/backup/components/article/ChapterToc.tsx

@@ -1,73 +0,0 @@
-import type { Key } from "antd/lib/table/interface";
-import { useState, useEffect } from "react";
-
-import { get } from "../../request";
-import type { IChapterToc, IChapterTocListResponse } from "../../api/Corpus";
-import type { ListNodeData } from "./EditableTree";
-import TocTree from "./TocTree";
-import { Skeleton } from "antd";
-
-interface IWidget {
-  book?: number;
-  para?: number;
-  maxLevel?: number;
-  onSelect?: (selectedKeys: Key[]) => void;
-  onData?: (data: IChapterToc[]) => void;
-}
-const ChapterTocWidget = ({
-  book,
-  para,
-  maxLevel = 8,
-  onSelect,
-  onData,
-}: IWidget) => {
-  const [tocList, setTocList] = useState<ListNodeData[]>([]);
-  const [loading, setLoading] = useState(true);
-  useEffect(() => {
-    const url = `/v2/chapter?view=toc&book=${book}&para=${para}`;
-    setLoading(true);
-    console.info("api request", url);
-    get<IChapterTocListResponse>(url)
-      .then((json) => {
-        console.info("api response", json);
-        const chapters = json.data.rows.filter(
-          (value) => value.level <= maxLevel
-        );
-        onData && onData(chapters);
-        const toc = chapters.map((item, _id) => {
-          return {
-            key: `${item.book}-${item.paragraph}`,
-            title: item.text,
-            level: item.level,
-          };
-        });
-        setTocList(toc);
-        if (chapters.length > 0) {
-          const path: string[] = [];
-          for (let index = chapters.length - 1; index >= 0; index--) {
-            const element = chapters[index];
-            if (element.book === book && para && element.paragraph <= para) {
-              path.push(`${element.book}-${element.paragraph}`);
-              break;
-            }
-          }
-        }
-      })
-      .finally(() => setLoading(false));
-  }, [book, maxLevel, para]);
-
-  return loading ? (
-    <Skeleton active />
-  ) : (
-    <TocTree
-      treeData={tocList}
-      onSelect={(selectedKeys: Key[]) => {
-        if (typeof onSelect !== "undefined") {
-          onSelect && onSelect(selectedKeys);
-        }
-      }}
-    />
-  );
-};
-
-export default ChapterTocWidget;

+ 0 - 463
dashboard-v6/backup/components/article/EditableTree.tsx

@@ -1,463 +0,0 @@
-import React, { useState } from "react";
-import { useEffect } from "react";
-import { message, Modal, Tree } from "antd";
-import type { DataNode, TreeProps } from "antd/es/tree";
-import type { Key } from "antd/lib/table/interface";
-import { DeleteOutlined, SaveOutlined } from "@ant-design/icons";
-import { FileAddOutlined, LinkOutlined } from "@ant-design/icons";
-
-import { Button, Divider, Space } from "antd";
-import { useIntl } from "react-intl";
-import EditableTreeNode from "./EditableTreeNode";
-import { randomString } from "../../utils";
-
-export interface TreeNodeData {
-  key: string;
-  id: string;
-  title: string | React.ReactNode;
-  title_text?: string;
-  icon?: React.ReactNode;
-  children: TreeNodeData[];
-  status?: number;
-  deletedAt?: string | null;
-  level: number;
-}
-export type ListNodeData = {
-  key: string;
-  title: string | React.ReactNode;
-  title_text?: string;
-  level: number;
-  status?: number;
-  children?: number;
-  deletedAt?: string | null;
-};
-
-let tocActivePath: TreeNodeData[] = [];
-function tocGetTreeData(articles: ListNodeData[], active = "") {
-  const treeData = [];
-
-  const treeParents = [];
-
-  const rootNode: TreeNodeData = {
-    key: randomString(),
-    id: "0",
-    title: "root",
-    title_text: "root",
-    level: 0,
-    children: [],
-  };
-  treeData.push(rootNode);
-  let lastInsNode: TreeNodeData = rootNode;
-
-  let iCurrLevel = 0;
-  const keys: string[] = [];
-  for (let index = 0; index < articles.length; index++) {
-    const element = articles[index];
-
-    const newNode: TreeNodeData = {
-      key: randomString(),
-      id: element.key,
-      title: element.title,
-      title_text: element.title_text,
-      children: [],
-      icon: keys.includes(element.key) ? <LinkOutlined /> : undefined,
-      status: element.status,
-      level: element.level,
-      deletedAt: element.deletedAt,
-    };
-    if (!keys.includes(element.key)) {
-      keys.push(element.key);
-    }
-    /*
-		if (active == element.article) {
-			newNode["extraClasses"] = "active";
-		}
-*/
-    if (newNode.level > iCurrLevel) {
-      //新的层级比较大,为上一个的子目录
-      treeParents.push(lastInsNode);
-      lastInsNode.children.push(newNode);
-    } else if (newNode.level === iCurrLevel) {
-      //目录层级相同,为平级
-      treeParents[treeParents.length - 1].children.push(newNode);
-    } else {
-      // 小于 挂在上一个层级
-      while (treeParents.length > 1) {
-        treeParents.pop();
-        if (treeParents[treeParents.length - 1].level < newNode.level) {
-          break;
-        }
-      }
-      treeParents[treeParents.length - 1].children.push(newNode);
-    }
-    lastInsNode = newNode;
-    iCurrLevel = newNode.level;
-
-    if (active === element.key) {
-      tocActivePath = [];
-      for (let index = 1; index < treeParents.length; index++) {
-        tocActivePath.push(treeParents[index]);
-      }
-    }
-  }
-  return treeData[0].children;
-}
-
-function treeToList(treeNode: TreeNodeData[]): ListNodeData[] {
-  let iTocTreeCurrLevel = 1;
-
-  const arrTocTree: ListNodeData[] = [];
-
-  for (const iterator of treeNode) {
-    getTreeNodeData(iterator);
-  }
-
-  function getTreeNodeData(node: TreeNodeData) {
-    let children = 0;
-    if (typeof node.children != "undefined") {
-      children = node.children.length;
-    }
-    arrTocTree.push({
-      key: node.id,
-      title: node.title,
-      title_text: node.title_text,
-      level: iTocTreeCurrLevel,
-      children: children,
-      deletedAt: node.deletedAt,
-    });
-    if (children > 0) {
-      iTocTreeCurrLevel++;
-      for (const iterator of node.children) {
-        getTreeNodeData(iterator);
-      }
-      iTocTreeCurrLevel--;
-    }
-  }
-
-  return arrTocTree;
-}
-interface IWidget {
-  treeData: ListNodeData[];
-  addFileButton?: React.ReactNode;
-  addOnArticle?: TreeNodeData;
-  updatedNode?: TreeNodeData;
-  onChange?: Function;
-  onSelect?: Function;
-  onSave?: Function;
-  onAddFile?: Function;
-  onAppend?: Function;
-  onTitleClick?: Function;
-}
-const EditableTreeWidget = ({
-  treeData,
-  addFileButton,
-  addOnArticle,
-  updatedNode,
-  onChange,
-  onSelect,
-  onSave,
-  onAppend,
-  onTitleClick,
-}: IWidget) => {
-  const intl = useIntl();
-  const [checkKeys, setCheckKeys] = useState<string[]>([]);
-  const [checkNodes, setCheckNodes] = useState<TreeNodeData[]>([]);
-  const [gData, setGData] = useState<TreeNodeData[]>([]);
-  const [listTreeData, setListTreeData] = useState<ListNodeData[]>();
-  const [keys, setKeys] = useState<Key>("");
-
-  useEffect(() => {
-    if (typeof onChange !== "undefined") {
-      onChange(listTreeData);
-    }
-  }, [listTreeData]);
-
-  useEffect(() => {
-    //找到节点并更新
-    if (typeof updatedNode === "undefined") {
-      return;
-    }
-    const update = (_node: TreeNodeData[]) => {
-      _node.forEach((value, index, array) => {
-        if (value.id === updatedNode.id) {
-          array[index].title = updatedNode.title;
-          array[index].title_text = updatedNode.title_text;
-          console.log("key found");
-          return;
-        } else {
-          update(array[index].children);
-        }
-        return;
-      });
-    };
-    const newTree = [...gData];
-    update(newTree);
-    setGData(newTree);
-    const list = treeToList(newTree);
-    setListTreeData(list);
-  }, [updatedNode]);
-
-  const appendNode = (key: string, node: TreeNodeData) => {
-    console.log("key", key);
-    const append = (_node: TreeNodeData[]) => {
-      _node.forEach((value, index, array) => {
-        if (value.key === key) {
-          array[index].children.push(node);
-          console.log("key found");
-          return;
-        } else {
-          append(array[index].children);
-        }
-        return;
-      });
-    };
-    const newTree = [...gData];
-    append(newTree);
-    setGData(newTree);
-    const list = treeToList(newTree);
-    setListTreeData(list);
-  };
-
-  useEffect(() => {
-    if (typeof addOnArticle === "undefined") {
-      return;
-    }
-    console.log("add ", addOnArticle);
-
-    const newTreeData = [...gData, addOnArticle];
-    setGData(newTreeData);
-    const list = treeToList(newTreeData);
-    setListTreeData(list);
-  }, [addOnArticle]);
-
-  useEffect(() => {
-    const data = tocGetTreeData(treeData);
-    console.log("tree data", data);
-    setGData(data);
-  }, [treeData]);
-
-  const onCheck: TreeProps["onCheck"] = (checkedKeys, info) => {
-    console.log("onCheck", checkedKeys, info);
-    setCheckKeys(checkedKeys as string[]);
-    setCheckNodes(info.checkedNodes as TreeNodeData[]);
-  };
-
-  const onDragEnter: TreeProps["onDragEnter"] = (info) => {
-    console.log(info);
-    // expandedKeys 需要受控时设置
-    // setExpandedKeys(info.expandedKeys)
-  };
-
-  const onDrop: TreeProps["onDrop"] = (info) => {
-    console.log(info);
-    const dropKey = info.node.key;
-    const dragKey = info.dragNode.key;
-    const dropPos = info.node.pos.split("-");
-    const dropPosition =
-      info.dropPosition - Number(dropPos[dropPos.length - 1]);
-
-    const loop = (
-      data: DataNode[],
-      key: React.Key,
-      callback: (node: DataNode, i: number, data: DataNode[]) => void
-    ) => {
-      for (let i = 0; i < data.length; i++) {
-        if (data[i].key === key) {
-          return callback(data[i], i, data);
-        }
-        if (data[i].children) {
-          loop(data[i].children!, key, callback);
-        }
-      }
-    };
-    const data = [...gData];
-
-    // Find dragObject
-    let dragObj: DataNode;
-    loop(data, dragKey, (item, index, arr) => {
-      arr.splice(index, 1);
-      dragObj = item;
-    });
-
-    if (!info.dropToGap) {
-      // Drop on the content
-      loop(data, dropKey, (item) => {
-        item.children = item.children || [];
-        // where to insert 示例添加到头部,可以是随意位置
-        item.children.unshift(dragObj);
-      });
-    } else if (
-      ((info.node as any).props.children || []).length > 0 && // Has children
-      (info.node as any).props.expanded && // Is expanded
-      dropPosition === 1 // On the bottom gap
-    ) {
-      loop(data, dropKey, (item) => {
-        item.children = item.children || [];
-        // where to insert 示例添加到头部,可以是随意位置
-        item.children.unshift(dragObj);
-        // in previous version, we use item.children.push(dragObj) to insert the
-        // item to the tail of the children
-      });
-    } else {
-      let ar: DataNode[] = [];
-      let i: number;
-      loop(data, dropKey, (_item, index, arr) => {
-        ar = arr;
-        i = index;
-      });
-      if (dropPosition === -1) {
-        ar.splice(i!, 0, dragObj!);
-      } else {
-        ar.splice(i! + 1, 0, dragObj!);
-      }
-    }
-    setGData(data);
-    const list = treeToList(data);
-    setListTreeData(list);
-  };
-
-  return (
-    <>
-      <Space>
-        {addFileButton}
-        <Button
-          icon={<FileAddOutlined />}
-          onClick={async () => {
-            if (typeof onAppend !== "undefined") {
-              const newNode = await onAppend({
-                key: "",
-                title: "",
-                children: [],
-                level: 0,
-              });
-              console.log("newNode", newNode);
-              if (newNode) {
-                const append = [...gData, newNode];
-                setGData(append);
-                const list = treeToList(append);
-                setListTreeData(list);
-                return true;
-              } else {
-                message.error("添加失败");
-                return false;
-              }
-            } else {
-              return false;
-            }
-          }}
-        >
-          {intl.formatMessage({ id: "buttons.create" })}
-        </Button>
-        <Button
-          icon={<DeleteOutlined />}
-          danger
-          disabled={checkKeys.length === 0}
-          onClick={() => {
-            const delTree = (node: TreeNodeData[]): boolean => {
-              for (let index = 0; index < node.length; index++) {
-                if (checkKeys.includes(node[index].key)) {
-                  node.splice(index, 1);
-                  return true;
-                } else {
-                  const cf = delTree(node[index].children);
-                  if (cf) {
-                    return cf;
-                  }
-                }
-              }
-              return false;
-            };
-
-            Modal.confirm({
-              title: "从文集移除下列文章吗?(文章不会被删除)",
-              content: (
-                <>
-                  {checkNodes.map((item, id) => (
-                    <div key={id}>
-                      {id + 1} {item.title}
-                    </div>
-                  ))}
-                </>
-              ),
-              onOk() {
-                const tmp = [...gData];
-                const find = delTree(tmp);
-
-                console.log("delete", keys, find, tmp);
-                setGData(tmp);
-                const list = treeToList(tmp);
-                setListTreeData(list);
-              },
-            });
-          }}
-        >
-          {intl.formatMessage({ id: "buttons.remove" })}
-        </Button>
-        <Button
-          icon={<SaveOutlined />}
-          onClick={() => {
-            if (typeof onSave !== "undefined") {
-              onSave(listTreeData);
-            }
-          }}
-          type="primary"
-        >
-          {intl.formatMessage({ id: "buttons.save" })}
-        </Button>
-      </Space>
-      <Divider></Divider>
-      <Tree
-        showLine
-        showIcon
-        checkable
-        rootClassName="draggable-tree"
-        draggable
-        blockNode
-        selectable={false}
-        onDragEnter={onDragEnter}
-        onDrop={onDrop}
-        onCheck={onCheck}
-        onSelect={(selectedKeys: Key[]) => {
-          if (selectedKeys.length > 0) {
-            setKeys(selectedKeys[0]);
-          } else {
-            setKeys("");
-          }
-          if (typeof onSelect !== "undefined") {
-            onSelect(selectedKeys);
-          }
-        }}
-        treeData={gData}
-        titleRender={(node: TreeNodeData) => {
-          return (
-            <EditableTreeNode
-              node={node}
-              onAdd={async () => {
-                if (typeof onAppend !== "undefined") {
-                  const newNode = await onAppend(node);
-                  console.log("newNode", newNode);
-                  if (newNode) {
-                    appendNode(node.key, newNode);
-                    return true;
-                  } else {
-                    message.error("添加失败");
-                    return false;
-                  }
-                } else {
-                  return false;
-                }
-              }}
-              onTitleClick={(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
-                if (typeof onTitleClick !== "undefined") {
-                  onTitleClick(e, node);
-                }
-              }}
-            />
-          );
-        }}
-      />
-    </>
-  );
-};
-
-export default EditableTreeWidget;

+ 0 - 72
dashboard-v6/backup/components/article/EditableTreeNode.tsx

@@ -1,72 +0,0 @@
-import { Button, message, Space, Typography } from "antd";
-import { useState } from "react";
-import { PlusOutlined } from "@ant-design/icons";
-
-import type { TreeNodeData } from "./EditableTree"
-const { Text } = Typography;
-
-interface IWidget {
-  node: TreeNodeData;
-  onAdd?: Function;
-  onTitleClick?: Function;
-}
-const EditableTreeNodeWidget = ({ node, onAdd, onTitleClick }: IWidget) => {
-  const [showNodeMenu, setShowNodeMenu] = useState(false);
-  const [loading, setLoading] = useState(false);
-
-  const title = (
-    <Text type={node.status === 10 ? "secondary" : undefined}>
-      {node.title_text ? node.title_text : node.title}
-    </Text>
-  );
-
-  const TitleText = () =>
-    node.deletedAt ? (
-      <Text delete disabled>
-        {title}
-      </Text>
-    ) : (
-      <Text
-        onClick={(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
-          if (typeof onTitleClick !== "undefined") {
-            onTitleClick(e);
-          }
-        }}
-      >
-        {title}
-      </Text>
-    );
-
-  const Menu = () => (
-    <Space style={{ visibility: showNodeMenu ? "visible" : "hidden" }}>
-      <Button
-        loading={loading}
-        size="middle"
-        icon={<PlusOutlined />}
-        type="text"
-        onClick={async () => {
-          if (typeof onAdd !== "undefined") {
-            setLoading(true);
-            const ok = await onAdd();
-            setLoading(false);
-            if (!ok) {
-              message.error("error");
-            }
-          }
-        }}
-      />
-    </Space>
-  );
-
-  return (
-    <Space
-      onMouseEnter={() => setShowNodeMenu(true)}
-      onMouseLeave={() => setShowNodeMenu(false)}
-    >
-      <TitleText />
-      <Menu />
-    </Space>
-  );
-};
-
-export default EditableTreeNodeWidget;

+ 0 - 83
dashboard-v6/backup/components/article/ExerciseList.tsx

@@ -1,83 +0,0 @@
-import { useEffect, useState } from "react";
-import { Collapse, Space, Tag } from "antd";
-
-import type { ICourseExerciseResponse } from "../../api/Course";
-import { get } from "../../request";
-import type { IUser } from "../auth/User";
-import MdView from "../template/MdView";
-
-const { Panel } = Collapse;
-
-interface DataItem {
-  sn: number;
-  name: string;
-  user: IUser;
-  wbw: number;
-  translation: number;
-  question: number;
-  html: string;
-}
-interface IWidget {
-  courseId?: string;
-  articleId?: string;
-  exerciseId?: string;
-}
-const ExerciseListWidget = ({ courseId, articleId, exerciseId }: IWidget) => {
-  const [data, setData] = useState<DataItem[]>();
-
-  useEffect(() => {
-    const url = `/v2/exercise?course_id=${courseId}&article_id=${articleId}&exercise_id=${exerciseId}`;
-    console.log(url);
-    get<ICourseExerciseResponse>(url)
-      .then((json) => {
-        if (json.ok) {
-          console.log(json.data);
-          const items: DataItem[] = json.data.rows.map((item, id) => {
-            const member: DataItem = {
-              sn: id,
-              name: item.user.nickName,
-              user: item.user,
-              wbw: item.wbw,
-              translation: item.translation,
-              question: item.question,
-              html: item.html,
-            };
-            return member;
-          });
-          setData(items);
-        } else {
-          console.error(json.message);
-        }
-      })
-      .catch((error) => {
-        console.error(error);
-      });
-  }, [courseId, articleId, exerciseId]);
-  return (
-    <>
-      <Collapse>
-        {data?.map((item, id) => {
-          const header = (
-            <Space>
-              <span>{item.name}</span>
-              {item.wbw === 0 ? <></> : <Tag color="blue">wbw-{item.wbw}</Tag>}
-              {item.question === 0 ? (
-                <></>
-              ) : (
-                <Tag color="#5BD8A6">Q-{item.question}</Tag>
-              )}
-            </Space>
-          );
-
-          return (
-            <Panel header={header} key={id}>
-              <MdView html={item.html} />
-            </Panel>
-          );
-        })}
-      </Collapse>
-    </>
-  );
-};
-
-export default ExerciseListWidget;

+ 0 - 56
dashboard-v6/backup/components/article/Find.tsx

@@ -1,56 +0,0 @@
-import { useState } from "react";
-import { Input, Space, Select } from "antd";
-
-const { Search } = Input;
-
-const FindWidget = () => {
-  const [isLoading, setIsLoading] = useState(false);
-
-  const onSearch = (value: string) => {
-    setIsLoading(true);
-    console.log(value);
-  };
-  const onReplace = (value: string) => {
-    console.log(value);
-  };
-  return (
-    <div>
-      <Space orientation="vertical">
-        <Search
-          placeholder="input search text"
-          allowClear
-          onSearch={onSearch}
-          style={{ width: "100%" }}
-          loading={isLoading}
-        />
-        <Search
-          placeholder="input search text"
-          allowClear
-          enterButton="替换"
-          style={{ width: "100%" }}
-          onSearch={onReplace}
-        />
-        <Select
-          defaultValue="current"
-          style={{ width: "100%" }}
-          onChange={(value: string) => {
-            console.log(`selected ${value}`);
-          }}
-          options={[
-            {
-              value: "current",
-              label: "当前文档",
-            },
-            {
-              value: "all",
-              label: "全部译文",
-            },
-          ]}
-        />
-        <div>搜索结果</div>
-      </Space>
-    </div>
-  );
-};
-
-export default FindWidget;

+ 0 - 21
dashboard-v6/backup/components/article/MainMenu.tsx

@@ -1,21 +0,0 @@
-import { Button, Dropdown } from "antd";
-import { AppstoreOutlined } from "@ant-design/icons";
-import { mainMenuItems } from "../library/HeadBar";
-
-const MainMenuWidget = () => {
-  return (
-    <Dropdown
-      menu={{ items: mainMenuItems }}
-      placement="bottomLeft"
-      trigger={["click"]}
-    >
-      <Button
-        type="text"
-        style={{ display: "block", color: "white" }}
-        icon={<AppstoreOutlined />}
-      />
-    </Dropdown>
-  );
-};
-
-export default MainMenuWidget;

+ 0 - 82
dashboard-v6/backup/components/article/ModeSwitch.tsx

@@ -1,82 +0,0 @@
-import { Segmented } from "antd";
-import { useEffect, useState } from "react";
-import { useIntl } from "react-intl";
-import type { IChannel } from "../channel/Channel"
-import ChannelPicker from "../channel/ChannelPicker";
-
-interface IWidget {
-  currMode?: string;
-  channel: string | null;
-  onModeChange?: Function;
-  onChannelChange?: Function;
-}
-const ModeSwitchWidget = ({
-  currMode = "read",
-  onModeChange,
-  channel,
-  onChannelChange,
-}: IWidget) => {
-  const intl = useIntl();
-  const [mode, setMode] = useState<string>(currMode);
-  const [newMode, setNewMode] = useState<string>();
-  const [channelPickerOpen, setChannelPickerOpen] = useState(false);
-  useEffect(() => {
-    setMode(currMode);
-  }, [currMode]);
-  return (
-    <>
-      <Segmented
-        size="middle"
-        style={{
-          color: "rgb(134 134 134 / 90%)",
-          backgroundColor: "rgb(129 129 129 / 17%)",
-          display: "block",
-        }}
-        defaultValue={currMode}
-        options={[
-          {
-            label: intl.formatMessage({ id: "buttons.read" }),
-            value: "read",
-          },
-          {
-            label: intl.formatMessage({ id: "buttons.translate" }),
-            value: "edit",
-          },
-          {
-            label: intl.formatMessage({ id: "buttons.wbw" }),
-            value: "wbw",
-          },
-        ]}
-        value={mode}
-        onChange={(value) => {
-          const _mode = value.toString();
-
-          if (_mode !== "read" && channel === null) {
-            setChannelPickerOpen(true);
-            setNewMode(_mode);
-          } else {
-            if (typeof onModeChange !== "undefined") {
-              onModeChange(_mode);
-            }
-            setMode(_mode);
-          }
-        }}
-      />
-      <ChannelPicker
-        open={channelPickerOpen}
-        defaultOwner="my"
-        onClose={() => setChannelPickerOpen(false)}
-        onSelect={(channels: IChannel[]) => {
-          if (newMode) {
-            setMode(newMode);
-          }
-          if (typeof onChannelChange !== "undefined") {
-            onChannelChange(channels, newMode);
-          }
-        }}
-      />
-    </>
-  );
-};
-
-export default ModeSwitchWidget;

+ 0 - 38
dashboard-v6/backup/components/article/Nav.tsx

@@ -1,38 +0,0 @@
-import { Space, Select } from "antd";
-
-const NavWidget = () => {
-  return (
-    <div>
-      <Space orientation="vertical">
-        <Select
-          defaultValue="current"
-          style={{ width: "100%" }}
-          onChange={(value: string) => {
-            console.log(`selected ${value}`);
-          }}
-          options={[
-            {
-              value: "book-mark",
-              label: "书签",
-            },
-            {
-              value: "tag",
-              label: "标签",
-            },
-            {
-              value: "suggestion",
-              label: "修改建议",
-            },
-            {
-              value: "qa",
-              label: "问答",
-            },
-          ]}
-        />
-        <div>搜索结果</div>
-      </Space>
-    </div>
-  );
-};
-
-export default NavWidget;

+ 0 - 80
dashboard-v6/backup/components/article/Navigate.tsx

@@ -1,80 +0,0 @@
-import { useEffect, useState } from "react";
-
-import { get } from "../../request";
-import type { ArticleType } from "./Article";
-import React from "react";
-import NavigateButton from "./NavigateButton";
-import type { ITocPathNode } from "../../../src/components/tipitaka/TocPath";
-
-interface INavButton {
-  title: string;
-  subtitle: string;
-  id: string;
-}
-interface INavResponse {
-  ok: boolean;
-  message: string;
-  data: {
-    prev?: INavButton;
-    next?: INavButton;
-  };
-}
-
-interface IWidget {
-  type?: ArticleType;
-  articleId?: string;
-  path?: ITocPathNode[];
-  onChange?: (
-    event: React.MouseEvent<HTMLElement, MouseEvent>,
-    id: string
-  ) => void;
-  onPathChange?: (key: string) => void;
-}
-const NavigateWidget = ({
-  type,
-  articleId,
-  path,
-  onChange,
-  onPathChange,
-}: IWidget) => {
-  const [prev, setPrev] = useState<INavButton>();
-  const [next, setNext] = useState<INavButton>();
-
-  useEffect(() => {
-    if (type && articleId) {
-      get<INavResponse>(`/v2/article-nav?type=${type}&id=${articleId}`).then(
-        (json) => {
-          if (json.ok) {
-            setPrev(json.data.prev);
-            setNext(json.data.next);
-          }
-        }
-      );
-    }
-  }, [articleId, type]);
-
-  return (
-    <NavigateButton
-      prevTitle={prev?.title}
-      nextTitle={next?.title}
-      path={path}
-      onPrev={(event: React.MouseEvent<HTMLElement, MouseEvent>) => {
-        if (typeof onChange !== "undefined" && prev) {
-          onChange(event, prev.id);
-        }
-      }}
-      onNext={(event: React.MouseEvent<HTMLElement, MouseEvent>) => {
-        if (typeof onChange !== "undefined" && next) {
-          onChange(event, next.id);
-        }
-      }}
-      onPathChange={(key: string) => {
-        if (typeof onPathChange !== "undefined") {
-          onPathChange(key);
-        }
-      }}
-    />
-  );
-};
-
-export default NavigateWidget;

+ 0 - 135
dashboard-v6/backup/components/article/NavigateButton.tsx

@@ -1,135 +0,0 @@
-import { Button, Dropdown, Modal, Space, Typography } from "antd";
-import { DoubleRightOutlined, DoubleLeftOutlined } from "@ant-design/icons";
-import { FolderOutlined } from "@ant-design/icons";
-
-import type { ITocPathNode } from "../../../src/components/tipitaka/TocPath";
-
-const { Paragraph, Text } = Typography;
-
-const EllipsisMiddle: React.FC<{
-  suffixCount: number;
-  maxWidth: number;
-  text?: string;
-}> = ({ suffixCount, maxWidth = 500, text = "" }) => {
-  const start = text.slice(0, text.length - suffixCount).trim();
-  const suffix = text.slice(-suffixCount).trim();
-  return (
-    <Text style={{ maxWidth: maxWidth }} ellipsis={{ suffix }}>
-      {start}
-    </Text>
-  );
-};
-
-interface IWidget {
-  prevTitle?: string;
-  nextTitle?: string;
-  path?: ITocPathNode[];
-  topOfChapter?: boolean;
-  endOfChapter?: boolean;
-  onPrev?: Function;
-  onNext?: Function;
-  onPathChange?: Function;
-}
-const NavigateButtonWidget = ({
-  prevTitle,
-  nextTitle,
-  topOfChapter = false,
-  endOfChapter = false,
-  path,
-  onPrev,
-  onNext,
-  onPathChange,
-}: IWidget) => {
-  const currTitle = path && path.length > 0 ? path[path.length - 1].title : "";
-
-  return (
-    <Paragraph
-      style={{
-        display: "flex",
-        justifyContent: "space-between",
-        backdropFilter: "blur(5px)",
-        backgroundColor: "rgba(200,200,200,0.2)",
-        padding: 4,
-      }}
-    >
-      <Button
-        size="middle"
-        icon={topOfChapter ? <FolderOutlined /> : undefined}
-        disabled={typeof prevTitle === "undefined"}
-        onClick={(event: React.MouseEvent<HTMLElement, MouseEvent>) => {
-          if (typeof onPrev !== "undefined") {
-            if (topOfChapter) {
-              Modal.confirm({
-                content: "已经到达章节开头,去上一个章节吗?",
-                okText: "确认",
-                cancelText: "取消",
-                onOk: () => {
-                  onPrev(event);
-                },
-              });
-            } else {
-              onPrev(event);
-            }
-          }
-        }}
-      >
-        <Space>
-          <DoubleLeftOutlined key="icon" />
-          <EllipsisMiddle maxWidth={250} suffixCount={7} text={prevTitle} />
-        </Space>
-      </Button>
-      <div>
-        <Dropdown
-          placement="top"
-          trigger={["hover"]}
-          menu={{
-            items: path?.map((item, _id) => {
-              return { label: item.title, key: item.key ?? item.title };
-            }),
-            onClick: (e) => {
-              console.debug("onPathChange", e.key);
-              if (typeof onPathChange !== "undefined") {
-                onPathChange(e.key);
-              }
-            },
-          }}
-        >
-          <span>{currTitle}</span>
-        </Dropdown>
-      </div>
-      <Button
-        icon={endOfChapter ? <FolderOutlined /> : undefined}
-        size="middle"
-        disabled={typeof nextTitle === "undefined"}
-        onClick={(event: React.MouseEvent<HTMLElement, MouseEvent>) => {
-          if (typeof onNext !== "undefined") {
-            if (endOfChapter) {
-              Modal.confirm({
-                content: "已经到达章节末尾,去下一个章节吗?",
-                okText: "确认",
-                cancelText: "取消",
-                onOk: () => {
-                  onNext(event);
-                },
-              });
-            } else {
-              onNext(event);
-            }
-          }
-        }}
-      >
-        <Space>
-          <EllipsisMiddle
-            key="title"
-            maxWidth={250}
-            suffixCount={7}
-            text={nextTitle?.substring(0, 20)}
-          />
-          <DoubleRightOutlined key="icon" />
-        </Space>
-      </Button>
-    </Paragraph>
-  );
-};
-
-export default NavigateButtonWidget;

+ 0 - 89
dashboard-v6/backup/components/article/PaliTextToc.tsx

@@ -1,89 +0,0 @@
-import type { Key } from "antd/lib/table/interface";
-import { useState, useEffect } from "react";
-
-import { get } from "../../request";
-import type { IPaliTocListResponse } from "../../api/Corpus";
-import type { ListNodeData } from "./EditableTree";
-import TocTree from "./TocTree";
-import { Skeleton } from "antd";
-
-interface IWidget {
-  book?: number;
-  para?: number;
-  series?: string;
-  channel?: string;
-  onSelect?: Function;
-  onClick?: Function;
-}
-const PaliTextTocWidget = ({
-  book,
-  para,
-  series,
-  onSelect,
-  onClick,
-}: IWidget) => {
-  const [tocList, setTocList] = useState<ListNodeData[]>([]);
-  const [selectedKeys, setSelectedKeys] = useState<Key[]>();
-  const [expandedKeys, setExpandedKeys] = useState<Key[]>();
-  const [loading, setLoading] = useState(true);
-  useEffect(() => {
-    let url = `/v2/palitext?view=book-toc`;
-    if (series) {
-      url += `&series=${series}`;
-    } else {
-      url += `&book=${book}&para=${para}`;
-    }
-    setLoading(true);
-    console.info("api request", url);
-    get<IPaliTocListResponse>(url)
-      .then((json) => {
-        console.info("api response", json);
-        const toc = json.data.rows.map((item, _id) => {
-          return {
-            key: `${item.book}-${item.paragraph}`,
-            title: item.toc,
-            level: parseInt(item.level),
-          };
-        });
-        setTocList(toc);
-        if (json.data.rows.length > 0) {
-          const path: string[] = [];
-          for (let index = json.data.rows.length - 1; index >= 0; index--) {
-            const element = json.data.rows[index];
-            if (element.book === book && para && element.paragraph <= para) {
-              path.push(`${element.book}-${element.paragraph}`);
-              break;
-            }
-          }
-          setExpandedKeys(path);
-          setSelectedKeys(path);
-        }
-      })
-      .finally(() => setLoading(false));
-  }, [book, para, series]);
-
-  return loading ? (
-    <Skeleton active />
-  ) : (
-    <TocTree
-      treeData={tocList}
-      selectedKeys={selectedKeys}
-      expandedKeys={expandedKeys}
-      onSelect={(selectedKeys: Key[]) => {
-        if (typeof onSelect !== "undefined") {
-          onSelect(selectedKeys);
-        }
-      }}
-      onClick={(
-        id: string,
-        e: React.MouseEvent<HTMLSpanElement, MouseEvent>
-      ) => {
-        if (typeof onClick !== "undefined") {
-          onClick(id, e);
-        }
-      }}
-    />
-  );
-};
-
-export default PaliTextTocWidget;

+ 0 - 173
dashboard-v6/backup/components/article/ProTabs.tsx

@@ -1,173 +0,0 @@
-import { useRef, useState } from "react";
-import { Switch } from "antd";
-import { Radio, Space } from "antd";
-import { SettingOutlined, ShoppingCartOutlined } from "@ant-design/icons";
-
-import SettingArticle from "../auth/setting/SettingArticle";
-import DictComponent from "../dict/DictComponent";
-import { DictIcon, TermIcon } from "../../assets/icon";
-import TermShell from "./TermShell";
-
-const setting = (
-  <>
-    <Space>
-      {"保存到用户设置"}
-      <Switch
-        defaultChecked
-        onChange={(checked) => {
-          console.log(checked);
-        }}
-      />
-    </Space>
-    <SettingArticle />
-  </>
-);
-
-const ProTabsWidget = () => {
-  const [value2, setValue2] = useState("close");
-  const divSetting = useRef<HTMLDivElement>(null);
-  const divDict = useRef<HTMLDivElement>(null);
-  const divTerm = useRef<HTMLDivElement>(null);
-  const divCart = useRef<HTMLDivElement>(null);
-  const divPanel = useRef<HTMLDivElement>(null);
-  const rightBarWidth = "48px";
-  const closeAll = () => {
-    if (divPanel.current) {
-      divPanel.current.style.display = "none";
-    }
-  };
-  const openPanel = () => {
-    if (divPanel.current) {
-      divPanel.current.style.display = "block";
-    }
-  };
-  const headHeight = 64;
-  const stylePanel: React.CSSProperties = {
-    height: `calc(100vh - ${headHeight})`,
-    overflowY: "scroll",
-  };
-  return (
-    <div style={{ display: "flex" }}>
-      <div ref={divPanel} style={{ width: 350, display: "none" }}>
-        <div ref={divSetting} style={stylePanel}>
-          {setting}
-        </div>
-        <div ref={divDict} style={stylePanel}>
-          <DictComponent />
-        </div>
-        <div ref={divTerm} style={stylePanel}>
-          <TermShell />
-        </div>
-        <div ref={divCart} style={stylePanel}></div>
-      </div>
-      <div
-        style={{
-          width: `${rightBarWidth}`,
-          display: "flex",
-          flexDirection: "column",
-        }}
-      >
-        <Radio.Group
-          value={value2}
-          optionType="button"
-          buttonStyle="solid"
-          onChange={(e) => {
-            console.log("radio change", e.target.value);
-            if (divSetting.current) {
-              divSetting.current.style.display = "none";
-            }
-            if (divDict.current) {
-              divDict.current.style.display = "none";
-            }
-            if (divTerm.current) {
-              divTerm.current.style.display = "none";
-            }
-            if (divCart.current) {
-              divCart.current.style.display = "none";
-            }
-            switch (e.target.value) {
-              case "setting":
-                if (divSetting.current) {
-                  divSetting.current.style.display = "block";
-                }
-                openPanel();
-                break;
-              case "dict":
-                if (divDict.current) {
-                  divDict.current.style.display = "block";
-                }
-                openPanel();
-
-                break;
-              case "term":
-                if (divTerm.current) {
-                  divTerm.current.style.display = "block";
-                }
-                openPanel();
-                break;
-              case "cart":
-                if (divCart.current) {
-                  divCart.current.style.display = "block";
-                }
-                openPanel();
-                break;
-              default:
-                break;
-            }
-            setValue2(e.target.value);
-          }}
-        >
-          <Space orientation="vertical">
-            <Radio
-              value="setting"
-              onClick={() => {
-                if (value2 === "setting") {
-                  setValue2("close");
-                  closeAll();
-                }
-              }}
-            >
-              <SettingOutlined />
-            </Radio>
-            <Radio
-              value="dict"
-              onClick={() => {
-                if (value2 === "dict") {
-                  setValue2("close");
-                  closeAll();
-                }
-              }}
-            >
-              <DictIcon />
-            </Radio>
-            <Radio
-              value="term"
-              onClick={() => {
-                if (value2 === "term") {
-                  setValue2("close");
-                  closeAll();
-                }
-              }}
-            >
-              <TermIcon />
-            </Radio>
-            <Radio
-              value="cart"
-              onClick={() => {
-                if (value2 === "cart") {
-                  setValue2("close");
-                  closeAll();
-                }
-              }}
-            >
-              <ShoppingCartOutlined />
-            </Radio>
-            <Radio value="close" style={{ display: "none" }}></Radio>
-          </Space>
-        </Radio.Group>
-      </div>
-    </div>
-  );
-};
-
-export default ProTabsWidget;

+ 0 - 222
dashboard-v6/backup/components/article/RightPanel.tsx

@@ -1,222 +0,0 @@
-import { Affix, Button, Space, Tabs } from "antd";
-import { useEffect, useState } from "react";
-import { CloseOutlined } from "@ant-design/icons";
-import { FullscreenOutlined, FullscreenExitOutlined } from "@ant-design/icons";
-
-import type { IChannel } from "../channel/Channel";
-import DictComponent from "../dict/DictComponent";
-import type { ArticleType } from "./Article";
-import { useAppSelector } from "../../hooks";
-import { openPanel, rightPanel } from "../../reducers/right-panel";
-import store from "../../store";
-import DiscussionBox from "../discussion/DiscussionBox";
-import { show } from "../../reducers/discussion";
-import { useIntl } from "react-intl";
-import SuggestionBox from "../template/SentEdit/SuggestionBox";
-import ChannelMy from "../channel/ChannelMy";
-import GrammarBook from "../term/GrammarBook";
-
-interface IWidget {
-  curr?: TPanelName;
-  type: ArticleType;
-  articleId: string;
-  selectedChannelsId?: string[];
-  onChannelSelect?: Function;
-  onClose?: Function;
-  onTabChange?: Function;
-}
-const RightPanelWidget = ({
-  curr = "close",
-  type,
-  articleId,
-  onChannelSelect,
-  selectedChannelsId,
-  onClose,
-  onTabChange,
-}: IWidget) => {
-  const [open, setOpen] = useState(false);
-  const [activeTab, setActiveTab] = useState<string>("dict");
-  const _openPanel = useAppSelector(rightPanel);
-  const intl = useIntl();
-
-  const divMinWidth = 400;
-  const divMaxWidth = 700;
-  const [divWidth, setDivWidth] = useState(divMinWidth);
-
-  const tabInnerStyle: React.CSSProperties = {
-    width: "100%",
-    height: `calc(100vh - 96px)`,
-    overflowY: "scroll",
-  };
-
-  useEffect(() => {
-    console.log("panel", _openPanel);
-    if (typeof _openPanel !== "undefined") {
-      if (typeof onTabChange !== "undefined") {
-        onTabChange(_openPanel);
-      }
-      store.dispatch(openPanel(undefined));
-    }
-  }, [_openPanel]);
-
-  useEffect(() => {
-    switch (curr) {
-      case "open":
-        setOpen(true);
-        break;
-      case "dict":
-        setOpen(true);
-        setActiveTab(curr);
-        break;
-      case "channel":
-        setOpen(true);
-        setActiveTab(curr);
-        break;
-      case "discussion":
-        setOpen(true);
-        setActiveTab(curr);
-        break;
-      case "suggestion":
-        setOpen(true);
-        setActiveTab(curr);
-        break;
-      case "grammar":
-        setOpen(true);
-        setActiveTab(curr);
-        break;
-      case "close":
-        setOpen(false);
-        break;
-      default:
-        setOpen(false);
-        break;
-    }
-  }, [curr]);
-  return (
-    <Affix offsetTop={44}>
-      <div
-        key="panel"
-        style={{
-          width: divWidth,
-          height: `calc(100vh - 44px)`,
-          overflowY: "hidden",
-          display: open ? "block" : "none",
-          paddingLeft: 8,
-          paddingTop: 8,
-        }}
-      >
-        <Tabs
-          type="card"
-          size="small"
-          defaultActiveKey={curr}
-          activeKey={activeTab}
-          onChange={(activeKey: string) => setActiveTab(activeKey)}
-          tabBarExtraContent={{
-            right: (
-              <Space>
-                {divWidth === divMinWidth ? (
-                  <Button
-                    type="link"
-                    icon={<FullscreenOutlined />}
-                    onClick={() => setDivWidth(divMaxWidth)}
-                  />
-                ) : (
-                  <Button
-                    type="link"
-                    icon={<FullscreenExitOutlined />}
-                    onClick={() => setDivWidth(divMinWidth)}
-                  />
-                )}
-                <Button
-                  type="text"
-                  size="small"
-                  icon={<CloseOutlined />}
-                  onClick={() => {
-                    store.dispatch(
-                      show({
-                        type: "discussion",
-                        resType: "sentence",
-                      })
-                    );
-                    if (typeof onClose !== "undefined") {
-                      onClose();
-                    }
-                  }}
-                />
-              </Space>
-            ),
-          }}
-          items={[
-            {
-              label: intl.formatMessage({
-                id: "columns.library.dict.title",
-              }),
-              key: "dict",
-              children: (
-                <div className="dict_component" style={tabInnerStyle}>
-                  <DictComponent />
-                </div>
-              ),
-            },
-            {
-              label: intl.formatMessage({
-                id: "columns.studio.channel.title",
-              }),
-              key: "channel",
-              children: (
-                <div style={tabInnerStyle}>
-                  <ChannelMy
-                    type={type}
-                    articleId={articleId}
-                    selectedKeys={selectedChannelsId}
-                    onSelect={(e: IChannel[]) => {
-                      console.log(e);
-                      if (typeof onChannelSelect !== "undefined") {
-                        onChannelSelect(e);
-                      }
-                    }}
-                  />
-                </div>
-              ),
-            },
-            {
-              label: intl.formatMessage({
-                id: "buttons.discussion",
-              }),
-              key: "discussion",
-              children: (
-                <div style={tabInnerStyle}>
-                  <DiscussionBox />
-                </div>
-              ),
-            },
-            {
-              label: intl.formatMessage({
-                id: "buttons.suggestion",
-              }),
-              key: "suggestion",
-              children: (
-                <div style={tabInnerStyle}>
-                  <SuggestionBox />
-                </div>
-              ),
-            },
-            {
-              label: intl.formatMessage({
-                id: "columns.library.palihandbook.title",
-              }),
-              key: "grammar",
-              children: (
-                <div style={tabInnerStyle}>
-                  <GrammarBook />
-                </div>
-              ),
-            },
-          ]}
-        />
-      </div>
-    </Affix>
-  );
-};
-
-export default RightPanelWidget;

+ 0 - 63
dashboard-v6/backup/components/article/RightToolsSwitch.tsx

@@ -1,63 +0,0 @@
-import { Segmented } from "antd";
-import { useEffect, useState } from "react";
-import { useIntl } from "react-intl";
-import { useAppSelector } from "../../hooks";
-import { rightPanel } from "../../reducers/right-panel";
-import type { TPanelName } from "./RightPanel"
-
-interface IWidget {
-  initMode?: string;
-  onModeChange?: Function;
-}
-const RightToolsSwitchWidget = ({
-  initMode = "close",
-  onModeChange,
-}: IWidget) => {
-  const intl = useIntl();
-  const [mode, setMode] = useState<string>(initMode);
-  const _openPanel = useAppSelector(rightPanel);
-
-  useEffect(() => {
-    if (typeof _openPanel !== "undefined") {
-      if (typeof onModeChange !== "undefined") {
-        onModeChange(_openPanel);
-      }
-      setMode(_openPanel);
-    }
-  }, [_openPanel]);
-
-  return (
-    <Segmented
-      size="middle"
-      style={{
-        color: "rgb(134 134 134 / 90%)",
-        backgroundColor: "rgb(129 129 129 / 17%)",
-        display: "block",
-      }}
-      options={[
-        {
-          label: intl.formatMessage({ id: "columns.library.dict.title" }),
-          value: "dict",
-        },
-        {
-          label: intl.formatMessage({ id: "columns.studio.channel.title" }),
-          value: "channel",
-        },
-        {
-          label: intl.formatMessage({ id: "buttons.close" }),
-          value: "close",
-        },
-      ]}
-      value={mode}
-      onChange={(value) => {
-        const newMode: TPanelName = value.toString() as TPanelName;
-        if (typeof onModeChange !== "undefined") {
-          onModeChange(newMode);
-        }
-        setMode(newMode);
-      }}
-    />
-  );
-};
-
-export default RightToolsSwitchWidget;

+ 0 - 25
dashboard-v6/backup/components/article/TermShell.tsx

@@ -1,25 +0,0 @@
-import { useEffect, useState } from "react";
-
-import { useAppSelector } from "../../hooks";
-import { message } from "../../reducers/command";
-
-import TermEdit, { type ITerm } from "../term/TermEdit";
-
-const TermShellWidget = () => {
-  const [termProps, setTermProps] = useState<ITerm>();
-  //接收术语消息
-  const commandMsg = useAppSelector(message);
-  useEffect(() => {
-    console.log("get command", commandMsg);
-    if (commandMsg?.type === "term") {
-      setTermProps(commandMsg.prop);
-    }
-  }, [commandMsg]);
-  return (
-    <div>
-      <TermEdit {...termProps} />
-    </div>
-  );
-};
-
-export default TermShellWidget;

+ 0 - 295
dashboard-v6/backup/components/article/TocTree.tsx

@@ -1,295 +0,0 @@
-import { Tree, Typography } from "antd";
-import { useEffect, useState } from "react";
-
-import type { ListNodeData } from "./EditableTree";
-import PaliText from "../template/Wbw/PaliText";
-import type { Key } from "antd/lib/table/interface";
-import { randomString } from "../../utils";
-import type { DataNode, EventDataNode } from "antd/es/tree";
-
-const { Text } = Typography;
-
-interface IIdMap {
-  key: string;
-  id: string;
-}
-export interface TreeNodeData {
-  key: string;
-  id: string;
-  title: string | React.ReactNode;
-  isLeaf?: boolean;
-  children?: TreeNodeData[];
-  level: number;
-  status?: number;
-  deletedAt?: string | null;
-}
-
-function tocGetTreeData(
-  listData: ListNodeData[],
-  active = ""
-): [TreeNodeData[] | undefined, IIdMap[]] {
-  const treeData: TreeNodeData[] = [];
-  let tocActivePath: TreeNodeData[] = [];
-  const treeParents = [];
-  const rootNode: TreeNodeData = {
-    key: randomString(),
-    id: "0",
-    title: "root",
-    level: 0,
-    children: [],
-  };
-  const idMap: IIdMap[] = [];
-  treeData.push(rootNode);
-  let lastInsNode: TreeNodeData = rootNode;
-
-  let iCurrLevel = 0;
-  for (let index = 0; index < listData.length; index++) {
-    const element = listData[index];
-    const newNode: TreeNodeData = {
-      key: randomString(),
-      id: element.key,
-      isLeaf: element.children === 0,
-      title: element.title,
-      level: element.level,
-      status: element.status,
-      deletedAt: element.deletedAt,
-    };
-    idMap.push({
-      key: newNode.key,
-      id: newNode.id,
-    });
-    if (newNode.level > iCurrLevel) {
-      //新的层级比较大,为上一个的子目录
-      treeParents.push(lastInsNode);
-      if (typeof lastInsNode.children === "undefined") {
-        lastInsNode.children = [];
-      }
-      lastInsNode.children.push(newNode);
-    } else if (newNode.level === iCurrLevel) {
-      //目录层级相同,为平级
-      const parentNode = treeParents[treeParents.length - 1];
-      if (typeof parentNode !== "undefined") {
-        if (typeof parentNode.children === "undefined") {
-          parentNode.children = [];
-        }
-        parentNode.children.push(newNode);
-      }
-    } else {
-      // 小于 挂在上一个层级
-      while (treeParents.length > 1) {
-        treeParents.pop();
-        if (treeParents[treeParents.length - 1].level < newNode.level) {
-          break;
-        }
-      }
-      const parentNode = treeParents[treeParents.length - 1];
-      if (typeof parentNode !== "undefined") {
-        if (typeof parentNode.children === "undefined") {
-          parentNode.children = [];
-        }
-        parentNode.children.push(newNode);
-      }
-    }
-    lastInsNode = newNode;
-    iCurrLevel = newNode.level;
-
-    if (active === element.key) {
-      tocActivePath = [];
-      for (let index = 1; index < treeParents.length; index++) {
-        //treeParents[index]["expanded"] = true;
-        tocActivePath.push(treeParents[index]);
-      }
-    }
-  }
-
-  return [treeData[0].children, idMap];
-}
-
-// It's just a lazy load simple demo. You can use tree map to optimize update perf.
-const updateTreeData = (
-  list: TreeNodeData[],
-  key: React.Key,
-  children: TreeNodeData[]
-): TreeNodeData[] => {
-  return list.map((node) => {
-    if (node.key === key) {
-      console.debug("updateTreeData found", key, node);
-      node.children = children;
-      return node;
-    }
-
-    if (node.children) {
-      return {
-        ...node,
-        children: updateTreeData(node.children, key, children),
-      };
-    }
-
-    return node;
-  });
-};
-
-interface IWidgetTocTree {
-  treeData?: ListNodeData[];
-  expandedKeys?: Key[];
-  selectedKeys?: Key[];
-  onSelect?: Function;
-  onClick?: Function;
-  onLoad?: (key: string) => string;
-}
-
-const TocTreeWidget = ({
-  treeData,
-  expandedKeys,
-  selectedKeys,
-  onSelect,
-  onClick,
-  onLoad,
-}: IWidgetTocTree) => {
-  const [tree, setTree] = useState<TreeNodeData[]>();
-  const [expanded, setExpanded] = useState<Key[]>();
-  const [selected, setSelected] = useState<Key[]>();
-  const [keyIdMap, setKeyIdMap] = useState<IIdMap[]>();
-
-  useEffect(() => {
-    if (treeData && treeData.length > 0) {
-      const [data, idMap] = tocGetTreeData(treeData, "");
-      setTree(data);
-      setKeyIdMap(idMap);
-      console.log(" tree data", data);
-    } else {
-      setTree([]);
-    }
-  }, [treeData]);
-
-  useEffect(() => {
-    if (!keyIdMap) {
-      return;
-    }
-    const realKey = selectedKeys?.map((item) => {
-      const mapIndex = keyIdMap?.findIndex((value) => value.id === item);
-      if (mapIndex !== -1) {
-        return keyIdMap[mapIndex].key;
-      } else {
-        return "";
-      }
-    });
-    console.log("realKey", realKey);
-    setSelected(realKey);
-  }, [keyIdMap, selectedKeys]);
-
-  useEffect(() => {
-    if (!keyIdMap) {
-      return;
-    }
-    const realKey = expandedKeys?.map((item) => {
-      const mapIndex = keyIdMap?.findIndex((value) => value.id === item);
-      if (mapIndex !== -1) {
-        return keyIdMap[mapIndex].key;
-      } else {
-        return "";
-      }
-    });
-    console.log("realKey", realKey);
-    setExpanded(realKey);
-  }, [expandedKeys, keyIdMap]);
-
-  const onLoadData = ({ key, children }: any) =>
-    new Promise<void>((resolve) => {
-      if (children) {
-        resolve();
-        return;
-      }
-      if (typeof onLoad === "undefined") {
-        resolve();
-        return;
-      }
-
-      setTimeout(() => {
-        setTree((origin) => {
-          if (!origin) {
-            return origin;
-          }
-          return updateTreeData(origin, key, [
-            {
-              title: "Child Node",
-              key: randomString(),
-              id: `${key}-0`,
-              level: 2,
-            },
-            {
-              title: "Child Node",
-              key: randomString(),
-              id: `${key}-1`,
-              level: 2,
-            },
-          ]);
-        });
-
-        resolve();
-      }, 1000);
-    });
-
-  return (
-    <Tree
-      treeData={tree}
-      selectedKeys={selected}
-      expandedKeys={expanded}
-      autoExpandParent
-      loadData={onLoadData}
-      onExpand={(expandedKeys: Key[]) => {
-        setExpanded(expandedKeys);
-      }}
-      onClick={(
-        e: React.MouseEvent<HTMLSpanElement, MouseEvent>,
-        node: EventDataNode<DataNode>
-      ) => {
-        if (typeof onClick !== "undefined") {
-          const selectedId = keyIdMap?.find(
-            (value) => node.key === value.key
-          )?.id;
-          if (selectedId) {
-            onClick(selectedId, e);
-          }
-        }
-      }}
-      onSelect={(selectedKeys: Key[]) => {
-        setSelected(selectedKeys);
-        if (typeof onSelect !== "undefined") {
-          const selectedId = keyIdMap
-            ?.filter((value) => selectedKeys.includes(value.key))
-            .map((item) => item.id);
-          onSelect(selectedId);
-        }
-      }}
-      blockNode
-      titleRender={(node: TreeNodeData) => {
-        const currNode =
-          typeof node.title === "string" ? (
-            node.title === "" ? (
-              "[unnamed]"
-            ) : (
-              <PaliText
-                textType={node.status === 10 ? "secondary" : undefined}
-                text={node.title}
-              />
-            )
-          ) : (
-            node.title
-          );
-
-        return (
-          <Text
-            delete={node.deletedAt ? true : false}
-            disabled={node.deletedAt ? true : false}
-            type={node.status === 10 ? "secondary" : undefined}
-          >
-            {currNode}
-          </Text>
-        );
-      }}
-    />
-  );
-};
-
-export default TocTreeWidget;

+ 0 - 92
dashboard-v6/backup/components/article/Token.tsx

@@ -1,92 +0,0 @@
-import { Button, message, Segmented, Typography } from "antd";
-import type { SegmentedValue } from "antd/lib/segmented";
-import { useEffect, useState } from "react";
-import { CopyOutlined } from "@ant-design/icons";
-
-import { useIntl } from "react-intl";
-import type { ArticleType } from "./Article";
-import { post } from "../../request";
-import type {
-  IPayload,
-  ITokenCreate,
-  ITokenCreateResponse,
-  TPower,
-} from "../../api/token";
-const { Text } = Typography;
-
-interface IWidget {
-  channelId?: string;
-  articleId?: string;
-  type?: ArticleType;
-}
-const DictInfoCopyRef = ({ channelId, articleId, type }: IWidget) => {
-  const [text, setText] = useState("");
-  const [power, setPower] = useState<TPower>("readonly");
-  const intl = useIntl();
-
-  useEffect(() => {
-    if (!channelId || !articleId || !type) {
-      console.error("token", channelId, articleId, type);
-      return;
-    }
-    const id = articleId.split("-");
-    if (!channelId || !id || id.length < 2) {
-      console.error("channels or book or para is undefined", channelId, id);
-      return;
-    }
-    const _book = id[0];
-    const _para = id[1];
-    const payload: IPayload[] = [];
-    payload.push({
-      res_id: channelId,
-      res_type: "channel",
-      book: parseInt(_book),
-      para_start: parseInt(_para),
-      para_end: parseInt(_para) + 100,
-      power: power,
-    });
-    const url = "/v2/access-token";
-    const values = { payload: payload };
-    console.info("token api request", url, values);
-    post<ITokenCreate, ITokenCreateResponse>(url, values).then((json) => {
-      console.info("token api response", json);
-      if (json.ok) {
-        setText(json.data.rows[0].token);
-      }
-    });
-  }, [articleId, channelId, power, type]);
-  return (
-    <div>
-      <div style={{ textAlign: "center", padding: 20 }}>
-        <Segmented
-          options={["readonly", "edit"]}
-          onChange={(value: SegmentedValue) => {
-            setPower(value as TPower);
-          }}
-        />
-      </div>
-      <div>
-        <Text>{text}</Text>
-      </div>
-
-      <div style={{ textAlign: "center", padding: 20 }}>
-        <Button
-          type="primary"
-          style={{ width: 200 }}
-          icon={<CopyOutlined />}
-          onClick={() => {
-            navigator.clipboard.writeText(text).then(() => {
-              message.success("链接地址已经拷贝到剪贴板");
-            });
-          }}
-        >
-          {intl.formatMessage({
-            id: "buttons.copy",
-          })}
-        </Button>
-      </div>
-    </div>
-  );
-};
-
-export default DictInfoCopyRef;

+ 0 - 60
dashboard-v6/backup/components/article/TokenModal.tsx

@@ -1,60 +0,0 @@
-import { useEffect, useState } from "react";
-import { Modal } from "antd";
-import type { ArticleType } from "./Article"
-import Token from "./Token";
-
-interface IWidget {
-  channelId?: string;
-  articleId?: string;
-  type?: ArticleType;
-  trigger?: React.ReactNode;
-  open?: boolean;
-  onClose?: Function;
-}
-const TokenModal = ({
-  channelId,
-  articleId,
-  type,
-  trigger,
-  open = false,
-  onClose,
-}: IWidget) => {
-  const [isModalOpen, setIsModalOpen] = useState(open);
-
-  useEffect(() => setIsModalOpen(open), [open]);
-  const showModal = () => {
-    setIsModalOpen(true);
-  };
-
-  const handleOk = () => {
-    if (typeof onClose !== "undefined") {
-      onClose(false);
-    }
-    setIsModalOpen(false);
-  };
-
-  const handleCancel = () => {
-    if (typeof onClose !== "undefined") {
-      onClose(false);
-    }
-    setIsModalOpen(false);
-  };
-
-  return (
-    <>
-      <span onClick={showModal}>{trigger}</span>
-      <Modal
-        width={500}
-        title="token"
-        footer={false}
-        open={isModalOpen}
-        onOk={handleOk}
-        onCancel={handleCancel}
-      >
-        <Token channelId={channelId} articleId={articleId} type={type} />
-      </Modal>
-    </>
-  );
-};
-
-export default TokenModal;

+ 0 - 38
dashboard-v6/backup/components/article/ToolButton.tsx

@@ -1,38 +0,0 @@
-import { Button, Drawer, Tooltip } from "antd";
-import { useState } from "react";
-
-interface IWidget {
-  icon?: JSX.Element;
-  content?: JSX.Element;
-  title?: string;
-}
-const ToolButtonWidget = ({ icon, content, title }: IWidget) => {
-  const [open, setOpen] = useState(false);
-
-  return (
-    <>
-      <Tooltip placement="right" title={title}>
-        <Button
-          size="middle"
-          icon={icon}
-          onClick={() => {
-            setOpen(true);
-          }}
-        />
-      </Tooltip>
-      <Drawer
-        title={title}
-        width={460}
-        placement="left"
-        onClose={() => {
-          setOpen(false);
-        }}
-        open={open}
-      >
-        {content}
-      </Drawer>
-    </>
-  );
-};
-
-export default ToolButtonWidget;

+ 0 - 159
dashboard-v6/backup/components/article/ToolButtonDiscussion.tsx

@@ -1,159 +0,0 @@
-import { useIntl } from "react-intl";
-import { useEffect, useState } from "react";
-import { Button, Tag, Tree } from "antd";
-import { ReloadOutlined } from "@ant-design/icons";
-
-import ToolButton from "./ToolButton";
-import { post } from "../../request";
-import type { IUser } from "../auth/User"
-import { CommentOutlinedIcon } from "../../assets/icon";
-
-interface IPrTreeData {
-  book: number;
-  paragraph: number;
-  word_start: number;
-  word_end: number;
-  channel_id: string;
-  content: string;
-  pr_count: number;
-}
-interface IPrTreeRequestData {
-  book: number;
-  paragraph: number;
-  word_start: number;
-  word_end: number;
-  channel_id: string;
-}
-interface IPrData {
-  title: string;
-  children_count: number;
-  editor?: IUser;
-}
-interface IPrTreeRequest {
-  data: IPrTreeRequestData[];
-}
-interface IPrTreeResponseData {
-  sentence: IPrTreeData;
-  pr: IPrData[];
-}
-interface IPrTreeResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: IPrTreeResponseData[]; count: number };
-}
-interface DataNode {
-  title: string;
-  key: string;
-  isLeaf?: boolean;
-  childrenCount?: number;
-  children?: DataNode[];
-}
-
-interface IWidget {
-  type?: string;
-  articleId?: string;
-}
-const ToolButtonDiscussionWidget = ({ type, articleId }: IWidget) => { // eslint-disable-line
-  const [treeData, setTreeData] = useState<DataNode[]>([]);
-  const [loading, setLoading] = useState(false);
-  const intl = useIntl();
-
-  const refresh = () => {
-    const pr = document.querySelectorAll("div.tran_sent");
-
-    const prRequestData: IPrTreeRequestData[] = [];
-    for (let index = 0; index < pr.length; index++) {
-      const element = pr[index];
-      const id = element.id.split("_");
-      prRequestData.push({
-        book: parseInt(id[0]),
-        paragraph: parseInt(id[1]),
-        word_start: parseInt(id[2]),
-        word_end: parseInt(id[3]),
-        channel_id: id[4],
-      });
-    }
-    console.log("request pr tree", prRequestData);
-    setLoading(true);
-    post<IPrTreeRequest, IPrTreeResponse>("/v2/sent-discussion-tree", {
-      data: prRequestData,
-    })
-      .then((json) => {
-        console.log("discussion tree", json);
-        if (json.ok) {
-          const newTree: DataNode[] = json.data.rows.map((item) => {
-            const children: DataNode[] = item.pr.map((pr) => {
-              return {
-                title: pr.title,
-                key: pr.title,
-                childrenCount: pr.children_count,
-              };
-            });
-            return {
-              title: item.sentence.content,
-              key: `${item.sentence.book}_${item.sentence.paragraph}_${item.sentence.word_start}_${item.sentence.word_end}_${item.sentence.channel_id}`,
-              children: children,
-            };
-          });
-          setTreeData(newTree);
-        }
-      })
-      .finally(() => setLoading(false));
-  };
-
-  useEffect(() => {
-    //refresh();
-  }, []);
-
-  return (
-    <ToolButton
-      title="讨论"
-      icon={<CommentOutlinedIcon />}
-      content={
-        <>
-          <div style={{ display: "flex", justifyContent: "space-between" }}>
-            <span></span>
-            <Button
-              type="text"
-              icon={<ReloadOutlined />}
-              loading={loading}
-              onClick={() => {
-                refresh();
-              }}
-            >
-              {intl.formatMessage({
-                id: "buttons.refresh",
-              })}
-            </Button>
-          </div>
-
-          {treeData.length === 0 ? (
-            <>没有数据</>
-          ) : (
-            <Tree
-              treeData={treeData}
-              titleRender={(node) => {
-                const ele = document.getElementById(node.key);
-                const count = node.childrenCount
-                  ? node.childrenCount
-                  : node.children?.length;
-                return (
-                  <div
-                    onClick={() => {
-                      ele?.scrollIntoView();
-                    }}
-                  >
-                    {node.title}
-                    <Tag style={{ borderRadius: 5 }}>{count}</Tag>
-                  </div>
-                );
-              }}
-            />
-          )}
-        </>
-      }
-    />
-  );
-};
-
-export default ToolButtonDiscussionWidget;

+ 0 - 227
dashboard-v6/backup/components/article/ToolButtonNav.tsx

@@ -1,227 +0,0 @@
-import { useEffect, useState } from "react";
-import { useNavigate } from "react-router";
-import { Button, message, Space, Tag, Tree, Typography } from "antd";
-import { CompassOutlined, ReloadOutlined } from "@ant-design/icons";
-
-import ToolButton from "./ToolButton";
-import { useAppSelector } from "../../hooks";
-import { sentenceList } from "../../reducers/sentence";
-import ToolButtonNavMore from "./ToolButtonNavMore";
-import ToolButtonNavSliceTitle from "./ToolButtonNavSliceTitle";
-import { fullUrl } from "../../utils";
-
-const { Text } = Typography;
-
-interface DataNode {
-  title: React.ReactNode;
-  key: string;
-  isLeaf?: boolean;
-  children?: DataNode[];
-}
-interface ISlice {
-  id: number;
-  para: string;
-  len: number;
-}
-interface IWidget {
-  type?: string;
-  articleId?: string;
-}
-const ToolButtonNavWidget = ({ type, articleId }: IWidget) => { // eslint-disable-line
-  const [treeData, setTreeData] = useState<DataNode[]>([]);
-  const [slice, setSlice] = useState<number>(1);
-  const navigate = useNavigate();
-
-  const allSentList = useAppSelector(sentenceList);
-
-  const refresh = () => {
-    const divList = document.querySelectorAll("div.pcd_sent");
-
-    const sentList: string[] = [];
-    for (let index = 0; index < divList.length; index++) {
-      const element = divList[index];
-      const id = element.id.split("_");
-      sentList.push(id[1]);
-    }
-
-    //计算总字符数
-    let allStrLen = 0;
-    allSentList.forEach((value) => {
-      allStrLen += value.origin ? value.origin[0].length : 0;
-    });
-    const oneSliceLen = allStrLen / slice;
-
-    const paraSlice: ISlice[] = [];
-    let currSliceId = 0;
-    let currSliceLen = 0;
-    for (let index = 0; index < sentList.length; index++) {
-      const sent = sentList[index];
-      const currPara = sent.split("-").slice(0, 2).join("-");
-      if (!paraSlice.find((value) => value.para === currPara)) {
-        let paraLen = 0; //段落字符长度
-        allSentList
-          .filter(
-            (value) => value.id.split("-").slice(0, 2).join("-") === currPara
-          )
-          .map((item) => (item.origin ? item.origin[0] : ""))
-          .forEach((value) => {
-            paraLen += value.length;
-          });
-
-        //计算如果放进去或者不放进去哪个更接近块的预设大小
-        let next = currSliceId;
-        if (currSliceLen + paraLen <= oneSliceLen) {
-          //放进去还不到一个块,直接放进去
-          currSliceLen = currSliceLen + paraLen;
-        } else if (currSliceLen === 0) {
-          //当前块里没东西直接放进去
-          if (paraLen >= oneSliceLen) {
-            //此块比一个块大
-            next = currSliceId + 1;
-            currSliceLen = 0;
-          } else {
-            currSliceLen = paraLen;
-          }
-        } else {
-          //放进去超过一个块,需要比较是放进去好还是不放进去好
-          const remain = oneSliceLen - currSliceLen;
-          const extra = currSliceLen + paraLen - oneSliceLen;
-          if (remain < extra) {
-            //移到下一个块
-            currSliceId++;
-            next = currSliceId;
-            currSliceLen = paraLen;
-          } else {
-            //放这个块
-            currSliceLen += paraLen;
-          }
-        }
-        paraSlice.push({ id: currSliceId, para: currPara, len: paraLen });
-        currSliceId = next;
-      }
-    }
-
-    const mSlice: DataNode[] = new Array(currSliceId + 1)
-      .fill(1)
-      .map((_item, index) => {
-        let sliceStrLen = 0;
-        const sliceChildren: string[] = [];
-        const newTree: DataNode[] = paraSlice
-          .filter((value) => value.id === index)
-          .map((item) => {
-            const children = sentList
-              .filter(
-                (value) => value.split("-").slice(0, 2).join("-") === item.para
-              )
-              .map((item1) => {
-                const str = allSentList.find((value) => value.id === item1);
-                return {
-                  title: str
-                    ? str.origin
-                      ? str.origin[0].slice(0, 30)
-                      : item1
-                    : item1,
-                  key: item1,
-                };
-              });
-            sliceStrLen += item.len;
-            sliceChildren.push(item.para);
-            return {
-              title: item.para + "-" + item.len.toString(),
-              key: item.para,
-              children: children,
-            };
-          });
-
-        return {
-          title: (
-            <ToolButtonNavSliceTitle
-              label={
-                <Space>
-                  <Text>{`第${index + 1}组`}</Text>
-                  <Tag>{`${sliceStrLen}`}</Tag>
-                </Space>
-              }
-              onMenuClick={(key: string) => {
-                if (sliceChildren.length > 0) {
-                  const [book, _para] = sliceChildren[0].split("-");
-                  const paraList = sliceChildren.map(
-                    (item) => item.split("-")[1]
-                  );
-                  const url = `/article/para/${book}?par=${paraList.join(",")}`;
-                  console.log("url", url);
-
-                  switch (key) {
-                    case "copy-link":
-                      navigator.clipboard.writeText(fullUrl(url)).then(() => {
-                        message.success("链接地址已经拷贝到剪贴板");
-                      });
-
-                      break;
-                    case "open":
-                      navigate(url);
-                      break;
-                  }
-                }
-              }}
-            />
-          ),
-          key: `slice_${index}`,
-          children: newTree,
-        };
-      });
-    if (mSlice.length > 1) {
-      setTreeData(mSlice);
-    } else if (mSlice.length === 1) {
-      setTreeData(mSlice[0].children ? mSlice[0].children : []);
-    }
-  };
-
-  useEffect(refresh, [slice]);
-  return (
-    <ToolButton
-      title="导航"
-      icon={<CompassOutlined />}
-      content={
-        <>
-          <div style={{ textAlign: "right" }}>
-            <Space>
-              <Button
-                onClick={() => {
-                  refresh();
-                }}
-                size="small"
-                type="link"
-                icon={<ReloadOutlined />}
-              />
-              <ToolButtonNavMore
-                onSliceChange={(value: number) => {
-                  console.log(`selected ${value}`);
-                  setSlice(value);
-                }}
-              />
-            </Space>
-          </div>
-
-          <Tree
-            treeData={treeData}
-            titleRender={(node) => {
-              const ele = document.getElementById(node.key);
-              return (
-                <div
-                  onClick={() => {
-                    ele?.scrollIntoView();
-                  }}
-                >
-                  {node.title}
-                </div>
-              );
-            }}
-          />
-        </>
-      }
-    />
-  );
-};
-
-export default ToolButtonNavWidget;

+ 0 - 47
dashboard-v6/backup/components/article/ToolButtonNavMore.tsx

@@ -1,47 +0,0 @@
-import { SettingOutlined } from "@ant-design/icons";
-import { Button, Dropdown, type MenuProps } from "antd"
-
-interface IWidget {
-  onSliceChange?: Function;
-}
-const CaseFormulaWidget = ({ onSliceChange }: IWidget) => {
-  const sliceOption = new Array(8).fill(1).map((_item, index) => {
-    return { key: index + 2, label: index + 2 };
-  });
-  const items: MenuProps["items"] = [
-    {
-      label: "分组",
-      key: "slice",
-      children: [
-        {
-          label: "不分组",
-          key: 1,
-        },
-        ...sliceOption,
-      ],
-    },
-  ];
-  return (
-    <Dropdown
-      menu={{
-        items: items,
-        onClick: (e) => {
-          console.log("click ", e.key);
-          if (typeof onSliceChange !== "undefined") {
-            onSliceChange(e.key);
-          }
-        },
-      }}
-      placement="bottomRight"
-    >
-      <Button
-        type="text"
-        size="small"
-        icon={<SettingOutlined />}
-        onClick={(e) => e.preventDefault()}
-      />
-    </Dropdown>
-  );
-};
-
-export default CaseFormulaWidget;

+ 0 - 41
dashboard-v6/backup/components/article/ToolButtonNavSliceTitle.tsx

@@ -1,41 +0,0 @@
-import { Dropdown } from "antd";
-import React from "react";
-import { useIntl } from "react-intl";
-
-interface IWidget {
-  label?: React.ReactNode;
-  onMenuClick?: Function;
-}
-
-const ToolButtonNavSliceTitleWidget = ({ label, onMenuClick }: IWidget) => {
-  const intl = useIntl();
-  return (
-    <Dropdown.Button
-      type="text"
-      trigger={["click"]}
-      menu={{
-        items: [
-          {
-            key: "copy-link",
-            label: intl.formatMessage({
-              id: "buttons.copy.link",
-            }),
-          },
-          {
-            key: "open",
-            label: "打开",
-          },
-        ],
-        onClick: (e) => {
-          if (typeof onMenuClick !== "undefined") {
-            onMenuClick(e.key);
-          }
-        },
-      }}
-    >
-      <>{label}</>
-    </Dropdown.Button>
-  );
-};
-
-export default ToolButtonNavSliceTitleWidget;

+ 0 - 145
dashboard-v6/backup/components/article/ToolButtonPr.tsx

@@ -1,145 +0,0 @@
-import { useIntl } from "react-intl";
-import { useEffect, useState } from "react";
-import { Button, Tag, Tree } from "antd";
-import { ReloadOutlined } from "@ant-design/icons";
-
-import { HandOutlinedIcon } from "../../assets/icon";
-import ToolButton from "./ToolButton";
-import { post } from "../../request";
-import type { IUser } from "../auth/User"
-
-interface IPrTreeData {
-  book: number;
-  paragraph: number;
-  word_start: number;
-  word_end: number;
-  channel_id: string;
-  content: string;
-  pr_count: number;
-}
-interface IPrTreeRequestData {
-  book: number;
-  paragraph: number;
-  word_start: number;
-  word_end: number;
-  channel_id: string;
-}
-interface IPrData {
-  content: string;
-  editor?: IUser;
-}
-interface IPrTreeRequest {
-  data: IPrTreeRequestData[];
-}
-interface IPrTreeResponseData {
-  sentence: IPrTreeData;
-  pr: IPrData[];
-}
-interface IPrTreeResponse {
-  ok: boolean;
-  message: string;
-  data: { rows: IPrTreeResponseData[]; count: number };
-}
-interface DataNode {
-  title: string;
-  key: string;
-  isLeaf?: boolean;
-  children?: DataNode[];
-}
-
-interface IWidget {
-  type?: string;
-  articleId?: string;
-}
-const ToolButtonPrWidget = ({ type, articleId }: IWidget) => { // eslint-disable-line
-  const [treeData, setTreeData] = useState<DataNode[]>([]);
-  const [loading, setLoading] = useState(false);
-  const intl = useIntl();
-
-  const refresh = () => {
-    const pr = document.querySelectorAll("div.tran_sent");
-
-    const prRequestData: IPrTreeRequestData[] = [];
-    for (let index = 0; index < pr.length; index++) {
-      const element = pr[index];
-      const id = element.id.split("_");
-      prRequestData.push({
-        book: parseInt(id[0]),
-        paragraph: parseInt(id[1]),
-        word_start: parseInt(id[2]),
-        word_end: parseInt(id[3]),
-        channel_id: id[4],
-      });
-    }
-    console.log("request pr tree", prRequestData);
-    setLoading(true);
-    post<IPrTreeRequest, IPrTreeResponse>("/v2/sent-pr-tree", {
-      data: prRequestData,
-    })
-      .then((json) => {
-        console.log("pr tree", json);
-        if (json.ok) {
-          const newTree: DataNode[] = json.data.rows.map((item) => {
-            const children = item.pr.map((pr) => {
-              return { title: pr.content, key: pr.content };
-            });
-            return {
-              title: item.sentence.content,
-              key: `${item.sentence.book}_${item.sentence.paragraph}_${item.sentence.word_start}_${item.sentence.word_end}_${item.sentence.channel_id}`,
-              children: children,
-            };
-          });
-          setTreeData(newTree);
-        }
-      })
-      .finally(() => setLoading(false));
-  };
-
-  useEffect(() => {
-    //refresh();
-  }, []);
-  return (
-    <ToolButton
-      title="修改建议"
-      icon={<HandOutlinedIcon />}
-      content={
-        <>
-          <div style={{ display: "flex", justifyContent: "space-between" }}>
-            <span></span>
-            <Button
-              type="text"
-              icon={<ReloadOutlined />}
-              loading={loading}
-              onClick={() => {
-                refresh();
-              }}
-            >
-              {intl.formatMessage({
-                id: "buttons.refresh",
-              })}
-            </Button>
-          </div>
-          <Tree
-            treeData={treeData}
-            titleRender={(node) => {
-              const ele = document.getElementById(node.key);
-              const count = node.children?.length;
-              return (
-                <div
-                  onClick={() => {
-                    ele?.scrollIntoView();
-                  }}
-                >
-                  {node.title}
-                  <Tag style={{ borderRadius: 5 }}>{count}</Tag>
-                </div>
-              );
-            }}
-          />
-        </>
-      }
-    />
-  );
-};
-
-export default ToolButtonPrWidget;

+ 0 - 62
dashboard-v6/backup/components/article/ToolButtonSearch.tsx

@@ -1,62 +0,0 @@
-import { SearchOutlined } from "@ant-design/icons";
-
-import ToolButton from "./ToolButton";
-import { Input, Tree, type TreeDataNode } from "antd";
-import { useSearchParams } from "react-router";
-import type { ArticleType } from "./Article";
-import { get } from "../../request";
-import type { IArticleFtsListResponse } from "../../api/Article";
-import type { Key } from "antd/lib/table/interface";
-import { useState } from "react";
-
-const { Search } = Input;
-
-interface IWidget {
-  type?: ArticleType;
-  articleId?: string;
-  anthologyId?: string;
-  channels?: string[];
-}
-const ToolButtonSearchWidget = ({
-  type,
-  articleId,
-  anthologyId,
-  channels,
-}: IWidget) => {
-  const [_searchParams, _setSearchParams] = useSearchParams();
-  const [treeNode, _setTreeNode] = useState<TreeDataNode[]>();
-  const content = (
-    <>
-      <Search
-        placeholder="搜索本章节"
-        onSearch={(
-          value: string,
-          _event?:
-            | React.ChangeEvent<HTMLInputElement>
-            | React.MouseEvent<HTMLElement, MouseEvent>
-            | React.KeyboardEvent<HTMLInputElement>
-            | undefined
-        ) => {
-          if (type === "article") {
-            let url = `/v2/article-fts?id=${articleId}&anthology=${anthologyId}&key=${value}`;
-            url += "&channel=" + channels?.join(",");
-            console.debug("api request", url);
-            get<IArticleFtsListResponse>(url).then((json) => {
-              console.debug("api response", json);
-              if (json.ok) {
-              }
-            });
-          }
-        }}
-        style={{ width: "100%" }}
-      />
-      <Tree onSelect={(_selectedKeys: Key[]) => {}} treeData={treeNode} />
-    </>
-  );
-
-  return (
-    <ToolButton title="搜索" icon={<SearchOutlined />} content={content} />
-  );
-};
-
-export default ToolButtonSearchWidget;

+ 0 - 24
dashboard-v6/backup/components/article/ToolButtonSetting.tsx

@@ -1,24 +0,0 @@
-import { SettingOutlined } from "@ant-design/icons";
-import SettingArticle from "../auth/setting/SettingArticle";
-
-import ToolButton from "./ToolButton";
-import { useIntl } from "react-intl";
-
-interface IWidget {
-  type?: string;
-  articleId?: string;
-}
-const ToolButtonSettingWidget = ({ type, articleId }: IWidget) => { // eslint-disable-line
-  const intl = useIntl();
-  return (
-    <ToolButton
-      title={intl.formatMessage({
-        id: `buttons.setting`,
-      })}
-      icon={<SettingOutlined />}
-      content={<SettingArticle />}
-    />
-  );
-};
-
-export default ToolButtonSettingWidget;

+ 0 - 22
dashboard-v6/backup/components/article/ToolButtonTag.tsx

@@ -1,22 +0,0 @@
-import { TagOutlined } from "@ant-design/icons";
-
-import ToolButton from "./ToolButton";
-
-interface IWidget {
-  type?: string;
-  articleId?: string;
-}
-const ToolButtonTagWidget = ({ articleId }: IWidget) => {
-  const id = articleId?.split("_");
-  let tocWidget = <></>;
-  if (id && id.length > 0) {
-    const sentId = id[0].split("-");
-    if (sentId.length > 1) {
-      tocWidget = <></>;
-    }
-  }
-
-  return <ToolButton title="标签" icon={<TagOutlined />} content={tocWidget} />;
-};
-
-export default ToolButtonTagWidget;

+ 0 - 87
dashboard-v6/backup/components/article/ToolButtonToc.tsx

@@ -1,87 +0,0 @@
-import { MenuOutlined } from "@ant-design/icons";
-import type { Key } from "antd/lib/table/interface"
-import AnthologyTocTree from "../anthology/AnthologyTocTree";
-import type { ArticleType } from "./Article"
-
-import PaliTextToc from "./PaliTextToc";
-import ToolButton from "./ToolButton";
-import TextBookToc from "../anthology/TextBookToc";
-import { useIntl } from "react-intl";
-
-interface IWidget {
-  type?: ArticleType;
-  articleId?: string;
-  anthologyId?: string | null;
-  courseId?: string | null;
-  channels?: string[];
-  onSelect?: Function;
-}
-const ToolButtonTocWidget = ({
-  type,
-  articleId,
-  anthologyId,
-  courseId,
-  channels,
-  onSelect,
-}: IWidget) => {
-  const intl = useIntl();
-  //TODO 都放return里面
-  let tocWidget = <></>;
-  if (type === "chapter" || type === "para") {
-    if (articleId) {
-      const sentId = articleId.split("-");
-      if (sentId.length > 1) {
-        tocWidget = (
-          <PaliTextToc
-            book={parseInt(sentId[0])}
-            para={parseInt(sentId[1])}
-            onSelect={(selectedKeys: Key[]) => {
-              if (typeof onSelect !== "undefined" && selectedKeys.length > 0) {
-                onSelect(selectedKeys[0]);
-              }
-            }}
-          />
-        );
-      }
-    }
-  } else if (type === "article") {
-    if (anthologyId) {
-      tocWidget = (
-        <AnthologyTocTree
-          anthologyId={anthologyId}
-          channels={channels}
-          onClick={(_anthology: string, article: string, target: string) => {
-            if (typeof onSelect !== "undefined") {
-              onSelect(article, target);
-            }
-          }}
-        />
-      );
-    }
-  } else if (type === "textbook") {
-    tocWidget = (
-      <TextBookToc
-        courseId={courseId}
-        channels={channels}
-        onClick={(article: string, target: string) => {
-          console.debug("TextBookToc onClick", article);
-          if (typeof onSelect !== "undefined") {
-            onSelect(article, target);
-          }
-        }}
-      />
-    );
-  }
-
-  return (
-    <ToolButton
-      title={intl.formatMessage({
-        id: "labels.table-of-content",
-      })}
-      icon={<MenuOutlined />}
-      content={tocWidget}
-    />
-  );
-};
-
-export default ToolButtonTocWidget;

+ 0 - 401
dashboard-v6/backup/components/article/TreeText.tsx

@@ -1,401 +0,0 @@
-import React, { useState, useCallback, useEffect } from "react";
-import { Tree, Segmented, Spin } from "antd";
-import type { TreeDataNode } from "antd";
-import {
-  BookOutlined,
-  FileTextOutlined,
-  FolderOutlined,
-  FontSizeOutlined,
-  TranslationOutlined,
-} from "@ant-design/icons";
-import { get } from "../../request";
-import type {
-  IPaliListResponse,
-  IPaliParagraphResponse,
-  ISentenceListResponse,
-} from "../../api/Corpus";
-
-// 定义节点类型
-type NodeType =
-  | "book"
-  | "chapter"
-  | "paragraph"
-  | "sentence"
-  | "text"
-  | "resources"
-  | "translations"
-  | "similar"
-  | "preview";
-
-// 定义模式类型
-type ParagraphMode = "preview" | "edit";
-
-// 定义基础节点数据接口
-interface BaseNodeData {
-  id: string;
-  title: string;
-  type: NodeType;
-  isLeaf?: boolean;
-  preview?: string;
-  content?: React.ReactNode;
-  children?: BaseNodeData[];
-}
-
-// 定义树节点接口
-interface TreeNode extends TreeDataNode {
-  key: string;
-  type: NodeType;
-  isLeaf?: boolean;
-  children?: TreeNode[];
-}
-
-// 定义API响应接口
-type ApiResponse = BaseNodeData;
-
-// 定义组件状态接口
-interface IWidget {
-  type?: NodeType;
-  rootId?: string;
-  channelsId?: string[];
-}
-const TreeTextComponent = ({ type, rootId, channelsId }: IWidget) => {
-  const [treeData, setTreeData] = useState<BaseNodeData[]>([]);
-  const [loadingKeys, setLoadingKeys] = useState<string[]>([]);
-  const [paragraphModes, setParagraphModes] = useState<
-    Record<string, ParagraphMode>
-  >({});
-
-  useEffect(() => {
-    if (type === "chapter") {
-      const url = `/v2/palitext/${rootId}`;
-      get<IPaliParagraphResponse>(url).then((json) => {
-        if (json.ok) {
-          setTreeData([
-            {
-              id: json.data.uid,
-              title: json.data.text,
-              type: "chapter",
-            },
-          ]);
-        }
-      });
-    }
-  }, [rootId, type]);
-
-  // 模拟API调用
-  const mockApiCall = useCallback(
-    async (type: NodeType, key: string): Promise<ApiResponse[]> => {
-      console.log("Calling API:", type, key);
-      //await new Promise((resolve) => setTimeout(resolve, 1000)); // 模拟网络延迟
-
-      // 模拟不同类型节点的响应数据
-      if (type === "book") {
-        return [
-          { id: "chapter_1", title: "第一章", type: "chapter" },
-          { id: "chapter_2", title: "第二章", type: "chapter" },
-          { id: "chapter_3", title: "第三章", type: "chapter" },
-        ];
-      } else if (type === "chapter") {
-        const url = `/v2/palitext?view=children&id=${key}`;
-        const paragraphs = await get<IPaliListResponse>(url);
-        return paragraphs.data.rows.map((item) => {
-          if (item.level < 8) {
-            return {
-              id: item.uid,
-              title: item.toc,
-              type: "chapter",
-            };
-          } else {
-            return {
-              id: `${item.book}-${item.paragraph}`,
-              title: item.paragraph.toString(),
-              type: "paragraph",
-              preview: item.text,
-            };
-          }
-        });
-      } else if (type === "paragraph") {
-        const [book, paragraph] = key.split("-");
-        const url = `/v2/sentence?view=paragraph&book=${book}&para=${paragraph}&channels=${channelsId?.join()}`;
-        const res = await get<ISentenceListResponse>(url);
-        return res.data.rows.map((item) => {
-          return {
-            id: item.id ?? "123",
-            title: `${item.book}-${item.paragraph}-${item.word_start}-${item.word_end}`,
-            type: "sentence",
-            children: [
-              {
-                id: "text_node",
-                title: item.content,
-                type: "text",
-                isLeaf: true,
-              },
-              {
-                id: "resources",
-                title: "资源",
-                type: "resources",
-                children: [
-                  {
-                    id: "translations",
-                    title: "参考译文",
-                    type: "translations",
-                  },
-                  {
-                    id: "similar",
-                    title: "相似句",
-                    type: "similar",
-                  },
-                ],
-              },
-            ],
-          };
-        });
-      } else if (type === "similar") {
-        return [
-          {
-            id: "text_node",
-            title: "句子文本:This is the original sentence text.",
-            type: "text",
-            isLeaf: true,
-          },
-          {
-            id: "resources",
-            title: "资源",
-            type: "resources",
-            children: [
-              {
-                id: "translations",
-                title: "参考译文",
-                type: "translations",
-              },
-              {
-                id: "similar",
-                title: "相似句",
-                type: "similar",
-              },
-            ],
-          },
-        ];
-      }
-      return [];
-    },
-    [channelsId]
-  );
-
-  // 获取节点图标
-  const getNodeIcon = (type: NodeType): React.ReactNode => {
-    switch (type) {
-      case "book":
-        return <BookOutlined />;
-      case "chapter":
-        return <FolderOutlined />;
-      case "paragraph":
-        return <FileTextOutlined />;
-      case "sentence":
-        return <FontSizeOutlined />;
-      case "translations":
-        return <TranslationOutlined />;
-      case "similar":
-        return <FileTextOutlined />;
-      default:
-        return null;
-    }
-  };
-
-  // 构建树节点
-  const buildTreeNode = (
-    node: BaseNodeData,
-    parentKey: string = ""
-  ): TreeNode => {
-    const key = parentKey
-      ? `${parentKey}_${node.id}`
-      : `${node.type}_${node.id}`;
-    const isLoading = loadingKeys.includes(key);
-
-    let children: TreeNode[] = [];
-    let hasChildren = false;
-
-    if (node.type === "paragraph") {
-      const mode = paragraphModes[key] || "preview";
-
-      if (mode === "preview" && node.preview) {
-        // 预览模式:只显示预览文字节点
-        children = [
-          {
-            title: `预览:${node.preview}`,
-            key: `${key}_preview`,
-            type: "preview" as NodeType,
-            isLeaf: true,
-            icon: <FontSizeOutlined style={{ color: "#1890ff" }} />,
-          },
-        ];
-      } else if (mode === "edit" && node.children) {
-        // 编辑模式:显示子sentence节点
-        children = node.children.map((child) => buildTreeNode(child, key));
-      }
-
-      hasChildren = Boolean(node.children && node.children.length > 0);
-    } else if (node.children) {
-      children = node.children.map((child) => buildTreeNode(child, key));
-      hasChildren = true;
-    } else if (
-      !node.isLeaf &&
-      ["book", "chapter", "paragraph", "sentence"].includes(node.type)
-    ) {
-      hasChildren = true;
-    }
-
-    const handleModeChange = (value: string | number): void => {
-      setParagraphModes((prev) => ({
-        ...prev,
-        [key]: value as ParagraphMode,
-      }));
-    };
-
-    const handleSegmentedClick = (e: React.MouseEvent): void => {
-      e.stopPropagation();
-    };
-
-    const treeNode: TreeNode = {
-      title: (
-        <div style={{ display: "inline-block" }}>
-          <div
-            style={{
-              display: "flex",
-              alignItems: "center",
-              justifyContent: "space-between",
-              width: "100%",
-            }}
-          >
-            {node.content ?? <span>{node.title}</span>}
-            {node.type === "paragraph" && (
-              <Segmented
-                size="small"
-                value={paragraphModes[key] || "preview"}
-                onChange={handleModeChange}
-                options={[
-                  { label: "预览", value: "preview" },
-                  { label: "编辑", value: "edit" },
-                ]}
-                style={{ marginLeft: 8 }}
-                onClick={handleSegmentedClick}
-              />
-            )}
-          </div>
-        </div>
-      ),
-      key,
-      type: node.type,
-      icon: isLoading ? <Spin size="small" /> : getNodeIcon(node.type),
-      isLeaf: node.isLeaf || (!hasChildren && node.type === "text"),
-      children: children.length > 0 ? children : undefined,
-    };
-
-    return treeNode;
-  };
-
-  // 懒加载处理
-  const onLoadData = async (node: TreeNode): Promise<void> => {
-    const { key, type } = node;
-
-    if (loadingKeys.includes(key)) return;
-
-    setLoadingKeys((prev) => [...prev, key]);
-
-    try {
-      const id = key.split("_").pop() || "";
-      const data = await mockApiCall(type, id);
-
-      // 更新树数据
-      const updateTreeData = (
-        nodes: BaseNodeData[],
-        parentKey: string = ""
-      ): BaseNodeData[] => {
-        return nodes.map((node) => {
-          const currentKey = parentKey
-            ? `${parentKey}_${node.id}`
-            : `${node.type}_${node.id}`;
-          if (currentKey === key) {
-            return {
-              ...node,
-              children: data.map((child) => ({
-                ...child,
-                children: child.children || [],
-              })),
-            };
-          } else if (node.children) {
-            return {
-              ...node,
-              children: updateTreeData(node.children, currentKey),
-            };
-          }
-          return node;
-        });
-      };
-
-      setTreeData((prev) => updateTreeData(prev));
-    } catch (error) {
-      console.error("加载数据失败:", error);
-    } finally {
-      setLoadingKeys((prev) => prev.filter((k) => k !== key));
-    }
-  };
-
-  return (
-    <div
-      style={{ padding: 20, backgroundColor: "#f5f5f5", minHeight: "100vh" }}
-    >
-      <div
-        style={{
-          backgroundColor: "white",
-          padding: 20,
-          borderRadius: 8,
-          boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
-        }}
-      >
-        <h2 style={{ marginBottom: 20, color: "#1890ff" }}>
-          树状文本展示组件 (TypeScript)
-        </h2>
-        <Tree
-          showIcon
-          showLine={true}
-          loadData={onLoadData}
-          treeData={treeData.map((node) => buildTreeNode(node))}
-          style={{ fontSize: 14 }}
-          blockNode
-        />
-      </div>
-
-      {/* 使用说明 */}
-      <div
-        style={{
-          marginTop: 20,
-          backgroundColor: "white",
-          padding: 20,
-          borderRadius: 8,
-          boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
-        }}
-      >
-        <h3>功能说明:</h3>
-        <ul style={{ lineHeight: 1.8 }}>
-          <li>📚 点击book节点懒加载chapter数据</li>
-          <li>📄 点击chapter节点懒加载paragraph数据</li>
-          <li>📝 paragraph节点右侧可切换预览/编辑模式</li>
-          <li>👁️ 预览模式:只显示预览文字</li>
-          <li>✏️ 编辑模式:显示子sentence节点</li>
-          <li>📖 sentence节点包含句子文本和资源(参考译文、相似句)</li>
-        </ul>
-
-        <h4 style={{ marginTop: 20 }}>TypeScript 特性:</h4>
-        <ul style={{ lineHeight: 1.8 }}>
-          <li>🔒 完整的类型定义和类型安全</li>
-          <li>📝 接口定义清晰,便于维护</li>
-          <li>⚡ 更好的IDE支持和代码提示</li>
-          <li>🛡️ 编译时错误检查</li>
-        </ul>
-      </div>
-    </div>
-  );
-};
-
-export default TreeTextComponent;

+ 0 - 71
dashboard-v6/backup/components/article/TypeAnthology.tsx

@@ -1,71 +0,0 @@
-import type { ArticleMode, ArticleType } from "./Article";
-import AnthologyDetail from "./AnthologyDetail";
-import "./article.css";
-import { useState, useMemo } from "react";
-import ErrorResult from "../general/ErrorResult";
-import ArticleSkeleton from "./ArticleSkeleton";
-
-interface IWidget {
-  type?: ArticleType;
-  articleId?: string;
-  mode?: ArticleMode | null;
-  channelId?: string | null;
-  onArticleChange?: (
-    type: string,
-    articleId: string,
-    target: string,
-    extra?: { anthologyId?: string }
-  ) => void;
-  onFinal?: () => void;
-  onLoad?: () => void;
-  onTitle?: (title: string) => void;
-}
-
-const TypeAnthologyWidget = ({
-  channelId,
-  articleId,
-  onArticleChange,
-  onTitle,
-}: IWidget) => {
-  const [loading, setLoading] = useState(false);
-  const [errorCode, setErrorCode] = useState<number | null>(null);
-
-  /** ✅ 避免每次 render 都 split */
-  const channels = useMemo(
-    () => (channelId ? channelId.split("_") : undefined),
-    [channelId]
-  );
-
-  return (
-    <div>
-      {loading && <ArticleSkeleton />}
-
-      {!loading && errorCode && <ErrorResult code={errorCode} />}
-
-      {!errorCode && (
-        <AnthologyDetail
-          visible={!loading}
-          channels={channels}
-          aid={articleId}
-          onArticleClick={(anthologyId, articleId, target) => {
-            onArticleChange?.("article", articleId, target, {
-              anthologyId,
-            });
-          }}
-          onLoading={setLoading}
-          onError={(error: unknown) => {
-            console.error(error);
-            //TODO get real error code
-            setErrorCode(404);
-            //setErrorCode(message); //old code
-          }}
-          onTitle={(value) => {
-            onTitle?.(value);
-          }}
-        />
-      )}
-    </div>
-  );
-};
-
-export default TypeAnthologyWidget;

+ 0 - 101
dashboard-v6/backup/components/article/TypeArticle.tsx

@@ -1,101 +0,0 @@
-import { useState } from "react";
-import { Modal } from "antd";
-import { ExclamationCircleOutlined } from "@ant-design/icons";
-import type { IArticleDataResponse } from "../../api/Article";
-import type { ArticleMode, ArticleType } from "./Article";
-import TypeArticleReader from "./TypeArticleReader";
-import ArticleEdit from "./ArticleEdit";
-
-interface IWidget {
-  type?: ArticleType;
-  articleId?: string;
-  mode?: ArticleMode | null;
-  channelId?: string | null;
-  parentChannels?: string[];
-  anthologyId?: string | null;
-  active?: boolean;
-  hideInteractive?: boolean;
-  hideTitle?: boolean;
-  isSubWindow?: boolean;
-  onArticleChange?: Function;
-  onArticleEdit?: Function;
-  onLoad?: Function;
-  onAnthologySelect?: Function;
-}
-const TypeArticleWidget = ({
-  type,
-  channelId,
-  parentChannels,
-  articleId,
-  anthologyId,
-  mode = "read",
-  active = false,
-  hideInteractive = false,
-  hideTitle = false,
-  isSubWindow = false,
-  onArticleChange,
-  onLoad,
-  onAnthologySelect,
-  onArticleEdit,
-}: IWidget) => {
-  const [edit, setEdit] = useState(false);
-  return (
-    <div>
-      {edit ? (
-        <ArticleEdit
-          anthologyId={anthologyId ? anthologyId : undefined}
-          articleId={articleId}
-          resetButton="cancel"
-          onSubmit={(value: IArticleDataResponse) => {
-            if (typeof onArticleEdit !== "undefined") {
-              onArticleEdit(value);
-            }
-            setEdit(false);
-          }}
-          onCancel={() => {
-            Modal.confirm({
-              icon: <ExclamationCircleOutlined />,
-              content: "放弃修改吗?",
-              okType: "danger",
-              onOk() {
-                setEdit(false);
-              },
-            });
-          }}
-        />
-      ) : (
-        <TypeArticleReader
-          isSubWindow={isSubWindow}
-          type={type}
-          channelId={channelId}
-          parentChannels={parentChannels}
-          articleId={articleId}
-          anthologyId={anthologyId}
-          mode={mode}
-          active={active}
-          hideInteractive={hideInteractive}
-          hideTitle={hideTitle}
-          onArticleChange={onArticleChange}
-          onLoad={(data: IArticleDataResponse) => {
-            if (typeof onLoad !== "undefined") {
-              onLoad(data);
-            }
-          }}
-          onAnthologySelect={(
-            id: string,
-            e: React.MouseEvent<HTMLElement, MouseEvent>
-          ) => {
-            if (typeof onAnthologySelect !== "undefined") {
-              onAnthologySelect(id, e);
-            }
-          }}
-          onEdit={() => {
-            setEdit(true);
-          }}
-        />
-      )}
-    </div>
-  );
-};
-
-export default TypeArticleWidget;

+ 0 - 335
dashboard-v6/backup/components/article/TypeArticleReader.tsx

@@ -1,335 +0,0 @@
-import { useEffect, useState } from "react";
-import { Divider, message, Space, Tag } from "antd";
-
-import { get } from "../../request";
-import type {
-  IAnthologyResponse,
-  IArticleDataResponse,
-  IArticleNavData,
-  IArticleNavResponse,
-  IArticleResponse,
-} from "../../api/Article";
-import ArticleView, { type IFirstAnthology } from "./ArticleView";
-import TocTree from "./TocTree";
-import PaliText from "../template/Wbw/PaliText";
-import type { ITocPathNode } from "../../../src/components/tipitaka/TocPath";
-import type { ArticleMode, ArticleType } from "./Article";
-import "./article.css";
-import ArticleSkeleton from "./ArticleSkeleton";
-import ErrorResult from "../general/ErrorResult";
-import NavigateButton from "./NavigateButton";
-import InteractiveArea from "../discussion/InteractiveArea";
-import TypeArticleReaderToolbar from "./TypeArticleReaderToolbar";
-import type { IChannel } from "../channel/Channel";
-
-interface IWidget {
-  type?: ArticleType;
-  articleId?: string;
-  mode?: ArticleMode | null;
-  channelId?: string | null;
-  parentChannels?: string[];
-  anthologyId?: string | null;
-  active?: boolean;
-  hideInteractive?: boolean;
-  hideTitle?: boolean;
-  isSubWindow?: boolean;
-  onArticleChange?: Function;
-  onLoad?: Function;
-  onAnthologySelect?: Function;
-  onEdit?: Function;
-}
-const TypeArticleReaderWidget = ({
-  type,
-  channelId,
-  parentChannels,
-  articleId,
-  anthologyId,
-  mode = "read",
-  active = false,
-  hideInteractive = false,
-  hideTitle = false,
-  isSubWindow = false,
-  onArticleChange,
-  onLoad,
-  onAnthologySelect,
-  onEdit,
-}: IWidget) => {
-  const [articleData, setArticleData] = useState<IArticleDataResponse>();
-  const [articleHtml, setArticleHtml] = useState<string[]>(["<span />"]);
-  const [extra, setExtra] = useState(<></>);
-  const [loading, setLoading] = useState(false);
-  const [errorCode, setErrorCode] = useState<number>();
-  const [currPath, setCurrPath] = useState<ITocPathNode[]>();
-  const [nav, setNav] = useState<IArticleNavData>();
-  const [_defaultChannel, setDefaultChannel] = useState<IChannel | null>();
-
-  const channels = channelId?.split("_");
-
-  const srcDataMode = mode === "edit" || mode === "wbw" ? "edit" : "read";
-
-  //创建时获取文集channel
-  useEffect(() => {
-    if (!anthologyId) {
-      return;
-    }
-    if (channelId) {
-      return;
-    }
-    const url = `/v2/anthology/${anthologyId}`;
-    console.info("api request", url);
-
-    get<IAnthologyResponse>(url).then((json) => {
-      if (json.ok) {
-        if (json.data.default_channel) {
-          if (typeof onArticleChange === "undefined") {
-            //自控
-            setDefaultChannel(json.data.default_channel);
-          } else {
-            //外控
-            onArticleChange("article", articleId, null);
-          }
-        } else {
-          setDefaultChannel(null);
-        }
-      }
-    });
-  }, []);
-
-  useEffect(() => {
-    console.log("srcDataMode", srcDataMode);
-    if (!active) {
-      return;
-    }
-
-    if (typeof type === "undefined") {
-      return;
-    }
-
-    let mChannels: string[] = [];
-    if (channelId) {
-      mChannels.push(channelId);
-    }
-    if (parentChannels) {
-      mChannels = [...parentChannels, ...mChannels];
-    }
-    let url = `/v2/article/${articleId}?mode=${srcDataMode}`;
-    if (mChannels.length > 0) {
-      url += `&channel=${mChannels.join("_")}`;
-    }
-    url += anthologyId ? `&anthology=${anthologyId}` : "";
-    console.info("article api request", url);
-    setLoading(true);
-    get<IArticleResponse>(url)
-      .then((json) => {
-        console.info("article api response", json);
-        if (json.ok) {
-          setArticleData(json.data);
-          setCurrPath(json.data.path);
-          if (json.data.html) {
-            setArticleHtml([json.data.html]);
-          } else if (json.data.content) {
-            setArticleHtml([json.data.content]);
-          } else {
-            setArticleHtml([""]);
-          }
-          setExtra(
-            <TocTree
-              treeData={json.data.toc?.map((item) => {
-                const strTitle = item.title ? item.title : item.pali_title;
-                const key = item.key
-                  ? item.key
-                  : `${item.book}-${item.paragraph}`;
-                const progress = item.progress?.map((item, id) => (
-                  <Tag key={id}>{Math.round(item * 100) + "%"}</Tag>
-                ));
-                return {
-                  key: key,
-                  title: (
-                    <Space>
-                      <PaliText
-                        text={strTitle === "" ? "[unnamed]" : strTitle}
-                      />
-                      {progress}
-                    </Space>
-                  ),
-                  level: item.level,
-                };
-              })}
-              onClick={(
-                id: string,
-                e: React.MouseEvent<HTMLSpanElement, MouseEvent>
-              ) => {
-                const target = e.ctrlKey || e.metaKey ? "_blank" : "self";
-                if (typeof onArticleChange !== "undefined") {
-                  onArticleChange("article", id, target);
-                }
-              }}
-            />
-          );
-
-          if (typeof onLoad !== "undefined") {
-            onLoad(json.data);
-          }
-        } else {
-          console.error("json", json);
-          message.error(json.message);
-        }
-      })
-      .finally(() => {
-        setLoading(false);
-      })
-      .catch((e) => {
-        console.error(e);
-        setErrorCode(e);
-      });
-  }, [active, type, articleId, srcDataMode, channelId, anthologyId]);
-
-  useEffect(() => {
-    const url = `/v2/nav-article/${articleId}_${anthologyId}`;
-    console.info("api request", url);
-    get<IArticleNavResponse>(url)
-      .then((json) => {
-        console.debug("api response", json);
-        if (json.ok) {
-          setNav(json.data);
-        }
-      })
-      .catch((e) => {
-        console.error(e);
-      });
-  }, [anthologyId, articleId]);
-
-  let anthology: IFirstAnthology | undefined;
-  if (articleData?.anthology_count && articleData.anthology_first) {
-    anthology = {
-      id: articleData.anthology_first.uid,
-      title: articleData.anthology_first.title,
-      count: articleData?.anthology_count,
-    };
-  }
-
-  const title = articleData?.title_text ?? articleData?.title;
-
-  let endOfChapter = false;
-  if (nav?.curr && nav?.next) {
-    if (nav?.curr?.level > nav?.next?.level) {
-      endOfChapter = true;
-    }
-  }
-
-  let topOfChapter = false;
-  if (nav?.curr && nav?.prev) {
-    if (nav?.curr?.level > nav?.prev?.level) {
-      topOfChapter = true;
-    }
-  }
-
-  return (
-    <div>
-      {loading ? (
-        <ArticleSkeleton />
-      ) : errorCode ? (
-        <ErrorResult code={errorCode} />
-      ) : (
-        <>
-          <TypeArticleReaderToolbar
-            title={title}
-            articleId={articleId}
-            anthologyId={anthologyId}
-            role={articleData?.role}
-            isSubWindow={isSubWindow}
-            onEdit={() => {
-              if (typeof onEdit !== "undefined") {
-                onEdit();
-              }
-            }}
-            onAnthologySelect={(
-              id: string,
-              e: React.MouseEvent<HTMLElement, MouseEvent>
-            ) => {
-              if (typeof onAnthologySelect !== "undefined") {
-                onAnthologySelect(id, e);
-              }
-            }}
-          />
-          <ArticleView
-            id={articleData?.uid}
-            title={title}
-            subTitle={articleData?.subtitle}
-            summary={articleData?.summary}
-            content={articleData ? articleData.content : ""}
-            html={articleHtml}
-            path={currPath}
-            created_at={articleData?.created_at}
-            updated_at={articleData?.updated_at}
-            channels={channels}
-            type={type}
-            articleId={articleId}
-            anthology={anthology}
-            hideTitle={hideTitle}
-            onPathChange={(
-              node: ITocPathNode,
-              e: React.MouseEvent<
-                HTMLSpanElement | HTMLAnchorElement,
-                MouseEvent
-              >
-            ) => {
-              let newType = type;
-              if (node.level === 0) {
-                newType = "anthology";
-              } else {
-                newType = "article";
-              }
-              if (typeof onArticleChange !== "undefined") {
-                const newArticleId = node.key;
-                const target = e.ctrlKey || e.metaKey ? "_blank" : "self";
-                onArticleChange(newType, newArticleId, target);
-              }
-            }}
-          />
-          <Divider />
-          {extra}
-          <Divider />
-          <NavigateButton
-            prevTitle={nav?.prev?.title}
-            nextTitle={nav?.next?.title}
-            topOfChapter={topOfChapter}
-            endOfChapter={endOfChapter}
-            path={currPath}
-            onNext={() => {
-              if (typeof onArticleChange !== "undefined") {
-                onArticleChange("article", nav?.next?.article_id);
-              }
-            }}
-            onPrev={() => {
-              if (typeof onArticleChange !== "undefined") {
-                onArticleChange("article", nav?.prev?.article_id);
-              }
-            }}
-            onPathChange={(key: string) => {
-              if (typeof onArticleChange !== "undefined") {
-                const node = currPath?.find((value) => value.key === key);
-                if (node) {
-                  let newType = type;
-                  if (node.level === 0) {
-                    newType = "anthology";
-                  } else {
-                    newType = "article";
-                  }
-                  onArticleChange(newType, node.key, "_self");
-                }
-              }
-            }}
-          />
-          {hideInteractive ? (
-            <></>
-          ) : (
-            <InteractiveArea resType={"article"} resId={articleId} />
-          )}
-        </>
-      )}
-    </div>
-  );
-};
-
-export default TypeArticleReaderWidget;

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio