diff --git a/web/src/components/CreateMemoRelationDialog.tsx b/web/src/components/CreateMemoRelationDialog.tsx index 934903c7..98af069c 100644 --- a/web/src/components/CreateMemoRelationDialog.tsx +++ b/web/src/components/CreateMemoRelationDialog.tsx @@ -1,8 +1,10 @@ -import { Button, IconButton, Input } from "@mui/joy"; -import { isNaN, unionBy } from "lodash-es"; +import { Autocomplete, AutocompleteOption, Button, Chip, IconButton } from "@mui/joy"; import React, { useState } from "react"; import { toast } from "react-hot-toast"; +import useDebounce from "react-use/lib/useDebounce"; import { memoServiceClient } from "@/grpcweb"; +import { getDateTimeString } from "@/helpers/datetime"; +import useCurrentUser from "@/hooks/useCurrentUser"; import { Memo } from "@/types/proto/api/v2/memo_service"; import { useTranslate } from "@/utils/i18n"; import { generateDialog } from "./Dialog"; @@ -16,46 +18,58 @@ interface Props extends DialogProps { const CreateMemoRelationDialog: React.FC = (props: Props) => { const { destroy, onCancel, onConfirm } = props; const t = useTranslate(); - const [memoId, setMemoId] = useState(""); - const [memoList, setMemoList] = useState([]); + const user = useCurrentUser(); + const [searchText, setSearchText] = useState(""); + const [isFetching, setIsFetching] = useState(true); + const [fetchedMemos, setFetchedMemos] = useState([]); + const [selectedMemos, setSelectedMemos] = useState([]); + const filteredMemos = fetchedMemos.filter((memo) => !selectedMemos.includes(memo)); - const handleMemoIdInputKeyDown = (event: React.KeyboardEvent) => { - if (event.key === "Enter") { - handleSaveBtnClick(); - } - }; - - const handleMemoIdChanged = (event: React.ChangeEvent) => { - const memoId = event.target.value; - setMemoId(memoId.trim()); - }; - - const handleSaveBtnClick = async () => { - const id = Number(memoId); - if (isNaN(id)) { - toast.error("Invalid memo id"); - return; - } - - try { - const { memo } = await memoServiceClient.getMemo({ - id, - }); - if (!memo) { - toast.error("Not found memo"); - return; + useDebounce( + async () => { + setIsFetching(true); + try { + const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`]; + if (searchText) { + filters.push(`content_search == [${JSON.stringify(searchText)}]`); + } + const { memos } = await memoServiceClient.listMemos({ + limit: 10, + filter: filters.length > 0 ? filters.join(" && ") : undefined, + }); + setFetchedMemos(memos); + } catch (error: any) { + console.error(error); + toast.error(error.response.data.message); } + setIsFetching(false); + }, + 300, + [searchText] + ); - setMemoId(""); - setMemoList(unionBy([memo, ...memoList], "id")); - } catch (error: any) { - console.error(error); - toast.error(error.response.data.message); + const getHighlightedContent = (content: string) => { + const index = content.toLowerCase().indexOf(searchText.toLowerCase()); + if (index === -1) { + return content; + } + let before = content.slice(0, index); + if (before.length > 20) { + before = "..." + before.slice(before.length - 20); + } + const highlighted = content.slice(index, index + searchText.length); + let after = content.slice(index + searchText.length); + if (after.length > 20) { + after = after.slice(0, 20) + "..."; } - }; - const handleDeleteMemoRelation = async (memo: Memo) => { - setMemoList(memoList.filter((m) => m !== memo)); + return ( + <> + {before} + {highlighted} + {after} + + ); }; const handleCloseDialog = () => { @@ -67,7 +81,7 @@ const CreateMemoRelationDialog: React.FC = (props: Props) => { const handleConfirmBtnClick = async () => { if (onConfirm) { - onConfirm(memoList.map((memo) => memo.id)); + onConfirm(selectedMemos.map((memo) => memo.id)); } destroy(); }; @@ -81,37 +95,49 @@ const CreateMemoRelationDialog: React.FC = (props: Props) => {
- } - /> - {memoList.length > 0 && ( - <> -
- {memoList.map((memo) => ( -
handleDeleteMemoRelation(memo)} - > - {memo.content} - + clearOnBlur + disableClearable + placeholder={"Search content"} + noOptionsText={"No memos found"} + options={filteredMemos} + loading={isFetching} + inputValue={searchText} + value={selectedMemos} + multiple + onInputChange={(_, value) => setSearchText(value.trim())} + getOptionKey={(option) => option.name} + getOptionLabel={(option) => option.content} + isOptionEqualToValue={(option, value) => option.id === value.id} + renderOption={(props, option) => ( + +
+

{getDateTimeString(option.displayTime)}

+

+ {searchText ? getHighlightedContent(option.content) : option.content} +

+
+
+ )} + renderTags={(memos) => + memos.map((memo) => ( + +
+

{getDateTimeString(memo.displayTime)}

+ {memo.content}
- ))} -
- - )} + + )) + } + onChange={(_, value) => setSelectedMemos(value)} + />
-
diff --git a/web/src/components/MemoEditor/RelationListView.tsx b/web/src/components/MemoEditor/RelationListView.tsx index 3003bab1..187ce87c 100644 --- a/web/src/components/MemoEditor/RelationListView.tsx +++ b/web/src/components/MemoEditor/RelationListView.tsx @@ -43,7 +43,7 @@ const RelationListView = (props: Props) => { > {memo.content} - +
); })} diff --git a/web/src/pages/Archived.tsx b/web/src/pages/Archived.tsx index 23834941..8625a49c 100644 --- a/web/src/pages/Archived.tsx +++ b/web/src/pages/Archived.tsx @@ -34,10 +34,10 @@ const Archived = () => { const filters = [`creator == "${user.name}"`, "row_status == 'ARCHIVED'"]; const contentSearch: string[] = []; if (tagQuery) { - contentSearch.push(`"#${tagQuery}"`); + contentSearch.push(JSON.stringify(`#${tagQuery}`)); } if (textQuery) { - contentSearch.push(`"${textQuery}"`); + contentSearch.push(JSON.stringify(textQuery)); } if (contentSearch.length > 0) { filters.push(`content_search == [${contentSearch.join(", ")}]`); diff --git a/web/src/pages/Explore.tsx b/web/src/pages/Explore.tsx index d1810e20..7518e7a7 100644 --- a/web/src/pages/Explore.tsx +++ b/web/src/pages/Explore.tsx @@ -32,10 +32,10 @@ const Explore = () => { const filters = [`row_status == "NORMAL"`, `visibilities == [${user ? "'PUBLIC', 'PROTECTED'" : "'PUBLIC'"}]`]; const contentSearch: string[] = []; if (tagQuery) { - contentSearch.push(`"#${tagQuery}"`); + contentSearch.push(JSON.stringify(`#${tagQuery}`)); } if (textQuery) { - contentSearch.push(`"${textQuery}"`); + contentSearch.push(JSON.stringify(textQuery)); } if (contentSearch.length > 0) { filters.push(`content_search == [${contentSearch.join(", ")}]`); @@ -56,7 +56,7 @@ const Explore = () => {
{sortedMemos.map((memo) => ( - + ))} {isRequesting ? (
diff --git a/web/src/pages/Home.tsx b/web/src/pages/Home.tsx index 031c6385..3e872566 100644 --- a/web/src/pages/Home.tsx +++ b/web/src/pages/Home.tsx @@ -42,10 +42,10 @@ const Home = () => { const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`, `order_by_pinned == true`]; const contentSearch: string[] = []; if (tagQuery) { - contentSearch.push(`"#${tagQuery}"`); + contentSearch.push(JSON.stringify(`#${tagQuery}`)); } if (textQuery) { - contentSearch.push(`"${textQuery}"`); + contentSearch.push(JSON.stringify(textQuery)); } if (contentSearch.length > 0) { filters.push(`content_search == [${contentSearch.join(", ")}]`); @@ -73,7 +73,7 @@ const Home = () => {
{sortedMemos.map((memo) => ( - + ))} {isRequesting ? (
diff --git a/web/src/pages/MemoDetail.tsx b/web/src/pages/MemoDetail.tsx index d50d1617..9fe1cea3 100644 --- a/web/src/pages/MemoDetail.tsx +++ b/web/src/pages/MemoDetail.tsx @@ -198,7 +198,7 @@ const MemoDetail = () => { ({comments.length})
{comments.map((comment) => ( - + ))} )} diff --git a/web/src/pages/Timeline.tsx b/web/src/pages/Timeline.tsx index 5a961488..ffec52e1 100644 --- a/web/src/pages/Timeline.tsx +++ b/web/src/pages/Timeline.tsx @@ -69,10 +69,10 @@ const Timeline = () => { const filters = [`row_status == "NORMAL"`]; const contentSearch: string[] = []; if (tagQuery) { - contentSearch.push(`"#${tagQuery}"`); + contentSearch.push(JSON.stringify(`#${tagQuery}`)); } if (textQuery) { - contentSearch.push(`"${textQuery}"`); + contentSearch.push(JSON.stringify(textQuery)); } if (contentSearch.length > 0) { filters.push(`content_search == [${contentSearch.join(", ")}]`); @@ -90,10 +90,10 @@ const Timeline = () => { const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`]; const contentSearch: string[] = []; if (tagQuery) { - contentSearch.push(`"#${tagQuery}"`); + contentSearch.push(JSON.stringify(`#${tagQuery}`)); } if (textQuery) { - contentSearch.push(`"${textQuery}"`); + contentSearch.push(JSON.stringify(textQuery)); } if (contentSearch.length > 0) { filters.push(`content_search == [${contentSearch.join(", ")}]`); @@ -159,7 +159,7 @@ const Timeline = () => {
{group.memos.map((memo, index) => (
diff --git a/web/src/pages/UserProfile.tsx b/web/src/pages/UserProfile.tsx index e3c695d2..396f5930 100644 --- a/web/src/pages/UserProfile.tsx +++ b/web/src/pages/UserProfile.tsx @@ -67,10 +67,10 @@ const UserProfile = () => { const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`, `order_by_pinned == true`]; const contentSearch: string[] = []; if (tagQuery) { - contentSearch.push(`"#${tagQuery}"`); + contentSearch.push(JSON.stringify(`#${tagQuery}`)); } if (textQuery) { - contentSearch.push(`"${textQuery}"`); + contentSearch.push(JSON.stringify(textQuery)); } if (contentSearch.length > 0) { filters.push(`content_search == [${contentSearch.join(", ")}]`); @@ -107,7 +107,7 @@ const UserProfile = () => {
{sortedMemos.map((memo) => ( - + ))} {isRequesting ? (