Przeglądaj źródła

Merge pull request #1753 from visuddhinanda/agile

添加页码引用模版
visuddhinanda 2 lat temu
rodzic
commit
234090119d

+ 25 - 0
dashboard/src/components/api/Article.ts

@@ -178,3 +178,28 @@ export interface IDeleteResponse {
   message: string;
   data: number;
 }
+
+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;
+}

+ 12 - 0
dashboard/src/components/article/Article.tsx

@@ -4,6 +4,7 @@ import TypeAnthology from "./TypeAnthology";
 import TypeTerm from "./TypeTerm";
 import TypePali from "./TypePali";
 import "./article.css";
+import TypePage from "./TypePage";
 
 export type ArticleMode = "read" | "edit" | "wbw";
 export type ArticleType =
@@ -133,6 +134,17 @@ const ArticleWidget = ({
             }
           }}
         />
+      ) : type === "page" ? (
+        <TypePage
+          articleId={articleId}
+          channelId={channelId}
+          mode={mode}
+          onArticleChange={(type: ArticleType, id: string) => {
+            if (typeof onArticleChange !== "undefined") {
+              onArticleChange(type, id);
+            }
+          }}
+        />
       ) : (
         <></>
       )}

+ 73 - 0
dashboard/src/components/article/NavigateButton.tsx

@@ -0,0 +1,73 @@
+import { Button, Space, Typography } from "antd";
+import { DoubleRightOutlined, DoubleLeftOutlined } from "@ant-design/icons";
+
+const { Paragraph, Text } = Typography;
+
+const EllipsisMiddle: React.FC<{
+  suffixCount: number;
+  maxWidth: number;
+  children?: string;
+}> = ({ suffixCount, maxWidth = 500, children = "" }) => {
+  const start = children.slice(0, children.length - suffixCount).trim();
+  const suffix = children.slice(-suffixCount).trim();
+  return (
+    <Text style={{ maxWidth: maxWidth }} ellipsis={{ suffix }}>
+      {start}
+    </Text>
+  );
+};
+
+interface IWidget {
+  title?: string;
+  prevTitle?: string;
+  nextTitle?: string;
+  onPrev?: Function;
+  onNext?: Function;
+}
+const NavigateButtonWidget = ({
+  title,
+  prevTitle,
+  nextTitle,
+  onPrev,
+  onNext,
+}: IWidget) => {
+  return (
+    <Paragraph style={{ display: "flex", justifyContent: "space-between" }}>
+      <Button
+        disabled={typeof prevTitle === "undefined"}
+        size="large"
+        onClick={(event: React.MouseEvent<HTMLElement, MouseEvent>) => {
+          if (typeof onPrev !== "undefined") {
+            onPrev(event);
+          }
+        }}
+      >
+        <Space>
+          <DoubleLeftOutlined />
+          <EllipsisMiddle maxWidth={300} suffixCount={7}>
+            {prevTitle}
+          </EllipsisMiddle>
+        </Space>
+      </Button>
+      <Text>{title}</Text>
+      <Button
+        size="large"
+        disabled={typeof nextTitle === "undefined"}
+        onClick={(event: React.MouseEvent<HTMLElement, MouseEvent>) => {
+          if (typeof onNext !== "undefined") {
+            onNext(event);
+          }
+        }}
+      >
+        <Space>
+          <EllipsisMiddle maxWidth={300} suffixCount={7}>
+            {nextTitle}
+          </EllipsisMiddle>
+          <DoubleRightOutlined />
+        </Space>
+      </Button>
+    </Paragraph>
+  );
+};
+
+export default NavigateButtonWidget;

+ 152 - 0
dashboard/src/components/article/TypePage.tsx

@@ -0,0 +1,152 @@
+import { useEffect, useState } from "react";
+
+import { get } from "../../request";
+import { IPageNavData, IPageNavResponse } from "../api/Article";
+
+import { ArticleMode, ArticleType } from "./Article";
+import "./article.css";
+import { message } from "antd";
+
+import { bookName } from "../fts/book_name";
+import TypePali from "./TypePali";
+import NavigateButton from "./NavigateButton";
+
+interface IParam {
+  articleId?: string;
+  mode?: ArticleMode | null;
+  channelId?: string | null;
+  book?: string | null;
+  para?: string | null;
+}
+interface IWidget {
+  articleId?: string;
+  mode?: ArticleMode | null;
+  channelId?: string | null;
+  onArticleChange?: Function;
+  onFinal?: Function;
+  onLoad?: Function;
+}
+const TypeTermWidget = ({
+  channelId,
+  articleId,
+  mode = "read",
+  onArticleChange,
+}: IWidget) => {
+  /**
+   * 页面加载
+   * M 缅文页码
+   * P PTS页码
+   * V vri页码
+   * T 泰文页码
+   * O 其他
+   * para 缅文段落号
+   * url 格式 /article/page/M-dīghanikāya-2-10
+   * 书名在 dashboard\src\components\fts\book_name.ts
+   */
+
+  const [paramPali, setParamPali] = useState<IParam>();
+  const [nav, setNav] = useState<IPageNavData>();
+
+  useEffect(() => {
+    if (typeof articleId === "undefined") {
+      return;
+    }
+
+    const pageParam = articleId.split("_");
+    if (pageParam.length < 4) {
+      return;
+    }
+    //查询书号
+    const booksId = bookName
+      .filter((value) => value.term === pageParam[1])
+      .map((item) => item.id)
+      .join("_");
+    const url = `/v2/nav-page/${pageParam[0].toUpperCase()}-${booksId}-${
+      pageParam[2]
+    }-${pageParam[3]}`;
+
+    console.log("url", url);
+    get<IPageNavResponse>(url)
+      .then((json) => {
+        if (json.ok) {
+          const data = json.data;
+          setNav(data);
+          const begin = data.curr.paragraph;
+          const end = data.next.paragraph;
+          let para: number[] = [];
+          for (let index = begin; index <= end; index++) {
+            para.push(index);
+          }
+          setParamPali({
+            articleId: `${data.curr.book}-${data.curr.paragraph}`,
+            book: data.curr.book.toString(),
+            para: para.join(),
+            mode: mode,
+            channelId: channelId,
+          });
+        } else {
+          message.error(json.message);
+        }
+      })
+      .finally(() => {})
+      .catch((e) => {
+        console.error(e);
+      });
+  }, [articleId, channelId, mode]);
+
+  return (
+    <div>
+      {paramPali ? (
+        <>
+          <TypePali
+            type={"para"}
+            {...paramPali}
+            onArticleChange={(type: ArticleType, id: string) => {
+              if (typeof onArticleChange !== "undefined") {
+                onArticleChange(type, id);
+              }
+            }}
+          />
+          <NavigateButton
+            prevTitle={nav?.prev.page.toString()}
+            nextTitle={nav?.next.page.toString()}
+            onNext={() => {
+              if (typeof onArticleChange !== "undefined") {
+                if (typeof articleId === "undefined") {
+                  return;
+                }
+                const pageParam = articleId.split("_");
+                if (pageParam.length < 4) {
+                  return;
+                }
+                const id = `${pageParam[0]}-${pageParam[1]}-${pageParam[2]}-${
+                  parseInt(pageParam[3]) + 1
+                }`;
+                onArticleChange("page", id);
+              }
+            }}
+            onPrev={() => {
+              if (typeof onArticleChange !== "undefined") {
+                if (typeof articleId === "undefined") {
+                  return;
+                }
+                const pageParam = articleId.split("_");
+                if (pageParam.length < 4) {
+                  return;
+                }
+                const id = `${pageParam[0]}-${pageParam[1]}-${pageParam[2]}-${
+                  parseInt(pageParam[3]) - 1
+                }`;
+                onArticleChange("page", id);
+              }
+            }}
+          />
+        </>
+      ) : (
+        <>loading</>
+      )}
+    </div>
+  );
+};
+
+export default TypeTermWidget;

+ 0 - 1
dashboard/src/components/article/TypeTerm.tsx

@@ -86,7 +86,6 @@ const TypeTermWidget = ({
         <ErrorResult code={errorCode} />
       ) : (
         <>
-          {" "}
           <ArticleView
             id={articleData?.uid}
             title={articleData?.title}

+ 6 - 1
dashboard/src/components/fts/FtsBookList.tsx

@@ -166,7 +166,12 @@ const FtsBookListWidget = ({
         title={
           <Space>
             {"总计"}
-            <Badge size="small" count={total} color="lime" />
+            <Badge
+              size="small"
+              count={total}
+              overflowCount={999}
+              color="lime"
+            />
             {"本书"}
           </Space>
         }

+ 33 - 2
dashboard/src/components/template/Article.tsx

@@ -3,6 +3,9 @@ import { Typography } from "antd";
 import { useState } from "react";
 import Article, { ArticleType } from "../article/Article";
 import { Link } from "react-router-dom";
+import { fullUrl } from "../../utils";
+
+const { Text } = Typography;
 
 export type TDisplayStyle = "modal" | "card" | "toggle" | "link";
 interface IWidgetChapterCtl {
@@ -43,15 +46,43 @@ export const ArticleCtl = ({
     />
   );
   let output = <></>;
+  let articleLink = `/article/${type}/${id}?mode=read`;
+  articleLink += channel ? `&channel=${channel}` : "";
+
   switch (style) {
     case "modal":
       output = (
         <>
-          <Typography.Link onClick={showModal}>{aTitle}</Typography.Link>
+          <Typography.Link
+            onClick={(event: React.MouseEvent<HTMLElement, MouseEvent>) => {
+              if (event.ctrlKey || event.metaKey) {
+                let link = `/article/${type}/${id}?mode=read`;
+                link += channel ? `&channel=${channel}` : "";
+                window.open(fullUrl(link), "_blank");
+              } else {
+                showModal();
+              }
+            }}
+          >
+            {aTitle}
+          </Typography.Link>
           <Modal
             width={"80%"}
             style={{ maxWidth: 1000 }}
-            title={aTitle}
+            title={
+              <div
+                style={{
+                  display: "flex",
+                  justifyContent: "space-between",
+                  marginRight: 30,
+                }}
+              >
+                <Text>{aTitle}</Text>
+                <Link to={articleLink} target="_blank">
+                  {"新窗口打开"}
+                </Link>
+              </div>
+            }
             open={isModalOpen}
             onOk={handleOk}
             onCancel={handleCancel}

+ 4 - 1
dashboard/src/components/template/MdTpl.tsx

@@ -1,4 +1,4 @@
-import GrammarPop, { GrammarPopShell } from "../dict/GrammarPop";
+import { GrammarPopShell } from "../dict/GrammarPop";
 import Article from "./Article";
 import Exercise from "./Exercise";
 import Mermaid from "./Mermaid";
@@ -6,6 +6,7 @@ import Nissaya from "./Nissaya";
 import Note from "./Note";
 import ParaHandle from "./ParaHandle";
 import Quote from "./Quote";
+import QuoteLink from "./QuoteLink";
 import SentEdit from "./SentEdit";
 import SentRead from "./SentRead";
 import Term from "./Term";
@@ -48,6 +49,8 @@ const Widget = ({ tpl, props, children }: IWidgetMdTpl) => {
       return <Mermaid props={props ? props : ""} />;
     case "grammar-pop":
       return <GrammarPopShell props={props ? props : ""} />;
+    case "quote-link":
+      return <QuoteLink props={props ? props : ""} />;
     default:
       return <>未定义模版({tpl})</>;
   }

+ 53 - 0
dashboard/src/components/template/QuoteLink.tsx

@@ -0,0 +1,53 @@
+import { Button, Popover } from "antd";
+import { Typography } from "antd";
+import { SearchOutlined, CopyOutlined } from "@ant-design/icons";
+import { ProCard } from "@ant-design/pro-components";
+import { bookName as _bookName } from "../fts/book_name";
+import { ArticleCtl, TDisplayStyle } from "./Article";
+import { ArticleType } from "../article/Article";
+
+const { Text, Link } = Typography;
+
+interface IWidgetQuoteLinkCtl {
+  type: string;
+  bookName: string;
+  volume: string;
+  page: string;
+  style: TDisplayStyle;
+}
+const QuoteLinkCtl = ({
+  type,
+  bookName,
+  volume,
+  page,
+  style,
+}: IWidgetQuoteLinkCtl) => {
+  const abbr = _bookName.find((value) => value.term === bookName)?.abbr;
+  let textShow = `${abbr} ${volume}. ${page}`;
+
+  return (
+    <>
+      <ArticleCtl
+        title={textShow}
+        type={"page"}
+        id={`${type}_${bookName}_${volume}_${page}`}
+        style={style}
+      />
+    </>
+  );
+};
+
+interface IWidget {
+  props: string;
+}
+const Widget = ({ props }: IWidget) => {
+  const prop = JSON.parse(atob(props)) as IWidgetQuoteLinkCtl;
+  console.log(prop);
+  return (
+    <>
+      <QuoteLinkCtl {...prop} />
+    </>
+  );
+};
+
+export default Widget;

+ 1 - 1
dashboard/src/pages/library/article/show.tsx

@@ -340,7 +340,7 @@ const Widget = () => {
                 target?: string
               ) => {
                 console.log("article change", newType, article, target);
-
+                scrollToTop();
                 let url = `/article/${newType}/${article}?mode=${currMode}`;
                 searchParams.forEach((value, key) => {
                   console.log(value, key);

+ 12 - 0
openapi/public/assets/protocol/resources/sentence/index.yaml

@@ -37,6 +37,18 @@ get:
       description: 是否由服务器渲染content 默认false
       schema:
         type: boolean
+    - name: format
+      in: query
+      description: 服务器渲染content的格式。默认react
+      schema:
+        type: string
+        enum:
+          - react
+          - unity
+          - html
+          - text
+          - tex
+          - simple
     - name: limit
       in: query
       description: 每次提取记录数