Commit 4ad60286 authored by Steven's avatar Steven

refactor: use popover instead of dialog for memo relations

parent a5978e76
import { IconButton } from "@mui/joy";
import { uniqBy } from "lodash-es";
import { useContext } from "react";
import toast from "react-hot-toast";
import showCreateMemoRelationDialog from "@/components/CreateMemoRelationDialog";
import Icon from "@/components/Icon";
import { MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service";
import { Memo } from "@/types/proto/api/v1/memo_service";
import { EditorRefActions } from "../Editor";
import { MemoEditorContext } from "../types";
interface Props {
editorRef: React.RefObject<EditorRefActions>;
}
const AddMemoRelationButton = (props: Props) => {
const { editorRef } = props;
const context = useContext(MemoEditorContext);
const handleAddMemoRelationBtnClick = () => {
showCreateMemoRelationDialog({
onConfirm: (memos, embedded) => {
// If embedded mode is enabled, embed the memo instead of creating a relation.
if (embedded) {
if (!editorRef.current) {
toast.error("Failed to embed memo");
return;
}
const cursorPosition = editorRef.current.getCursorPosition();
const prevValue = editorRef.current.getContent().slice(0, cursorPosition);
if (prevValue !== "" && !prevValue.endsWith("\n")) {
editorRef.current.insertText("\n");
}
for (const memo of memos) {
editorRef.current.insertText(`![[memos/${memo.uid}]]\n`);
}
setTimeout(() => {
editorRef.current?.scrollToCursor();
editorRef.current?.focus();
});
return;
}
context.setRelationList(
uniqBy(
[
...memos.map((memo) => ({
memo: context.memoName || "",
relatedMemo: memo.name,
type: MemoRelation_Type.REFERENCE,
})),
...context.relationList,
].filter((relation) => relation.relatedMemo !== context.memoName),
"relatedMemo",
),
);
},
filter: (memo: Memo) =>
memo.name !== context.memoName && !context.relationList.some((relation) => relation.relatedMemo === memo.name),
});
};
return (
<IconButton size="sm" onClick={handleAddMemoRelationBtnClick}>
<Icon.Link className="w-5 h-5 mx-auto" />
</IconButton>
);
};
export default AddMemoRelationButton;
import { Autocomplete, AutocompleteOption, Button, Checkbox, Chip, IconButton } from "@mui/joy"; import { Autocomplete, AutocompleteOption, Button, Checkbox, Chip, IconButton } from "@mui/joy";
import React, { useState } from "react"; import { uniqBy } from "lodash-es";
import React, { useContext, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import useDebounce from "react-use/lib/useDebounce"; import useDebounce from "react-use/lib/useDebounce";
import Icon from "@/components/Icon";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/Popover";
import { memoServiceClient } from "@/grpcweb"; import { memoServiceClient } from "@/grpcweb";
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts"; import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service";
import { Memo } from "@/types/proto/api/v1/memo_service"; import { Memo } from "@/types/proto/api/v1/memo_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { generateDialog } from "./Dialog"; import { EditorRefActions } from "../Editor";
import Icon from "./Icon"; import { MemoEditorContext } from "../types";
interface Props extends DialogProps { interface Props {
onConfirm: (memos: Memo[], embedded?: boolean) => void; editorRef: React.RefObject<EditorRefActions>;
// Custom filter function for filtering memos.
filter?: (memo: Memo) => boolean;
} }
const CreateMemoRelationDialog: React.FC<Props> = (props: Props) => { const AddMemoRelationPopover = (props: Props) => {
const { destroy, onConfirm } = props; const { editorRef } = props;
const t = useTranslate(); const t = useTranslate();
const context = useContext(MemoEditorContext);
const user = useCurrentUser(); const user = useCurrentUser();
const [searchText, setSearchText] = useState<string>(""); const [searchText, setSearchText] = useState<string>("");
const [isFetching, setIsFetching] = useState<boolean>(true); const [isFetching, setIsFetching] = useState<boolean>(true);
const [fetchedMemos, setFetchedMemos] = useState<Memo[]>([]); const [fetchedMemos, setFetchedMemos] = useState<Memo[]>([]);
const [selectedMemos, setSelectedMemos] = useState<Memo[]>([]); const [selectedMemos, setSelectedMemos] = useState<Memo[]>([]);
const [embedded, setEmbedded] = useState<boolean>(true); const [embedded, setEmbedded] = useState<boolean>(true);
const filteredMemos = fetchedMemos.filter((memo) => !selectedMemos.includes(memo) && (!props.filter || props.filter(memo))); const [popoverOpen, setPopoverOpen] = useState<boolean>(false);
const filteredMemos = fetchedMemos.filter(
(memo) =>
!selectedMemos.includes(memo) &&
memo.name !== context.memoName &&
!context.relationList.some((relation) => relation.relatedMemo === memo.name),
);
useDebounce( useDebounce(
async () => { async () => {
setIsFetching(true); setIsFetching(true);
try { try {
const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`, `include_comments == true`]; const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`];
if (searchText) { if (searchText) {
filters.push(`content_search == [${JSON.stringify(searchText)}]`); filters.push(`content_search == [${JSON.stringify(searchText)}]`);
} }
...@@ -74,24 +84,54 @@ const CreateMemoRelationDialog: React.FC<Props> = (props: Props) => { ...@@ -74,24 +84,54 @@ const CreateMemoRelationDialog: React.FC<Props> = (props: Props) => {
); );
}; };
const handleCloseDialog = () => { const addMemoRelations = async () => {
destroy(); // If embedded mode is enabled, embed the memo instead of creating a relation.
}; if (embedded) {
if (!editorRef.current) {
toast.error("Failed to embed memo");
return;
}
const handleConfirmBtnClick = async () => { const cursorPosition = editorRef.current.getCursorPosition();
onConfirm(selectedMemos, embedded); const prevValue = editorRef.current.getContent().slice(0, cursorPosition);
destroy(); if (prevValue !== "" && !prevValue.endsWith("\n")) {
editorRef.current.insertText("\n");
}
for (const memo of selectedMemos) {
editorRef.current.insertText(`![[memos/${memo.uid}]]\n`);
}
setTimeout(() => {
editorRef.current?.scrollToCursor();
editorRef.current?.focus();
});
} else {
context.setRelationList(
uniqBy(
[
...selectedMemos.map((memo) => ({
memo: context.memoName || "",
relatedMemo: memo.name,
type: MemoRelation_Type.REFERENCE,
})),
...context.relationList,
].filter((relation) => relation.relatedMemo !== context.memoName),
"relatedMemo",
),
);
}
setSelectedMemos([]);
setPopoverOpen(false);
}; };
return ( return (
<> <Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
<div className="dialog-header-container !w-96"> <PopoverTrigger>
<p className="title-text">{t("reference.add-references")}</p> <IconButton size="sm">
<IconButton size="sm" onClick={() => destroy()}> <Icon.Link className="w-5 h-5 mx-auto" />
<Icon.X className="w-5 h-auto" />
</IconButton> </IconButton>
</div> </PopoverTrigger>
<div className="dialog-content-container max-w-[24rem]"> <PopoverContent align="center">
<div className="w-[16rem] flex flex-col justify-start items-start">
<Autocomplete <Autocomplete
className="w-full" className="w-full"
size="md" size="md"
...@@ -128,31 +168,18 @@ const CreateMemoRelationDialog: React.FC<Props> = (props: Props) => { ...@@ -128,31 +168,18 @@ const CreateMemoRelationDialog: React.FC<Props> = (props: Props) => {
} }
onChange={(_, value) => setSelectedMemos(value)} onChange={(_, value) => setSelectedMemos(value)}
/> />
<div className="mt-3"> <div className="mt-2">
<Checkbox label={t("reference.embedded-usage")} checked={embedded} onChange={(e) => setEmbedded(e.target.checked)} /> <Checkbox size="sm" label={t("reference.embedded-usage")} checked={embedded} onChange={(e) => setEmbedded(e.target.checked)} />
</div> </div>
<div className="mt-4 w-full flex flex-row justify-end items-center space-x-1"> <div className="mt-2 w-full flex flex-row justify-start items-center space-x-1">
<Button variant="plain" color="neutral" onClick={handleCloseDialog}> <Button size="sm" onClick={addMemoRelations} disabled={selectedMemos.length === 0}>
{t("common.cancel")}
</Button>
<Button onClick={handleConfirmBtnClick} disabled={selectedMemos.length === 0}>
{t("common.confirm")} {t("common.confirm")}
</Button> </Button>
</div> </div>
</div> </div>
</> </PopoverContent>
</Popover>
); );
}; };
function showCreateMemoRelationDialog(props: Omit<Props, "destroy">) { export default AddMemoRelationPopover;
generateDialog(
{
className: "create-memo-relation-dialog",
dialogName: "create-memo-relation-dialog",
},
CreateMemoRelationDialog,
props,
);
}
export default showCreateMemoRelationDialog;
...@@ -20,7 +20,7 @@ import { useTranslate } from "@/utils/i18n"; ...@@ -20,7 +20,7 @@ import { useTranslate } from "@/utils/i18n";
import { convertVisibilityFromString, convertVisibilityToString } from "@/utils/memo"; import { convertVisibilityFromString, convertVisibilityToString } from "@/utils/memo";
import Icon from "../Icon"; import Icon from "../Icon";
import VisibilityIcon from "../VisibilityIcon"; import VisibilityIcon from "../VisibilityIcon";
import AddMemoRelationButton from "./ActionButton/AddMemoRelationButton"; import AddMemoRelationPopover from "./ActionButton/AddMemoRelationPopover";
import MarkdownMenu from "./ActionButton/MarkdownMenu"; import MarkdownMenu from "./ActionButton/MarkdownMenu";
import TagSelector from "./ActionButton/TagSelector"; import TagSelector from "./ActionButton/TagSelector";
import UploadResourceButton from "./ActionButton/UploadResourceButton"; import UploadResourceButton from "./ActionButton/UploadResourceButton";
...@@ -432,7 +432,7 @@ const MemoEditor = (props: Props) => { ...@@ -432,7 +432,7 @@ const MemoEditor = (props: Props) => {
<TagSelector editorRef={editorRef} /> <TagSelector editorRef={editorRef} />
<MarkdownMenu editorRef={editorRef} /> <MarkdownMenu editorRef={editorRef} />
<UploadResourceButton /> <UploadResourceButton />
<AddMemoRelationButton editorRef={editorRef} /> <AddMemoRelationPopover editorRef={editorRef} />
</div> </div>
</div> </div>
<Divider className="!mt-2" /> <Divider className="!mt-2" />
......
...@@ -22,6 +22,13 @@ const theme = extendTheme({ ...@@ -22,6 +22,13 @@ const theme = extendTheme({
}, },
}, },
}, },
JoyAutocomplete: {
styleOverrides: {
listbox: {
zIndex: 9999,
},
},
},
}, },
}); });
......
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