visuddhinanda пре 1 месец
родитељ
комит
35cffe747f

+ 2 - 2
dashboard-v6/documents/development/v6-todo-list.md

@@ -28,7 +28,7 @@
 - [ ] `/dict/list`=>`resources/dict`
 - [ ] `/dict/list`=>`resources/dict`
 - [x] `/term/list`=>`workgroup/term`
 - [x] `/term/list`=>`workgroup/term`
 - [ ] `/article/list`=>`workgroup/article`
 - [ ] `/article/list`=>`workgroup/article`
-- [ ] `/anthology/list`=>`workgroup/anthology`
+- [x] `/anthology/list`=>`workgroup/anthology`
 - [ ] `/attachment/list`=>`resources/attachment`
 - [ ] `/attachment/list`=>`resources/attachment`
 - [ ] `/tags/list`=>`resources/tags`
 - [ ] `/tags/list`=>`resources/tags`
 
 
@@ -36,7 +36,7 @@
 
 
 ### 4️⃣ Collaboration 模块
 ### 4️⃣ Collaboration 模块
 
 
-- [ ] `/group/list`=>`collaboration/team-space`
+- [ ] `/group/list`=>`collaboration/team`
 - [ ] `/invite/list`=>`collaboration/invite`
 - [ ] `/invite/list`=>`collaboration/invite`
 - [ ] `/transfer/list`=>`collaboration/transfer`
 - [ ] `/transfer/list`=>`collaboration/transfer`
 
 

+ 6 - 2
dashboard-v6/src/Router.tsx

@@ -118,7 +118,7 @@ const router = createBrowserRouter(
                   Component: WorkspaceAnthologyList,
                   Component: WorkspaceAnthologyList,
                 },
                 },
                 {
                 {
-                  path: ":id",
+                  path: ":anthologyId",
                   loader: anthologyLoader,
                   loader: anthologyLoader,
                   handle: {
                   handle: {
                     crumb: (match: { data: { title: string } }) =>
                     crumb: (match: { data: { title: string } }) =>
@@ -133,6 +133,10 @@ const router = createBrowserRouter(
                       },
                       },
                       Component: WorkspaceAnthologyEdit,
                       Component: WorkspaceAnthologyEdit,
                     },
                     },
+                    {
+                      path: ":articleId",
+                      Component: WorkspaceArticleShow,
+                    },
                   ],
                   ],
                 },
                 },
               ],
               ],
@@ -149,7 +153,7 @@ const router = createBrowserRouter(
                   Component: WorkspaceArticleList,
                   Component: WorkspaceArticleList,
                 },
                 },
                 {
                 {
-                  path: ":id",
+                  path: ":articleId",
                   Component: WorkspaceArticleShow,
                   Component: WorkspaceArticleShow,
                   loader: articleLoader,
                   loader: articleLoader,
                   handle: {
                   handle: {

+ 6 - 6
dashboard-v6/src/api/Article.ts

@@ -575,32 +575,32 @@ export const fetchAnthology = (id: string): Promise<IAnthologyResponse> => {
 };
 };
 
 
 export async function anthologyLoader({ params }: LoaderFunctionArgs) {
 export async function anthologyLoader({ params }: LoaderFunctionArgs) {
-  const id = params.id;
+  const id = params.anthologyId;
 
 
   if (!id) {
   if (!id) {
-    throw new Response("Missing channelId", { status: 400 });
+    throw new Response("Missing anthologyId", { status: 400 });
   }
   }
 
 
   const res = await fetchAnthology(id);
   const res = await fetchAnthology(id);
 
 
   if (!res.ok) {
   if (!res.ok) {
-    throw new Response("Channel not found", { status: 404 });
+    throw new Response("anthology not found", { status: 404 });
   }
   }
 
 
   return res.data;
   return res.data;
 }
 }
 
 
 export async function articleLoader({ params }: LoaderFunctionArgs) {
 export async function articleLoader({ params }: LoaderFunctionArgs) {
-  const id = params.id;
+  const id = params.articleId;
 
 
   if (!id) {
   if (!id) {
-    throw new Response("Missing channelId", { status: 400 });
+    throw new Response("Missing articleId", { status: 400 });
   }
   }
 
 
   const res = await fetchArticle(id);
   const res = await fetchArticle(id);
 
 
   if (!res.ok) {
   if (!res.ok) {
-    throw new Response("Channel not found", { status: 404 });
+    throw new Response("article not found", { status: 404 });
   }
   }
 
 
   return res.data;
   return res.data;

+ 24 - 6
dashboard-v6/src/components/anthology/AnthologyDetail.tsx → dashboard-v6/src/components/anthology/AnthologyReader.tsx

@@ -1,22 +1,26 @@
-import { Divider, Typography } from "antd";
+import { Button, Divider, Space, Typography } from "antd";
+import { EditOutlined, ReloadOutlined } from "@ant-design/icons";
 
 
-import AnthologyTocTree from "../anthology/AnthologyTocTree";
+import AnthologyTocTree from "./AnthologyTocTree";
 import { useIntl } from "react-intl";
 import { useIntl } from "react-intl";
 import ArticleSkeleton from "../article/components/ArticleSkeleton";
 import ArticleSkeleton from "../article/components/ArticleSkeleton";
 import ErrorResult from "../general/ErrorResult";
 import ErrorResult from "../general/ErrorResult";
 import AnthologyInfo from "./components/AnthologyInfo";
 import AnthologyInfo from "./components/AnthologyInfo";
 import { useAnthology } from "./hooks/useAnthology";
 import { useAnthology } from "./hooks/useAnthology";
+import ArticleHeader from "../article/components/ArticleHeader";
+import type { TTarget } from "../../types";
 
 
 const { Title } = Typography;
 const { Title } = Typography;
 
 
 interface Props {
 interface Props {
   id?: string;
   id?: string;
   channels?: string[];
   channels?: string[];
-  onArticleClick?: (anthologyId: string, id: string, target: string) => void;
+  onArticleClick?: (anthologyId: string, id: string, target?: TTarget) => void;
+  onEdit?: () => void;
 }
 }
 
 
-const AnthologyDetailWidget = ({ id, channels, onArticleClick }: Props) => {
-  const { data, loading, errorCode } = useAnthology(id);
+const AnthologyReader = ({ id, channels, onArticleClick, onEdit }: Props) => {
+  const { data, loading, errorCode, refresh } = useAnthology(id);
   const intl = useIntl();
   const intl = useIntl();
 
 
   return (
   return (
@@ -25,6 +29,20 @@ const AnthologyDetailWidget = ({ id, channels, onArticleClick }: Props) => {
       {!loading && errorCode && <ErrorResult code={errorCode} />}
       {!loading && errorCode && <ErrorResult code={errorCode} />}
       {!loading && !errorCode && (
       {!loading && !errorCode && (
         <>
         <>
+          <ArticleHeader
+            action={
+              <Space>
+                <Button type="primary" icon={<EditOutlined />} onClick={onEdit}>
+                  edit
+                </Button>
+                <Button
+                  type="link"
+                  icon={<ReloadOutlined />}
+                  onClick={refresh}
+                />
+              </Space>
+            }
+          />
           <AnthologyInfo data={data} />
           <AnthologyInfo data={data} />
           <Divider />
           <Divider />
           <Title level={5}>
           <Title level={5}>
@@ -44,4 +62,4 @@ const AnthologyDetailWidget = ({ id, channels, onArticleClick }: Props) => {
   );
   );
 };
 };
 
 
-export default AnthologyDetailWidget;
+export default AnthologyReader;

+ 3 - 6
dashboard-v6/src/components/anthology/AnthologyTocTree.tsx

@@ -4,15 +4,12 @@ import { get } from "../../request";
 import type { IArticleMapListResponse } from "../../api/Article";
 import type { IArticleMapListResponse } from "../../api/Article";
 import type { ListNodeData } from "../article/components/EditableTree";
 import type { ListNodeData } from "../article/components/EditableTree";
 import TocTree from "../article/components/TocTree";
 import TocTree from "../article/components/TocTree";
+import type { TTarget } from "../../types";
 
 
 interface IWidget {
 interface IWidget {
   anthologyId?: string;
   anthologyId?: string;
   channels?: string[];
   channels?: string[];
-  onClick?: (
-    anthologyId: string,
-    id: string,
-    target: "_blank" | "self"
-  ) => void;
+  onClick?: (anthologyId: string, id: string, target?: TTarget) => void;
   onArticleSelect?: (anthologyId: string, keys: string[]) => void;
   onArticleSelect?: (anthologyId: string, keys: string[]) => void;
 }
 }
 const AnthologyTocTreeWidget = ({
 const AnthologyTocTreeWidget = ({
@@ -72,7 +69,7 @@ const AnthologyTocTreeWidget = ({
         id: string,
         id: string,
         e: React.MouseEvent<HTMLSpanElement, MouseEvent>
         e: React.MouseEvent<HTMLSpanElement, MouseEvent>
       ) => {
       ) => {
-        const target = e.ctrlKey || e.metaKey ? "_blank" : "self";
+        const target = e.ctrlKey || e.metaKey ? "_blank" : "_self";
         if (
         if (
           typeof onClick !== "undefined" &&
           typeof onClick !== "undefined" &&
           typeof anthologyId !== "undefined"
           typeof anthologyId !== "undefined"

+ 1 - 1
dashboard-v6/src/components/article/TypeAnthology.tsx

@@ -1,7 +1,7 @@
 import "./article.css";
 import "./article.css";
 
 
 import type { ArticleMode } from "../../api/Article";
 import type { ArticleMode } from "../../api/Article";
-import AnthologyDetail from "../anthology/AnthologyDetail";
+import AnthologyDetail from "../anthology/AnthologyReader";
 
 
 interface IWidget {
 interface IWidget {
   id?: string;
   id?: string;

+ 8 - 5
dashboard-v6/src/pages/workspace/anthology/show.tsx

@@ -1,9 +1,9 @@
 import { useNavigate, useParams, useSearchParams } from "react-router";
 import { useNavigate, useParams, useSearchParams } from "react-router";
 
 
-import AnthologyDetail from "../../../components/anthology/AnthologyDetail";
+import AnthologyReader from "../../../components/anthology/AnthologyReader";
 
 
 const Widget = () => {
 const Widget = () => {
-  const { id } = useParams(); //url 参数
+  const { anthologyId } = useParams(); //url 参数
   const navigate = useNavigate();
   const navigate = useNavigate();
   const [searchParams] = useSearchParams();
   const [searchParams] = useSearchParams();
 
 
@@ -13,12 +13,15 @@ const Widget = () => {
   return (
   return (
     <>
     <>
       <title>{"anthology-"}</title>
       <title>{"anthology-"}</title>
-      <AnthologyDetail
+      <AnthologyReader
         channels={channels}
         channels={channels}
-        id={id}
+        id={anthologyId}
         onArticleClick={(anthologyId, articleId, target) => {
         onArticleClick={(anthologyId, articleId, target) => {
           console.log("click", target);
           console.log("click", target);
-          navigate(`/workspace/article/${articleId}?anthology=${anthologyId}`);
+          navigate(`/workspace/anthology/${anthologyId}/${articleId}`);
+        }}
+        onEdit={() => {
+          navigate(`/workspace/anthology/${anthologyId}/edit`);
         }}
         }}
       />
       />
     </>
     </>

+ 39 - 11
dashboard-v6/src/pages/workspace/article/show.tsx

@@ -2,33 +2,61 @@ import { useNavigate, useParams, useSearchParams } from "react-router";
 import TypeArticle from "../../../components/article/TypeArticle";
 import TypeArticle from "../../../components/article/TypeArticle";
 import SplitLayout from "../../../components/general/SplitLayout";
 import SplitLayout from "../../../components/general/SplitLayout";
 import type { ArticleMode } from "../../../api/Article";
 import type { ArticleMode } from "../../../api/Article";
+import AnthologyTocTree from "../../../components/anthology/AnthologyTocTree";
 
 
 const Widget = () => {
 const Widget = () => {
-  const { id } = useParams();
-  const [searchParams, setSearchParams] = useSearchParams();
+  const { articleId, anthologyId } = useParams();
+  const [searchParams] = useSearchParams();
   const navigate = useNavigate();
   const navigate = useNavigate();
   const mode = searchParams.get("mode") ?? "read";
   const mode = searchParams.get("mode") ?? "read";
   const channelId = searchParams.get("channel");
   const channelId = searchParams.get("channel");
   const anthology = searchParams.get("anthology");
   const anthology = searchParams.get("anthology");
 
 
   return (
   return (
-    <SplitLayout key="mode-a" sidebarTitle="table of content" sidebar={<></>}>
+    <SplitLayout
+      key="mode-a"
+      sidebarTitle="table of content"
+      sidebar={
+        anthologyId ? (
+          <AnthologyTocTree
+            anthologyId={anthologyId}
+            channels={channelId ? channelId.split("_") : undefined}
+            onClick={(anthology, article, target) => {
+              if (target && target === "_blank") {
+                window.open(
+                  `${window.location.origin}${import.meta.env.BASE_URL}workspace/anthology/${anthology}/${article}`,
+                  "_blank"
+                );
+              } else {
+                navigate(`/workspace/anthology/${anthology}/${article}`);
+              }
+            }}
+          />
+        ) : (
+          <></>
+        )
+      }
+    >
       {({ expandButton }) => (
       {({ expandButton }) => (
         <TypeArticle
         <TypeArticle
-          articleId={id}
+          articleId={articleId}
           mode={mode as ArticleMode}
           mode={mode as ArticleMode}
-          anthologyId={anthology}
+          anthologyId={anthologyId ?? anthology}
           channelId={channelId}
           channelId={channelId}
           headerExtra={expandButton}
           headerExtra={expandButton}
           onAnthologySelect={(id) => {
           onAnthologySelect={(id) => {
-            setSearchParams((prev) => {
-              const next = new URLSearchParams(prev);
-              next.set("anthology", id);
-              return next;
-            });
+            navigate(`/workspace/anthology/${id}/${articleId}`);
           }}
           }}
           onArticleChange={(type, id) => {
           onArticleChange={(type, id) => {
-            navigate(`/workspace/${type}/${id}`);
+            if (anthologyId) {
+              if (type === "article") {
+                navigate(`/workspace/anthology/${anthologyId}/${id}`);
+              } else {
+                navigate(`/workspace/${type}/${id}`);
+              }
+            } else {
+              navigate(`/workspace/${type}/${id}`);
+            }
           }}
           }}
         />
         />
       )}
       )}