mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
chore: add memo relation list
This commit is contained in:
@ -44,9 +44,7 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
const memoContainerRef = useRef<HTMLDivElement>(null);
|
const memoContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const readonly = memo.creatorUsername !== user?.username;
|
const readonly = memo.creatorUsername !== user?.username;
|
||||||
const creator = userV1Store.getUserByUsername(memo.creatorUsername);
|
const creator = userV1Store.getUserByUsername(memo.creatorUsername);
|
||||||
const referenceRelations = memo.relationList.filter(
|
const referenceRelations = memo.relationList.filter((relation) => relation.type === "REFERENCE");
|
||||||
(relation) => relation.memoId === memo.id && relation.relatedMemoId !== memo.id && relation.type === "REFERENCE"
|
|
||||||
);
|
|
||||||
const commentRelations = memo.relationList.filter((relation) => relation.relatedMemoId === memo.id && relation.type === "COMMENT");
|
const commentRelations = memo.relationList.filter((relation) => relation.relatedMemoId === memo.id && relation.type === "COMMENT");
|
||||||
|
|
||||||
// Prepare memo creator.
|
// Prepare memo creator.
|
||||||
@ -286,7 +284,7 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
onMemoContentDoubleClick={handleMemoContentDoubleClick}
|
onMemoContentDoubleClick={handleMemoContentDoubleClick}
|
||||||
/>
|
/>
|
||||||
<MemoResourceListView resourceList={memo.resourceList} />
|
<MemoResourceListView resourceList={memo.resourceList} />
|
||||||
<MemoRelationListView relationList={referenceRelations} />
|
<MemoRelationListView memo={memo} relationList={referenceRelations} />
|
||||||
<div className="mt-4 w-full flex flex-row justify-between items-center gap-2">
|
<div className="mt-4 w-full flex flex-row justify-between items-center gap-2">
|
||||||
<div className="flex flex-row justify-start items-center">
|
<div className="flex flex-row justify-start items-center">
|
||||||
{creator && (
|
{creator && (
|
||||||
|
@ -7,51 +7,44 @@ interface Props {
|
|||||||
setRelationList: (relationList: MemoRelation[]) => void;
|
setRelationList: (relationList: MemoRelation[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FormatedMemoRelation extends MemoRelation {
|
|
||||||
relatedMemo: Memo;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RelationListView = (props: Props) => {
|
const RelationListView = (props: Props) => {
|
||||||
const { relationList, setRelationList } = props;
|
const { relationList, setRelationList } = props;
|
||||||
const memoCacheStore = useMemoCacheStore();
|
const memoCacheStore = useMemoCacheStore();
|
||||||
const [formatedMemoRelationList, setFormatedMemoRelationList] = useState<FormatedMemoRelation[]>([]);
|
const [referencingMemoList, setReferencingMemoList] = useState<Memo[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const requests = relationList
|
const requests = relationList
|
||||||
.filter((relation) => relation.type === "REFERENCE")
|
.filter((relation) => relation.type === "REFERENCE")
|
||||||
.map(async (relation) => {
|
.map(async (relation) => {
|
||||||
const relatedMemo = await memoCacheStore.getOrFetchMemoById(relation.relatedMemoId);
|
return await memoCacheStore.getOrFetchMemoById(relation.relatedMemoId);
|
||||||
return {
|
|
||||||
...relation,
|
|
||||||
relatedMemo,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
const list = await Promise.all(requests);
|
const list = await Promise.all(requests);
|
||||||
setFormatedMemoRelationList(list);
|
setReferencingMemoList(list);
|
||||||
})();
|
})();
|
||||||
}, [relationList]);
|
}, [relationList]);
|
||||||
|
|
||||||
const handleDeleteRelation = async (memoRelation: FormatedMemoRelation) => {
|
const handleDeleteRelation = async (memo: Memo) => {
|
||||||
const newRelationList = relationList.filter((relation) => relation.relatedMemoId !== memoRelation.relatedMemoId);
|
setRelationList(relationList.filter((relation) => relation.relatedMemoId !== memo.id));
|
||||||
setRelationList(newRelationList);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log("referencingMemoList", referencingMemoList);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{formatedMemoRelationList.length > 0 && (
|
{referencingMemoList.length > 0 && (
|
||||||
<div className="w-full flex flex-row gap-2 mt-2 flex-wrap">
|
<div className="w-full flex flex-row gap-2 mt-2 flex-wrap">
|
||||||
{formatedMemoRelationList.map((memoRelation) => {
|
{referencingMemoList.map((memo) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={memoRelation.relatedMemoId}
|
key={memo.id}
|
||||||
className="w-auto max-w-[50%] overflow-hidden flex flex-row justify-start items-center bg-gray-100 dark:bg-zinc-800 hover:opacity-80 rounded text-sm p-1 px-2 text-gray-500 cursor-pointer"
|
className="w-auto max-w-xs overflow-hidden flex flex-row justify-start items-center bg-gray-100 dark:bg-zinc-800 hover:opacity-80 rounded text-sm p-1 px-2 text-gray-500 cursor-pointer hover:line-through"
|
||||||
|
onClick={() => handleDeleteRelation(memo)}
|
||||||
>
|
>
|
||||||
<Icon.Link className="w-4 h-auto shrink-0" />
|
<Icon.Link className="w-4 h-auto shrink-0 opacity-80" />
|
||||||
<span className="mx-1 max-w-full text-ellipsis font-mono whitespace-nowrap overflow-hidden">
|
<span className="px-1 shrink-0 opacity-80">#{memo.id}</span>
|
||||||
{memoRelation.relatedMemo.content}
|
<span className="max-w-full text-ellipsis whitespace-nowrap overflow-hidden">{memo.content}</span>
|
||||||
</span>
|
<Icon.X className="w-4 h-auto hover:opacity-80 shrink-0 ml-1" />
|
||||||
<Icon.X className="w-4 h-auto hover:opacity-80 shrink-0" onClick={() => handleDeleteRelation(memoRelation)} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -1,45 +1,82 @@
|
|||||||
|
import { Tooltip } from "@mui/joy";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
import { useMemoCacheStore } from "@/store/v1";
|
import { useMemoCacheStore } from "@/store/v1";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
memo: Memo;
|
||||||
relationList: MemoRelation[];
|
relationList: MemoRelation[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const MemoRelationListView = (props: Props) => {
|
const MemoRelationListView = (props: Props) => {
|
||||||
|
const { memo, relationList } = props;
|
||||||
const memoCacheStore = useMemoCacheStore();
|
const memoCacheStore = useMemoCacheStore();
|
||||||
const [relatedMemoList, setRelatedMemoList] = useState<Memo[]>([]);
|
const [referencingMemoList, setReferencingMemoList] = useState<Memo[]>([]);
|
||||||
|
const [referencedMemoList, setReferencedMemoList] = useState<Memo[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const memoList = await Promise.all(props.relationList.map((relation) => memoCacheStore.getOrFetchMemoById(relation.relatedMemoId)));
|
const referencingMemoList = await Promise.all(
|
||||||
setRelatedMemoList(memoList);
|
relationList
|
||||||
|
.filter((relation) => relation.memoId === memo.id && relation.relatedMemoId !== memo.id)
|
||||||
|
.map((relation) => memoCacheStore.getOrFetchMemoById(relation.relatedMemoId))
|
||||||
|
);
|
||||||
|
setReferencingMemoList(referencingMemoList);
|
||||||
|
const referencedMemoList = await Promise.all(
|
||||||
|
relationList
|
||||||
|
.filter((relation) => relation.memoId !== memo.id && relation.relatedMemoId === memo.id)
|
||||||
|
.map((relation) => memoCacheStore.getOrFetchMemoById(relation.memoId))
|
||||||
|
);
|
||||||
|
setReferencedMemoList(referencedMemoList);
|
||||||
})();
|
})();
|
||||||
}, [props.relationList]);
|
}, [memo, relationList]);
|
||||||
|
|
||||||
const handleGotoMemoDetail = (memo: Memo) => {
|
|
||||||
window.open(`/m/${memo.id}`, "_blank");
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{relatedMemoList.length > 0 && (
|
{referencingMemoList.length > 0 && (
|
||||||
<div className="w-full max-w-full overflow-hidden grid grid-cols-1 gap-1 mt-2">
|
<div className="w-full mt-2 flex flex-row justify-start items-start">
|
||||||
{relatedMemoList.map((memo) => {
|
<Tooltip title="References" placement="top">
|
||||||
|
<Icon.Link className="w-4 h-auto shrink-0 opacity-70 mt-1.5 mr-1" />
|
||||||
|
</Tooltip>
|
||||||
|
<div className="w-auto max-w-[calc(100%-2rem)] flex flex-row justify-start items-center flex-wrap gap-2">
|
||||||
|
{referencingMemoList.map((memo) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div key={memo.id} className="block w-auto max-w-[50%]">
|
||||||
key={memo.id}
|
<Link
|
||||||
className="w-auto flex flex-row justify-start items-center hover:bg-gray-100 dark:hover:bg-zinc-800 rounded text-sm p-1 text-gray-500 dark:text-gray-400 cursor-pointer"
|
className="px-2 border rounded-full w-auto text-sm leading-6 flex flex-row justify-start items-center flex-nowrap text-gray-600 dark:text-gray-300 dark:border-gray-600 hover:shadow hover:opacity-80"
|
||||||
onClick={() => handleGotoMemoDetail(memo)}
|
to={`/m/${memo.id}`}
|
||||||
>
|
>
|
||||||
<div className="w-5 h-5 flex justify-center items-center shrink-0 bg-gray-100 dark:bg-zinc-800 rounded-full">
|
<span className="opacity-70 mr-1">#{memo.id}</span>
|
||||||
<Icon.Link className="w-3 h-auto" />
|
<span className="truncate">{memo.content}</span>
|
||||||
</div>
|
</Link>
|
||||||
<span className="mx-1 w-auto truncate">{memo.content}</span>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{referencedMemoList.length > 0 && (
|
||||||
|
<div className="w-full mt-2 flex flex-row justify-start items-start">
|
||||||
|
<Tooltip title="Referenced" placement="top">
|
||||||
|
<Icon.Milestone className="w-4 h-auto shrink-0 opacity-70 mt-1.5 mr-1" />
|
||||||
|
</Tooltip>
|
||||||
|
<div className="grow w-auto max-w-[calc(100%-2rem)] flex flex-row justify-start items-center flex-wrap gap-2">
|
||||||
|
{referencedMemoList.map((memo) => {
|
||||||
|
return (
|
||||||
|
<div key={memo.id} className="block w-auto max-w-[50%]">
|
||||||
|
<Link
|
||||||
|
className="px-2 border rounded-full w-auto text-sm leading-6 flex flex-row justify-start items-center flex-nowrap text-gray-600 dark:text-gray-300 dark:border-gray-600 hover:shadow hover:opacity-80"
|
||||||
|
to={`/m/${memo.id}`}
|
||||||
|
>
|
||||||
|
<span className="opacity-70 mr-1">#{memo.id}</span>
|
||||||
|
<span className="truncate">{memo.content}</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -78,7 +78,7 @@ const MemoResourceListView: React.FC<Props> = (props: Props) => {
|
|||||||
|
|
||||||
<div className={`w-full flex flex-col justify-start items-start ${className || ""}`}>
|
<div className={`w-full flex flex-col justify-start items-start ${className || ""}`}>
|
||||||
{videoResourceList.length > 0 && (
|
{videoResourceList.length > 0 && (
|
||||||
<div className="w-full grid grid-cols-2 sm:grid-cols-3 gap-2 mt-2wrapper">
|
<div className="w-full grid grid-cols-2 sm:grid-cols-3 gap-2 mt-2">
|
||||||
{videoResourceList.map((resource) => {
|
{videoResourceList.map((resource) => {
|
||||||
const url = getResourceUrl(resource);
|
const url = getResourceUrl(resource);
|
||||||
return (
|
return (
|
||||||
|
@ -36,10 +36,7 @@ const MemoDetail = () => {
|
|||||||
const memoId = Number(params.memoId);
|
const memoId = Number(params.memoId);
|
||||||
const memo = memoStore.state.memos.find((memo) => memo.id === memoId);
|
const memo = memoStore.state.memos.find((memo) => memo.id === memoId);
|
||||||
const allowEdit = memo?.creatorUsername === currentUser?.username;
|
const allowEdit = memo?.creatorUsername === currentUser?.username;
|
||||||
const referenceRelations =
|
const referenceRelations = memo?.relationList.filter((relation) => relation.type === "REFERENCE") || [];
|
||||||
memo?.relationList.filter(
|
|
||||||
(relation) => relation.memoId === memo?.id && relation.relatedMemoId !== memo?.id && relation.type === "REFERENCE"
|
|
||||||
) || [];
|
|
||||||
const commentRelations = memo?.relationList.filter((relation) => relation.relatedMemoId === memo.id && relation.type === "COMMENT") || [];
|
const commentRelations = memo?.relationList.filter((relation) => relation.relatedMemoId === memo.id && relation.type === "COMMENT") || [];
|
||||||
const comments = commentRelations
|
const comments = commentRelations
|
||||||
.map((relation) => memoStore.state.memos.find((memo) => memo.id === relation.memoId))
|
.map((relation) => memoStore.state.memos.find((memo) => memo.id === relation.memoId))
|
||||||
@ -113,9 +110,9 @@ const MemoDetail = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="relative flex-grow max-w-2xl w-full min-h-full flex flex-col justify-start items-start px-4 pb-6">
|
<div className="relative flex-grow max-w-2xl w-full min-h-full flex flex-col justify-start items-start px-4 pb-6">
|
||||||
{memo.parent && (
|
{memo.parent && (
|
||||||
<div className="w-full mb-4">
|
<div className="w-auto mb-4">
|
||||||
<Link
|
<Link
|
||||||
className="px-3 py-1 border rounded-full max-w-xs w-full text-sm flex flex-row justify-start items-center flex-nowrap text-gray-600 dark:text-gray-400 dark:border-gray-500 hover:shadow hover:opacity-80"
|
className="px-3 py-1 border rounded-full max-w-xs w-auto text-sm flex flex-row justify-start items-center flex-nowrap text-gray-600 dark:text-gray-400 dark:border-gray-500 hover:shadow hover:opacity-80"
|
||||||
to={`/m/${memo.parent.id}`}
|
to={`/m/${memo.parent.id}`}
|
||||||
>
|
>
|
||||||
<Icon.ArrowUpLeftFromCircle className="w-4 h-auto shrink-0 opacity-60" />
|
<Icon.ArrowUpLeftFromCircle className="w-4 h-auto shrink-0 opacity-60" />
|
||||||
@ -129,7 +126,7 @@ const MemoDetail = () => {
|
|||||||
</div>
|
</div>
|
||||||
<MemoContent content={memo.content} />
|
<MemoContent content={memo.content} />
|
||||||
<MemoResourceListView resourceList={memo.resourceList} />
|
<MemoResourceListView resourceList={memo.resourceList} />
|
||||||
<MemoRelationListView 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">
|
||||||
<Tooltip title={"Identifier"} placement="top">
|
<Tooltip title={"Identifier"} placement="top">
|
||||||
|
Reference in New Issue
Block a user