Bläddra i källkod

Merge pull request #1760 from visuddhinanda/agile

添加段落focus功能
visuddhinanda 2 år sedan
förälder
incheckning
3904ecd0e2

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

@@ -50,6 +50,7 @@ interface IWidget {
   exerciseId?: string;
   userName?: string;
   active?: boolean;
+  focus?: string | null;
   onArticleChange?: Function;
   onFinal?: Function;
   onLoad?: Function;
@@ -67,6 +68,7 @@ const ArticleWidget = ({
   userName,
   mode = "read",
   active = false,
+  focus,
   onArticleChange,
   onFinal,
   onLoad,
@@ -128,6 +130,7 @@ const ArticleWidget = ({
           mode={mode}
           book={book}
           para={para}
+          focus={focus}
           onArticleChange={(type: ArticleType, id: string) => {
             if (typeof onArticleChange !== "undefined") {
               onArticleChange(type, id);
@@ -138,6 +141,7 @@ const ArticleWidget = ({
         <TypePage
           articleId={articleId}
           channelId={channelId}
+          focus={focus}
           mode={mode}
           onArticleChange={(type: ArticleType, id: string) => {
             if (typeof onArticleChange !== "undefined") {

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

@@ -22,6 +22,7 @@ interface IWidget {
   articleId?: string;
   mode?: ArticleMode | null;
   channelId?: string | null;
+  focus?: string | null;
   onArticleChange?: Function;
   onFinal?: Function;
   onLoad?: Function;
@@ -29,6 +30,7 @@ interface IWidget {
 const TypeTermWidget = ({
   channelId,
   articleId,
+  focus,
   mode = "read",
   onArticleChange,
 }: IWidget) => {
@@ -101,6 +103,7 @@ const TypeTermWidget = ({
           <TypePali
             type={"para"}
             {...paramPali}
+            focus={focus}
             onArticleChange={(type: ArticleType, id: string) => {
               if (typeof onArticleChange !== "undefined") {
                 onArticleChange(type, id);

+ 9 - 0
dashboard/src/components/article/TypePali.tsx

@@ -12,6 +12,8 @@ import { ArticleMode, ArticleType } from "./Article";
 import "./article.css";
 import ArticleSkeleton from "./ArticleSkeleton";
 import ErrorResult from "../general/ErrorResult";
+import store from "../../store";
+import { refresh } from "../../reducers/focus";
 
 interface IWidget {
   type?: ArticleType;
@@ -21,6 +23,7 @@ interface IWidget {
   book?: string | null;
   para?: string | null;
   active?: boolean;
+  focus?: string | null;
   onArticleChange?: Function;
   onFinal?: Function;
   onLoad?: Function;
@@ -33,6 +36,7 @@ const TypePaliWidget = ({
   articleId,
   mode = "read",
   active = true,
+  focus,
   onArticleChange,
   onFinal,
   onLoad,
@@ -48,6 +52,11 @@ const TypePaliWidget = ({
   const channels = channelId?.split("_");
 
   const srcDataMode = mode === "edit" || mode === "wbw" ? "edit" : "read";
+
+  useEffect(() => {
+    store.dispatch(refresh({ type: "para", id: focus }));
+  }, [focus]);
+
   useEffect(() => {
     console.log("srcDataMode", srcDataMode);
     if (!active) {

+ 3 - 0
dashboard/src/components/template/Article.tsx

@@ -13,6 +13,7 @@ interface IWidgetChapterCtl {
   id?: string;
   channel?: string;
   title?: string;
+  focus?: string | null;
   style?: TDisplayStyle;
 }
 
@@ -21,6 +22,7 @@ export const ArticleCtl = ({
   id,
   channel,
   title,
+  focus,
   style = "modal",
 }: IWidgetChapterCtl) => {
   const [isModalOpen, setIsModalOpen] = useState(false);
@@ -42,6 +44,7 @@ export const ArticleCtl = ({
       type={type}
       articleId={id}
       channelId={channel}
+      focus={focus}
       mode="read"
     />
   );

+ 3 - 0
dashboard/src/components/template/MdTpl.tsx

@@ -5,6 +5,7 @@ import Mermaid from "./Mermaid";
 import Nissaya from "./Nissaya";
 import Note from "./Note";
 import ParaHandle from "./ParaHandle";
+import ParaShell from "./ParaShell";
 import Quote from "./Quote";
 import QuoteLink from "./QuoteLink";
 import SentEdit from "./SentEdit";
@@ -51,6 +52,8 @@ const Widget = ({ tpl, props, children }: IWidgetMdTpl) => {
       return <GrammarPopShell props={props ? props : ""} />;
     case "quote-link":
       return <QuoteLink props={props ? props : ""} />;
+    case "para-shell":
+      return <ParaShell props={props ? props : ""}>{children}</ParaShell>;
     default:
       return <>未定义模版({tpl})</>;
   }

+ 7 - 5
dashboard/src/components/template/ParaHandle.tsx

@@ -1,4 +1,4 @@
-import { Button, Divider, Dropdown, MenuProps, message } from "antd";
+import { Button, Dropdown, MenuProps, message } from "antd";
 import { useNavigate, useSearchParams } from "react-router-dom";
 import { fullUrl } from "../../utils";
 import { useIntl } from "react-intl";
@@ -10,7 +10,7 @@ interface IWidgetParaHandleCtl {
   channels?: string[];
   sentences: string[];
 }
-const ParaHandleCtl = ({
+export const ParaHandleCtl = ({
   book,
   para,
   mode = "read",
@@ -111,15 +111,17 @@ const ParaHandleCtl = ({
     }
   };
   return (
-    <Divider orientation="left">
+    <div>
       <Dropdown
         menu={{ items, onClick }}
         placement="bottomLeft"
         trigger={["click"]}
       >
-        <Button type="text">{para}</Button>
+        <Button size="small" type="text">
+          {para}
+        </Button>
       </Dropdown>
-    </Divider>
+    </div>
   );
 };
 

+ 89 - 0
dashboard/src/components/template/ParaShell.tsx

@@ -0,0 +1,89 @@
+import { useEffect, useState } from "react";
+import { useAppSelector } from "../../hooks";
+import { currFocus } from "../../reducers/focus";
+import { ParaHandleCtl } from "./ParaHandle";
+
+interface IWidgetParaShellCtl {
+  book: number;
+  para: number;
+  mode?: string;
+  channels?: string[];
+  sentences: string[];
+  children?: React.ReactNode | React.ReactNode[];
+}
+const ParaShellCtl = ({
+  book,
+  para,
+  mode = "read",
+  channels,
+  sentences,
+  children,
+}: IWidgetParaShellCtl) => {
+  const focus = useAppSelector(currFocus);
+  const [isFocus, setIsFocus] = useState(false);
+  useEffect(() => {
+    if (focus) {
+      if (focus.focus?.type === "para") {
+        if (focus.focus.id) {
+          const arrId = focus.focus.id.split("-");
+          if (arrId.length > 1) {
+            const focusBook = parseInt(arrId[0]);
+            const focusPara = arrId[1].split(",").map((item) => parseInt(item));
+            if (focusBook === book && focusPara.includes(para)) {
+              setIsFocus(true);
+            }
+          }
+        } else {
+          setIsFocus(false);
+        }
+      }
+    } else {
+      setIsFocus(false);
+    }
+  }, [book, focus, para]);
+  return (
+    <div
+      style={{
+        borderTop: isFocus ? undefined : "1px solid #80808080",
+        border: isFocus ? "1px solid #006bff" : undefined,
+        borderRadius: isFocus ? 6 : undefined,
+        marginTop: 20,
+        paddingTop: 16,
+      }}
+    >
+      <div
+        style={{
+          position: "absolute",
+          marginTop: "-2em",
+          marginLeft: "1em",
+          backgroundColor: "#d9e9ff",
+          borderRadius: "4px",
+        }}
+      >
+        <ParaHandleCtl
+          book={book}
+          para={para}
+          mode={mode}
+          channels={channels}
+          sentences={sentences}
+        />
+      </div>
+      {children}
+    </div>
+  );
+};
+
+interface IWidget {
+  props: string;
+  children?: React.ReactNode | React.ReactNode[];
+}
+const Widget = ({ props, children }: IWidget) => {
+  const prop = JSON.parse(atob(props)) as IWidgetParaShellCtl;
+  return (
+    <>
+      <ParaShellCtl {...prop}>{children}</ParaShellCtl>
+    </>
+  );
+};
+
+export default Widget;

+ 3 - 2
dashboard/src/components/template/QuoteLink.tsx

@@ -8,8 +8,8 @@ interface IWidgetQuoteLinkCtl {
   volume: string;
   page: string;
   style: TDisplayStyle;
-  book: number;
-  para: number;
+  book?: number;
+  para?: number;
 }
 const QuoteLinkCtl = ({
   type,
@@ -31,6 +31,7 @@ const QuoteLinkCtl = ({
       <ArticleCtl
         title={textShow}
         type={"page"}
+        focus={book && para ? `${book}-${para}` : undefined}
         id={`${type}_${bookName}_${volume}_${page}`}
         style={style}
       />

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

@@ -48,7 +48,6 @@ import { TResType } from "../../../components/discussion/DiscussionListCard";
 import { modeChange } from "../../../reducers/article-mode";
 import SearchButton from "../../../components/general/SearchButton";
 import ToStudio from "../../../components/auth/ToStudio";
-import { currentUser as _currentUser } from "../../../reducers/current-user";
 import LoginAlertModal from "../../../components/auth/LoginAlertModal";
 import ShareButton from "../../../components/export/ShareButton";
 
@@ -76,7 +75,6 @@ const Widget = () => {
     useState<IArticleDataResponse>();
 
   const paraChange = useAppSelector(paraParam);
-  const user = useAppSelector(_currentUser);
 
   useEffect(() => {
     if (typeof paraChange === "undefined") {
@@ -331,6 +329,7 @@ const Widget = () => {
               book={searchParams.get("book")}
               para={searchParams.get("par")}
               channelId={searchParams.get("channel")}
+              focus={searchParams.get("focus")}
               articleId={id}
               anthologyId={searchParams.get("anthology")}
               mode={searchParams.get("mode") as ArticleMode}

+ 30 - 0
dashboard/src/reducers/focus.ts

@@ -0,0 +1,30 @@
+import { createSlice, PayloadAction } from "@reduxjs/toolkit";
+
+import type { RootState } from "../store";
+
+export interface IFocus {
+  type: string;
+  id?: string | null;
+}
+
+interface IState {
+  focus?: IFocus;
+}
+
+const initialState: IState = {};
+
+export const slice = createSlice({
+  name: "focus",
+  initialState,
+  reducers: {
+    refresh: (state, action: PayloadAction<IFocus>) => {
+      state.focus = action.payload;
+    },
+  },
+});
+
+export const { refresh } = slice.actions;
+
+export const currFocus = (state: RootState): IState => state.focus;
+
+export default slice.reducer;

+ 2 - 0
dashboard/src/store.ts

@@ -25,6 +25,7 @@ import netStatusReducer from "./reducers/net-status";
 import discussionReducer from "./reducers/discussion";
 import wbwReducer from "./reducers/wbw";
 import termOrderReducer from "./reducers/term-order";
+import focusReducer from "./reducers/focus";
 
 const store = configureStore({
   reducer: {
@@ -53,6 +54,7 @@ const store = configureStore({
     discussion: discussionReducer,
     wbw: wbwReducer,
     termOrder: termOrderReducer,
+    focus: focusReducer,
   },
 });