Commit 5673e29e authored by Steven's avatar Steven

chore: compose memo in backend

parent feefaabc
...@@ -429,6 +429,16 @@ func (s *APIV2Service) convertMemoFromStore(ctx context.Context, memo *store.Mem ...@@ -429,6 +429,16 @@ func (s *APIV2Service) convertMemoFromStore(ctx context.Context, memo *store.Mem
return nil, errors.Wrap(err, "failed to get creator") return nil, errors.Wrap(err, "failed to get creator")
} }
listMemoRelationsResponse, err := s.ListMemoRelations(ctx, &apiv2pb.ListMemoRelationsRequest{Id: memo.ID})
if err != nil {
return nil, errors.Wrap(err, "failed to list memo relations")
}
listMemoResourcesResponse, err := s.ListMemoResources(ctx, &apiv2pb.ListMemoResourcesRequest{Id: memo.ID})
if err != nil {
return nil, errors.Wrap(err, "failed to list memo resources")
}
return &apiv2pb.Memo{ return &apiv2pb.Memo{
Id: int32(memo.ID), Id: int32(memo.ID),
RowStatus: convertRowStatusFromStore(memo.RowStatus), RowStatus: convertRowStatusFromStore(memo.RowStatus),
...@@ -441,6 +451,8 @@ func (s *APIV2Service) convertMemoFromStore(ctx context.Context, memo *store.Mem ...@@ -441,6 +451,8 @@ func (s *APIV2Service) convertMemoFromStore(ctx context.Context, memo *store.Mem
Nodes: convertFromASTNodes(rawNodes), Nodes: convertFromASTNodes(rawNodes),
Visibility: convertVisibilityFromStore(memo.Visibility), Visibility: convertVisibilityFromStore(memo.Visibility),
Pinned: memo.Pinned, Pinned: memo.Pinned,
Relations: listMemoRelationsResponse.Relations,
Resources: listMemoResourcesResponse.Resources,
}, nil }, nil
} }
......
...@@ -8,6 +8,7 @@ import "api/v2/memo_relation_service.proto"; ...@@ -8,6 +8,7 @@ import "api/v2/memo_relation_service.proto";
import "api/v2/resource_service.proto"; import "api/v2/resource_service.proto";
import "google/api/annotations.proto"; import "google/api/annotations.proto";
import "google/api/client.proto"; import "google/api/client.proto";
import "google/api/field_behavior.proto";
import "google/protobuf/field_mask.proto"; import "google/protobuf/field_mask.proto";
import "google/protobuf/timestamp.proto"; import "google/protobuf/timestamp.proto";
...@@ -115,6 +116,10 @@ message Memo { ...@@ -115,6 +116,10 @@ message Memo {
Visibility visibility = 10; Visibility visibility = 10;
bool pinned = 11; bool pinned = 11;
repeated Resource resources = 12 [(google.api.field_behavior) = OUTPUT_ONLY];
repeated MemoRelation relations = 13 [(google.api.field_behavior) = OUTPUT_ONLY];
} }
message CreateMemoRequest { message CreateMemoRequest {
......
...@@ -1803,6 +1803,8 @@ ...@@ -1803,6 +1803,8 @@
| nodes | [Node](#memos-api-v2-Node) | repeated | | | nodes | [Node](#memos-api-v2-Node) | repeated | |
| visibility | [Visibility](#memos-api-v2-Visibility) | | | | visibility | [Visibility](#memos-api-v2-Visibility) | | |
| pinned | [bool](#bool) | | | | pinned | [bool](#bool) | | |
| resources | [Resource](#memos-api-v2-Resource) | repeated | |
| relations | [MemoRelation](#memos-api-v2-MemoRelation) | repeated | |
......
This diff is collapsed.
...@@ -10,9 +10,8 @@ import useNavigateTo from "@/hooks/useNavigateTo"; ...@@ -10,9 +10,8 @@ import useNavigateTo from "@/hooks/useNavigateTo";
import { useFilterStore } from "@/store/module"; import { useFilterStore } from "@/store/module";
import { useUserV1Store, extractUsernameFromName, useMemoV1Store } from "@/store/v1"; import { useUserV1Store, extractUsernameFromName, useMemoV1Store } from "@/store/v1";
import { RowStatus } from "@/types/proto/api/v2/common"; import { RowStatus } from "@/types/proto/api/v2/common";
import { MemoRelation, MemoRelation_Type } from "@/types/proto/api/v2/memo_relation_service"; import { MemoRelation_Type } from "@/types/proto/api/v2/memo_relation_service";
import { Memo, Visibility } from "@/types/proto/api/v2/memo_service"; import { Memo, Visibility } from "@/types/proto/api/v2/memo_service";
import { Resource } from "@/types/proto/api/v2/resource_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { convertVisibilityToString } from "@/utils/memo"; import { convertVisibilityToString } from "@/utils/memo";
import showChangeMemoCreatedTsDialog from "./ChangeMemoCreatedTsDialog"; import showChangeMemoCreatedTsDialog from "./ChangeMemoCreatedTsDialog";
...@@ -50,10 +49,8 @@ const MemoView: React.FC<Props> = (props: Props) => { ...@@ -50,10 +49,8 @@ const MemoView: React.FC<Props> = (props: Props) => {
const [displayTime, setDisplayTime] = useState<string>(getRelativeTimeString(getTimeStampByDate(memo.displayTime))); const [displayTime, setDisplayTime] = useState<string>(getRelativeTimeString(getTimeStampByDate(memo.displayTime)));
const [creator, setCreator] = useState(userV1Store.getUserByUsername(extractUsernameFromName(memo.creator))); const [creator, setCreator] = useState(userV1Store.getUserByUsername(extractUsernameFromName(memo.creator)));
const [parentMemo, setParentMemo] = useState<Memo | undefined>(undefined); const [parentMemo, setParentMemo] = useState<Memo | undefined>(undefined);
const [resources, setResources] = useState<Resource[]>([]);
const [memoRelations, setMemoRelations] = useState<MemoRelation[]>([]);
const memoContainerRef = useRef<HTMLDivElement>(null); const memoContainerRef = useRef<HTMLDivElement>(null);
const referenceRelations = memoRelations.filter((relation) => relation.type === MemoRelation_Type.REFERENCE); const referenceRelations = memo.relations.filter((relation) => relation.type === MemoRelation_Type.REFERENCE);
const readonly = memo.creator !== user?.name; const readonly = memo.creator !== user?.name;
// Prepare memo creator. // Prepare memo creator.
...@@ -101,20 +98,14 @@ const MemoView: React.FC<Props> = (props: Props) => { ...@@ -101,20 +98,14 @@ const MemoView: React.FC<Props> = (props: Props) => {
return; return;
} }
memoStore.fetchMemoResources(memo.id).then((resources: Resource[]) => { const parentMemoId = memo.relations.find(
setResources(resources); (relation) => relation.memoId === memo.id && relation.type === MemoRelation_Type.COMMENT
}); )?.relatedMemoId;
memoStore.fetchMemoRelations(memo.id).then((relations: MemoRelation[]) => { if (parentMemoId) {
setMemoRelations(relations); memoStore.getOrFetchMemoById(parentMemoId).then((memo: Memo) => {
const parentMemoId = relations.find( setParentMemo(memo);
(relation) => relation.memoId === memo.id && relation.type === MemoRelation_Type.COMMENT });
)?.relatedMemoId; }
if (parentMemoId) {
memoStore.getOrFetchMemoById(parentMemoId).then((memo: Memo) => {
setParentMemo(memo);
});
}
});
}, [shouldRender]); }, [shouldRender]);
if (!shouldRender) { if (!shouldRender) {
...@@ -326,7 +317,7 @@ const MemoView: React.FC<Props> = (props: Props) => { ...@@ -326,7 +317,7 @@ const MemoView: React.FC<Props> = (props: Props) => {
</div> </div>
)} )}
<MemoContent content={memo.content} nodes={memo.nodes} onMemoContentClick={handleMemoContentClick} /> <MemoContent content={memo.content} nodes={memo.nodes} onMemoContentClick={handleMemoContentClick} />
<MemoResourceListView resourceList={resources} /> <MemoResourceListView resourceList={memo.resources} />
<MemoRelationListView memo={memo} relationList={referenceRelations} /> <MemoRelationListView memo={memo} relationList={referenceRelations} />
</div> </div>
); );
......
import { Button } from "@mui/joy"; import { Button } from "@mui/joy";
import copy from "copy-to-clipboard"; import copy from "copy-to-clipboard";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { getDateTimeString, getTimeString } from "@/helpers/datetime"; import { getDateTimeString, getTimeString } from "@/helpers/datetime";
import useLoading from "@/hooks/useLoading"; import useLoading from "@/hooks/useLoading";
import toImage from "@/labs/html2image"; import toImage from "@/labs/html2image";
import { useUserV1Store, extractUsernameFromName, useMemoV1Store } from "@/store/v1"; import { useUserV1Store, extractUsernameFromName } from "@/store/v1";
import { Memo } from "@/types/proto/api/v2/memo_service"; import { Memo } from "@/types/proto/api/v2/memo_service";
import { Resource } from "@/types/proto/api/v2/resource_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { generateDialog } from "./Dialog"; import { generateDialog } from "./Dialog";
import Icon from "./Icon"; import Icon from "./Icon";
...@@ -24,16 +23,13 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => { ...@@ -24,16 +23,13 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
const { memo, destroy } = props; const { memo, destroy } = props;
const t = useTranslate(); const t = useTranslate();
const userV1Store = useUserV1Store(); const userV1Store = useUserV1Store();
const memoStore = useMemoV1Store();
const downloadingImageState = useLoading(false); const downloadingImageState = useLoading(false);
const loadingState = useLoading(); const loadingState = useLoading();
const memoElRef = useRef<HTMLDivElement>(null); const memoElRef = useRef<HTMLDivElement>(null);
const [resources, setResources] = useState<Resource[]>([]);
const user = userV1Store.getUserByUsername(extractUsernameFromName(memo.creator)); const user = userV1Store.getUserByUsername(extractUsernameFromName(memo.creator));
useEffect(() => { useEffect(() => {
(async () => { (async () => {
setResources(await memoStore.fetchMemoResources(memo.id));
await userV1Store.getOrFetchUserByUsername(extractUsernameFromName(memo.creator)); await userV1Store.getOrFetchUserByUsername(extractUsernameFromName(memo.creator));
loadingState.setFinish(); loadingState.setFinish();
})(); })();
...@@ -105,7 +101,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => { ...@@ -105,7 +101,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
<span className="w-full px-6 pt-5 pb-2 text-sm text-gray-500">{getTimeString(memo.displayTime)}</span> <span className="w-full px-6 pt-5 pb-2 text-sm text-gray-500">{getTimeString(memo.displayTime)}</span>
<div className="w-full px-6 text-base pb-4"> <div className="w-full px-6 text-base pb-4">
<MemoContent content={memo.content} /> <MemoContent content={memo.content} />
<MemoResourceListView resourceList={resources} /> <MemoResourceListView resourceList={memo.resources} />
</div> </div>
<div className="flex flex-row justify-between items-center w-full bg-gray-100 dark:bg-zinc-700 py-4 px-6"> <div className="flex flex-row justify-between items-center w-full bg-gray-100 dark:bg-zinc-700 py-4 px-6">
<div className="flex flex-row justify-start items-center"> <div className="flex flex-row justify-start items-center">
......
import { useEffect, useState } from "react";
import Icon from "@/components/Icon"; import Icon from "@/components/Icon";
import MemoContent from "@/components/MemoContent"; import MemoContent from "@/components/MemoContent";
import MemoResourceListView from "@/components/MemoResourceListView"; import MemoResourceListView from "@/components/MemoResourceListView";
import { getTimeString } from "@/helpers/datetime"; import { getTimeString } from "@/helpers/datetime";
import { useMemoV1Store } from "@/store/v1"; import { MemoRelation_Type } from "@/types/proto/api/v2/memo_relation_service";
import { MemoRelation, MemoRelation_Type } from "@/types/proto/api/v2/memo_relation_service";
import { Memo } from "@/types/proto/api/v2/memo_service"; import { Memo } from "@/types/proto/api/v2/memo_service";
import { Resource } from "@/types/proto/api/v2/resource_service";
import MemoRelationListView from "./MemoRelationListView"; import MemoRelationListView from "./MemoRelationListView";
interface Props { interface Props {
...@@ -15,18 +12,7 @@ interface Props { ...@@ -15,18 +12,7 @@ interface Props {
const TimelineMemo = (props: Props) => { const TimelineMemo = (props: Props) => {
const { memo } = props; const { memo } = props;
const memoStore = useMemoV1Store(); const relations = memo.relations.filter((relation) => relation.type === MemoRelation_Type.REFERENCE);
const [resources, setResources] = useState<Resource[]>([]);
const [relations, setRelations] = useState<MemoRelation[]>([]);
useEffect(() => {
memoStore.fetchMemoResources(memo.id).then((resources: Resource[]) => {
setResources(resources);
});
memoStore.fetchMemoRelations(memo.id).then((relations: MemoRelation[]) => {
setRelations(relations.filter((relation) => relation.type === MemoRelation_Type.REFERENCE));
});
}, [memo.id]);
return ( return (
<div className="relative w-full flex flex-col justify-start items-start"> <div className="relative w-full flex flex-col justify-start items-start">
...@@ -36,7 +22,7 @@ const TimelineMemo = (props: Props) => { ...@@ -36,7 +22,7 @@ const TimelineMemo = (props: Props) => {
<span className="opacity-60">#{memo.id}</span> <span className="opacity-60">#{memo.id}</span>
</div> </div>
<MemoContent content={memo.content} nodes={memo.nodes} /> <MemoContent content={memo.content} nodes={memo.nodes} />
<MemoResourceListView resourceList={resources} /> <MemoResourceListView resourceList={memo.resources} />
<MemoRelationListView memo={memo} relationList={relations} /> <MemoRelationListView memo={memo} relationList={relations} />
</div> </div>
); );
......
...@@ -19,9 +19,8 @@ import { getDateTimeString } from "@/helpers/datetime"; ...@@ -19,9 +19,8 @@ import { getDateTimeString } from "@/helpers/datetime";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import useNavigateTo from "@/hooks/useNavigateTo"; import useNavigateTo from "@/hooks/useNavigateTo";
import { useUserV1Store, useMemoV1Store, extractUsernameFromName } from "@/store/v1"; import { useUserV1Store, useMemoV1Store, extractUsernameFromName } from "@/store/v1";
import { MemoRelation, MemoRelation_Type } from "@/types/proto/api/v2/memo_relation_service"; import { MemoRelation_Type } from "@/types/proto/api/v2/memo_relation_service";
import { Memo, Visibility } from "@/types/proto/api/v2/memo_service"; import { Memo, Visibility } from "@/types/proto/api/v2/memo_service";
import { Resource } from "@/types/proto/api/v2/resource_service";
import { User } from "@/types/proto/api/v2/user_service"; import { User } from "@/types/proto/api/v2/user_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { convertVisibilityToString } from "@/utils/memo"; import { convertVisibilityToString } from "@/utils/memo";
...@@ -38,12 +37,9 @@ const MemoDetail = () => { ...@@ -38,12 +37,9 @@ const MemoDetail = () => {
const memo = memoStore.getMemoById(memoId); const memo = memoStore.getMemoById(memoId);
const allowEdit = memo?.creatorId === currentUser.id; const allowEdit = memo?.creatorId === currentUser.id;
const [parentMemo, setParentMemo] = useState<Memo | undefined>(undefined); const [parentMemo, setParentMemo] = useState<Memo | undefined>(undefined);
const [resources, setResources] = useState<Resource[]>([]); const referenceRelations = memo?.relations.filter((relation) => relation.type === MemoRelation_Type.REFERENCE) || [];
const [memoRelations, setMemoRelations] = useState<MemoRelation[]>([]); const commentRelations =
const referenceRelations = memoRelations.filter((relation) => relation.type === MemoRelation_Type.REFERENCE); memo?.relations.filter((relation) => relation.relatedMemoId === memo?.id && relation.type === MemoRelation_Type.COMMENT) || [];
const commentRelations = memoRelations.filter(
(relation) => relation.relatedMemoId === memo?.id && relation.type === MemoRelation_Type.COMMENT
);
const comments = commentRelations.map((relation) => memoStore.getMemoById(relation.memoId)).filter((memo) => memo) as any as Memo[]; const comments = commentRelations.map((relation) => memoStore.getMemoById(relation.memoId)).filter((memo) => memo) as any as Memo[];
// Prepare memo. // Prepare memo.
...@@ -71,13 +67,7 @@ const MemoDetail = () => { ...@@ -71,13 +67,7 @@ const MemoDetail = () => {
} }
(async () => { (async () => {
const resources = await memoStore.fetchMemoResources(memo.id); const parentMemoId = memo.relations.find(
setResources(resources);
const memoRelations = await memoStore.fetchMemoRelations(memo.id);
const commentRelations = memoRelations.filter(
(relation) => relation.relatedMemoId === memo.id && relation.type === MemoRelation_Type.COMMENT
);
const parentMemoId = memoRelations.find(
(relation) => relation.memoId === memo.id && relation.type === MemoRelation_Type.COMMENT (relation) => relation.memoId === memo.id && relation.type === MemoRelation_Type.COMMENT
)?.relatedMemoId; )?.relatedMemoId;
if (parentMemoId) { if (parentMemoId) {
...@@ -85,9 +75,7 @@ const MemoDetail = () => { ...@@ -85,9 +75,7 @@ const MemoDetail = () => {
setParentMemo(memo); setParentMemo(memo);
}); });
} }
const requests = commentRelations.map((relation) => memoStore.getOrFetchMemoById(relation.memoId)); await Promise.all(commentRelations.map((relation) => memoStore.getOrFetchMemoById(relation.memoId)));
await Promise.all(requests);
setMemoRelations(memoRelations);
})(); })();
}, [memo]); }, [memo]);
...@@ -118,7 +106,7 @@ const MemoDetail = () => { ...@@ -118,7 +106,7 @@ const MemoDetail = () => {
const handleCommentCreated = async (commentId: number) => { const handleCommentCreated = async (commentId: number) => {
await memoStore.getOrFetchMemoById(commentId); await memoStore.getOrFetchMemoById(commentId);
setMemoRelations(await memoStore.fetchMemoRelations(memo.id)); await memoStore.getOrFetchMemoById(memo.id, true /* skip cache */);
}; };
return ( return (
...@@ -142,7 +130,7 @@ const MemoDetail = () => { ...@@ -142,7 +130,7 @@ const MemoDetail = () => {
<span className="text-gray-400 select-none">{getDateTimeString(memo.displayTime)}</span> <span className="text-gray-400 select-none">{getDateTimeString(memo.displayTime)}</span>
</div> </div>
<MemoContent content={memo.content} /> <MemoContent content={memo.content} />
<MemoResourceListView resourceList={resources} /> <MemoResourceListView resourceList={memo.resources} />
<MemoRelationListView memo={memo} relationList={referenceRelations} /> <MemoRelationListView memo={memo} relationList={referenceRelations} />
<div className="w-full mt-4 flex flex-col sm:flex-row justify-start sm:justify-between sm:items-center gap-2"> <div className="w-full mt-4 flex flex-col sm:flex-row justify-start sm:justify-between sm:items-center gap-2">
<div className="flex flex-row justify-start items-center"> <div className="flex flex-row justify-start items-center">
......
...@@ -22,9 +22,9 @@ export const useMemoV1Store = create( ...@@ -22,9 +22,9 @@ export const useMemoV1Store = create(
}); });
return memos; return memos;
}, },
getOrFetchMemoById: async (id: number) => { getOrFetchMemoById: async (id: number, skipCache = false) => {
const memo = get().memoById.get(id); const memo = get().memoById.get(id);
if (memo) { if (memo && !skipCache) {
return memo; return memo;
} }
...@@ -82,18 +82,6 @@ export const useMemoV1Store = create( ...@@ -82,18 +82,6 @@ export const useMemoV1Store = create(
return cloneDeep(state); return cloneDeep(state);
}); });
}, },
fetchMemoResources: async (id: number) => {
const { resources } = await memoServiceClient.listMemoResources({
id,
});
return resources;
},
fetchMemoRelations: async (id: number) => {
const { relations } = await memoServiceClient.listMemoRelations({
id,
});
return relations;
},
})) }))
); );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment