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

Merge pull request #1857 from visuddhinanda/agile

add SuggestionPopover
visuddhinanda 2 лет назад
Родитель
Сommit
11ac2e0d4e

+ 28 - 0
dashboard/src/components/corpus/PrPull.tsx

@@ -0,0 +1,28 @@
+import { useEffect } from "react";
+import { get } from "../../request";
+import { ISuggestionResponse } from "../api/Suggestion";
+import store from "../../store";
+import { refresh } from "../../reducers/pr-load";
+
+interface IWidget {
+  uid?: string | null;
+}
+const PrPullWidget = ({ uid }: IWidget) => {
+  useEffect(() => {
+    if (!uid) {
+      return;
+    }
+    const url = `/v2/sentpr/${uid}`;
+    console.log("url", url);
+    get<ISuggestionResponse>(url)
+      .then((json) => {
+        if (json.ok) {
+          store.dispatch(refresh(json.data));
+        }
+      })
+      .catch((e) => console.error(e));
+  }, [uid]);
+  return <></>;
+};
+
+export default PrPullWidget;

+ 2 - 0
dashboard/src/components/notification/NotificationIcon.tsx

@@ -10,6 +10,7 @@ const NotificationIconWidget = () => {
   useEffect(() => {
     let timer = setInterval(() => {
       const url = `/v2/notification?view=to&status=unread&limit=1`;
+      console.info("url", url);
       get<INotificationListResponse>(url).then((json) => {
         if (json.ok) {
           setCount(json.data.count);
@@ -25,6 +26,7 @@ const NotificationIconWidget = () => {
                     icon:
                       process.env.REACT_APP_API_HOST +
                       "/assets/images/wikipali_logo.png",
+                    tag: json.data.rows[0].id,
                   });
                   notification.onclick = (event) => {
                     event.preventDefault(); // 阻止浏览器聚焦于 Notification 的标签页

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

@@ -67,7 +67,7 @@ const SentCellWidget = ({
     ) {
       setBgColor("#1890ff33");
     } else {
-      setBgColor("unset");
+      setBgColor(undefined);
     }
   }, [discussionMessage, initValue?.id]);
 

+ 12 - 1
dashboard/src/components/template/SentEdit/SentContent.tsx

@@ -8,6 +8,7 @@ import { GetUserSetting } from "../../auth/setting/default";
 import { mode as _mode } from "../../../reducers/article-mode";
 import { IWbw } from "../Wbw/WbwWord";
 import { ArticleMode } from "../../article/Article";
+import SuggestionFocus from "./SuggestionFocus";
 
 interface ILayoutFlex {
   left: number;
@@ -152,7 +153,17 @@ const SentContentWidget = ({
       </div>
       <div style={{ flex: layoutFlex.right }}>
         {translation?.map((item, id) => {
-          return <SentCell key={id} initValue={item} compact={compact} />;
+          return (
+            <SuggestionFocus
+              book={item.book}
+              para={item.para}
+              start={item.wordStart}
+              end={item.wordEnd}
+              channelId={item.channel.id}
+            >
+              <SentCell key={id} initValue={item} compact={compact} />
+            </SuggestionFocus>
+          );
         })}
       </div>
     </div>

+ 60 - 0
dashboard/src/components/template/SentEdit/SuggestionFocus.tsx

@@ -0,0 +1,60 @@
+import { useEffect, useRef, useState } from "react";
+import { useAppSelector } from "../../../hooks";
+import { prInfo } from "../../../reducers/pr-load";
+
+interface IWidget {
+  book: number;
+  para: number;
+  start: number;
+  end: number;
+  channelId: string;
+  children?: React.ReactNode;
+}
+const SuggestionFocusWidget = ({
+  book,
+  para,
+  start,
+  end,
+  channelId,
+  children,
+}: IWidget) => {
+  const pr = useAppSelector(prInfo);
+  const [highlight, setHighlight] = useState(false);
+  const divRef = useRef<HTMLDivElement>(null);
+
+  useEffect(() => {
+    if (pr) {
+      if (
+        book === pr.book &&
+        para === pr.paragraph &&
+        start === pr.word_start &&
+        end === pr.word_end &&
+        channelId === pr.channel.id
+      ) {
+        setHighlight(true);
+        divRef.current?.scrollIntoView({
+          behavior: "smooth",
+          block: "center",
+          inline: "nearest",
+        });
+      } else {
+        setHighlight(false);
+      }
+    } else {
+      setHighlight(false);
+    }
+  }, [book, channelId, end, para, pr, start]);
+  return (
+    <div
+      ref={divRef}
+      style={{
+        backgroundColor: highlight ? "rgb(255 255 0 / 20%)" : undefined,
+        width: "100%",
+      }}
+    >
+      {children}
+    </div>
+  );
+};
+
+export default SuggestionFocusWidget;

+ 77 - 0
dashboard/src/components/template/SentEdit/SuggestionPopover.tsx

@@ -0,0 +1,77 @@
+import { Popover } from "antd";
+import { useEffect, useState } from "react";
+import SentCell from "./SentCell";
+import { ISentence } from "../SentEdit";
+import { useAppSelector } from "../../../hooks";
+import { prInfo, refresh } from "../../../reducers/pr-load";
+import store from "../../../store";
+
+interface IWidget {
+  book: number;
+  para: number;
+  start: number;
+  end: number;
+  channelId: string;
+}
+const SuggestionPopoverWidget = ({
+  book,
+  para,
+  start,
+  end,
+  channelId,
+}: IWidget) => {
+  const [open, setOpen] = useState(false);
+  const [sentData, setSentData] = useState<ISentence>();
+  const pr = useAppSelector(prInfo);
+
+  useEffect(() => {
+    if (pr) {
+      if (
+        book === pr.book &&
+        para === pr.paragraph &&
+        start === pr.word_start &&
+        end === pr.word_end &&
+        channelId === pr.channel.id
+      ) {
+        setSentData({
+          id: pr.id,
+          content: pr.content,
+          html: pr.html,
+          book: pr.book,
+          para: pr.paragraph,
+          wordStart: pr.word_start,
+          wordEnd: pr.word_end,
+          editor: pr.editor,
+          channel: { name: pr.channel.name, id: pr.channel.id },
+          updateAt: pr.updated_at,
+        });
+        setOpen(true);
+      }
+    }
+  }, [book, channelId, end, para, pr, start]);
+
+  const handleOpenChange = (newOpen: boolean) => {
+    setOpen(newOpen);
+    if (newOpen === false) {
+      store.dispatch(refresh(null));
+    }
+  };
+  return (
+    <Popover
+      placement="bottomRight"
+      content={
+        <div>
+          <SentCell value={sentData} key={1} isPr={true} showDiff={false} />
+        </div>
+      }
+      title={`${sentData?.editor.nickName}`}
+      trigger="click"
+      open={open}
+      onOpenChange={handleOpenChange}
+    >
+      <span></span>
+    </Popover>
+  );
+};
+
+export default SuggestionPopoverWidget;

+ 8 - 0
dashboard/src/components/template/SentEdit/SuggestionToolbar.tsx

@@ -10,6 +10,7 @@ import { count, show } from "../../../reducers/discussion";
 import { useAppSelector } from "../../../hooks";
 import { openPanel } from "../../../reducers/right-panel";
 import { useIntl } from "react-intl";
+import SuggestionPopover from "./SuggestionPopover";
 
 const { Text, Paragraph } = Typography;
 
@@ -109,6 +110,13 @@ const SuggestionToolbarWidget = ({
             <Tooltip title="修改建议">
               <HandOutlinedIcon />
             </Tooltip>
+            <SuggestionPopover
+              book={data.book}
+              para={data.para}
+              start={data.wordStart}
+              end={data.wordEnd}
+              channelId={data.channel.id}
+            />
             {prNumber}
           </Space>
           {compact ? undefined : <Divider type="vertical" />}

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

@@ -50,6 +50,7 @@ import ToStudio from "../../../components/auth/ToStudio";
 import LoginAlertModal from "../../../components/auth/LoginAlertModal";
 import ShareButton from "../../../components/export/ShareButton";
 import ChannelAlert from "../../../components/channel/ChannelAlert";
+import PrPull from "../../../components/corpus/PrPull";
 
 export interface ISearchParams {
   key: string;
@@ -451,6 +452,7 @@ const Widget = () => {
           </div>
         </div>
       </div>
+      <PrPull uid={searchParams.get("pr")} />
     </div>
   );
 };

+ 27 - 0
dashboard/src/reducers/pr-load.ts

@@ -0,0 +1,27 @@
+import { createSlice, PayloadAction } from "@reduxjs/toolkit";
+
+import type { RootState } from "../store";
+import { ISuggestionData } from "../components/api/Suggestion";
+
+interface IState {
+  suggestion?: ISuggestionData | null;
+}
+
+const initialState: IState = {};
+
+export const slice = createSlice({
+  name: "pr-load",
+  initialState,
+  reducers: {
+    refresh: (state, action: PayloadAction<ISuggestionData | null>) => {
+      state.suggestion = action.payload;
+    },
+  },
+});
+
+export const { refresh } = slice.actions;
+
+export const prInfo = (state: RootState): ISuggestionData | null | undefined =>
+  state.prLoad.suggestion;
+
+export default slice.reducer;

+ 2 - 0
dashboard/src/store.ts

@@ -26,6 +26,7 @@ import discussionReducer from "./reducers/discussion";
 import wbwReducer from "./reducers/wbw";
 import termOrderReducer from "./reducers/term-order";
 import focusReducer from "./reducers/focus";
+import prLoadReducer from "./reducers/pr-load";
 
 const store = configureStore({
   reducer: {
@@ -55,6 +56,7 @@ const store = configureStore({
     wbw: wbwReducer,
     termOrder: termOrderReducer,
     focus: focusReducer,
+    prLoad: prLoadReducer,
   },
 });
 

+ 11 - 0
rpc/morus/morus/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head></head>
+  <body>
+    <form action="morus.php" method="post">
+      <div>markdown text:</div>
+      <textarea type="text" name="text">test **markdown** render</textarea>
+      <div><input type="submit" value="Submit" /></div>
+    </form>
+  </body>
+</html>

+ 21 - 0
rpc/morus/morus/morus.php

@@ -0,0 +1,21 @@
+<?php
+require dirname(__FILE__) . '/vendor/autoload.php';
+
+
+if(isset($_POST['text'])){
+    $input = $_POST['text'];
+}else{
+    $json = file_get_contents('php://input');
+    $data = json_decode($json,true);
+    $input = $data['text'];
+}
+
+$Parsedown = new Parsedown();
+
+header('Content-Type: application/json; charset=utf-8');
+echo json_encode([
+            'ok'=>true,
+            'data'=>$Parsedown->text($input),
+            'message'=>'',
+        ],
+     JSON_UNESCAPED_UNICODE) ;