chore: fix memo search

pull/2658/head
Steven 1 year ago
parent 81524c38e9
commit 02265a6e1a

@ -56,8 +56,8 @@ func (s *APIV2Service) ListMemos(ctx context.Context, request *apiv2pb.ListMemos
if err != nil { if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid filter: %v", err) return nil, status.Errorf(codes.InvalidArgument, "invalid filter: %v", err)
} }
if filter.Visibility != nil { if len(filter.ContentSearch) > 0 {
memoFind.VisibilityList = []store.Visibility{*filter.Visibility} memoFind.ContentSearch = filter.ContentSearch
} }
if len(filter.Visibilities) > 0 { if len(filter.Visibilities) > 0 {
memoFind.VisibilityList = filter.Visibilities memoFind.VisibilityList = filter.Visibilities
@ -84,12 +84,6 @@ func (s *APIV2Service) ListMemos(ctx context.Context, request *apiv2pb.ListMemos
} }
memoFind.CreatorID = &user.ID memoFind.CreatorID = &user.ID
} }
if filter.Tag != nil {
memoFind.ContentSearch = append(memoFind.ContentSearch, fmt.Sprintf("#%s", *filter.Tag))
}
if filter.ContentSearch != nil {
memoFind.ContentSearch = append(memoFind.ContentSearch, *filter.ContentSearch)
}
if filter.RowStatus != nil { if filter.RowStatus != nil {
memoFind.RowStatus = filter.RowStatus memoFind.RowStatus = filter.RowStatus
} }
@ -198,6 +192,14 @@ func (s *APIV2Service) UpdateMemo(ctx context.Context, request *apiv2pb.UpdateMe
} else if path == "created_ts" { } else if path == "created_ts" {
createdTs := request.Memo.CreateTime.AsTime().Unix() createdTs := request.Memo.CreateTime.AsTime().Unix()
update.CreatedTs = &createdTs update.CreatedTs = &createdTs
} else if path == "pinned" {
if _, err := s.Store.UpsertMemoOrganizer(ctx, &store.MemoOrganizer{
MemoID: request.Id,
UserID: user.ID,
Pinned: request.Memo.Pinned,
}); err != nil {
return nil, status.Errorf(codes.Internal, "failed to upsert memo organizer")
}
} }
} }
@ -512,22 +514,20 @@ func convertVisibilityToStore(visibility apiv2pb.Visibility) store.Visibility {
// ListMemosFilterCELAttributes are the CEL attributes for ListMemosFilter. // ListMemosFilterCELAttributes are the CEL attributes for ListMemosFilter.
var ListMemosFilterCELAttributes = []cel.EnvOption{ var ListMemosFilterCELAttributes = []cel.EnvOption{
cel.Variable("visibility", cel.StringType),
cel.Variable("visibilities", cel.ListType(cel.StringType)), cel.Variable("visibilities", cel.ListType(cel.StringType)),
cel.Variable("created_ts_before", cel.IntType), cel.Variable("created_ts_before", cel.IntType),
cel.Variable("created_ts_after", cel.IntType), cel.Variable("created_ts_after", cel.IntType),
cel.Variable("creator", cel.StringType), cel.Variable("creator", cel.StringType),
cel.Variable("content_search", cel.ListType(cel.StringType)),
cel.Variable("row_status", cel.StringType), cel.Variable("row_status", cel.StringType),
} }
type ListMemosFilter struct { type ListMemosFilter struct {
Visibility *store.Visibility ContentSearch []string
Visibilities []store.Visibility Visibilities []store.Visibility
CreatedTsBefore *int64 CreatedTsBefore *int64
CreatedTsAfter *int64 CreatedTsAfter *int64
Creator *string Creator *string
Tag *string
ContentSearch *string
RowStatus *store.RowStatus RowStatus *store.RowStatus
} }
@ -554,39 +554,30 @@ func findField(callExpr *expr.Expr_Call, filter *ListMemosFilter) {
if len(callExpr.Args) == 2 { if len(callExpr.Args) == 2 {
idExpr := callExpr.Args[0].GetIdentExpr() idExpr := callExpr.Args[0].GetIdentExpr()
if idExpr != nil { if idExpr != nil {
if idExpr.Name == "visibility" { if idExpr.Name == "content_search" {
visibility := store.Visibility(callExpr.Args[1].GetConstExpr().GetStringValue()) contentSearch := []string{}
filter.Visibility = &visibility for _, expr := range callExpr.Args[1].GetListExpr().GetElements() {
} value := expr.GetConstExpr().GetStringValue()
if idExpr.Name == "visibilities" { contentSearch = append(contentSearch, value)
}
filter.ContentSearch = contentSearch
} else if idExpr.Name == "visibilities" {
visibilities := []store.Visibility{} visibilities := []store.Visibility{}
for _, expr := range callExpr.Args[1].GetListExpr().GetElements() { for _, expr := range callExpr.Args[1].GetListExpr().GetElements() {
value := expr.GetConstExpr().GetStringValue() value := expr.GetConstExpr().GetStringValue()
visibilities = append(visibilities, store.Visibility(value)) visibilities = append(visibilities, store.Visibility(value))
} }
filter.Visibilities = visibilities filter.Visibilities = visibilities
} } else if idExpr.Name == "created_ts_before" {
if idExpr.Name == "created_ts_before" {
createdTsBefore := callExpr.Args[1].GetConstExpr().GetInt64Value() createdTsBefore := callExpr.Args[1].GetConstExpr().GetInt64Value()
filter.CreatedTsBefore = &createdTsBefore filter.CreatedTsBefore = &createdTsBefore
} } else if idExpr.Name == "created_ts_after" {
if idExpr.Name == "created_ts_after" {
createdTsAfter := callExpr.Args[1].GetConstExpr().GetInt64Value() createdTsAfter := callExpr.Args[1].GetConstExpr().GetInt64Value()
filter.CreatedTsAfter = &createdTsAfter filter.CreatedTsAfter = &createdTsAfter
} } else if idExpr.Name == "creator" {
if idExpr.Name == "creator" {
creator := callExpr.Args[1].GetConstExpr().GetStringValue() creator := callExpr.Args[1].GetConstExpr().GetStringValue()
filter.Creator = &creator filter.Creator = &creator
} } else if idExpr.Name == "row_status" {
if idExpr.Name == "tag" {
tag := callExpr.Args[1].GetConstExpr().GetStringValue()
filter.Tag = &tag
}
if idExpr.Name == "content_search" {
contentSearch := callExpr.Args[1].GetConstExpr().GetStringValue()
filter.ContentSearch = &contentSearch
}
if idExpr.Name == "row_status" {
rowStatus := store.RowStatus(callExpr.Args[1].GetConstExpr().GetStringValue()) rowStatus := store.RowStatus(callExpr.Args[1].GetConstExpr().GetStringValue())
filter.RowStatus = &rowStatus filter.RowStatus = &rowStatus
} }

@ -135,7 +135,7 @@ message ListMemosRequest {
int32 limit = 2; int32 limit = 2;
// Filter is used to filter memos returned in the list. // Filter is used to filter memos returned in the list.
// Format: "creator == users/{username} && visibility == PUBLIC" // Format: "creator == users/{username} && visibilities == ['PUBLIC', 'PROTECTED']"
string filter = 3; string filter = 3;
} }

@ -1762,7 +1762,7 @@
| ----- | ---- | ----- | ----------- | | ----- | ---- | ----- | ----------- |
| offset | [int32](#int32) | | offset is the offset of the first memo to return. | | offset | [int32](#int32) | | offset is the offset of the first memo to return. |
| limit | [int32](#int32) | | limit is the maximum number of memos to return. | | limit | [int32](#int32) | | limit is the maximum number of memos to return. |
| filter | [string](#string) | | Filter is used to filter memos returned in the list. Format: "creator == users/{username} && visibility == PUBLIC" | | filter | [string](#string) | | Filter is used to filter memos returned in the list. Format: "creator == users/{username} && visibilities == ['PUBLIC', 'PROTECTED']" |

@ -316,7 +316,7 @@ type ListMemosRequest struct {
// limit is the maximum number of memos to return. // limit is the maximum number of memos to return.
Limit int32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` Limit int32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"`
// Filter is used to filter memos returned in the list. // Filter is used to filter memos returned in the list.
// Format: "creator == users/{username} && visibility == PUBLIC" // Format: "creator == users/{username} && visibilities == ['PUBLIC', 'PROTECTED']"
Filter string `protobuf:"bytes,3,opt,name=filter,proto3" json:"filter,omitempty"` Filter string `protobuf:"bytes,3,opt,name=filter,proto3" json:"filter,omitempty"`
} }

@ -47,7 +47,7 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
} }
if v := find.ContentSearch; len(v) != 0 { if v := find.ContentSearch; len(v) != 0 {
for _, s := range v { for _, s := range v {
where, args = append(where, "memo.content LIKE ?"), append(args, "%"+s+"%") where, args = append(where, "memo.content LIKE ?"), append(args, fmt.Sprintf("%%%s%%", s))
} }
} }
if v := find.VisibilityList; len(v) != 0 { if v := find.VisibilityList; len(v) != 0 {

@ -1,9 +1,10 @@
import { useEffect, useState } from "react"; import { useEffect, useRef, useState } from "react";
import Empty from "@/components/Empty"; import Empty from "@/components/Empty";
import MemoFilter from "@/components/MemoFilter"; import MemoFilter from "@/components/MemoFilter";
import MemoViewV1 from "@/components/MemoViewV1"; import MemoViewV1 from "@/components/MemoViewV1";
import MobileHeader from "@/components/MobileHeader"; import MobileHeader from "@/components/MobileHeader";
import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts"; import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts";
import { getTimeStampByDate } from "@/helpers/datetime";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { useFilterStore } from "@/store/module"; import { useFilterStore } from "@/store/module";
import { useMemoV1Store } from "@/store/v1"; import { useMemoV1Store } from "@/store/v1";
@ -15,27 +16,37 @@ const Explore = () => {
const user = useCurrentUser(); const user = useCurrentUser();
const filterStore = useFilterStore(); const filterStore = useFilterStore();
const memoStore = useMemoV1Store(); const memoStore = useMemoV1Store();
const [memos, setMemos] = useState<Memo[]>([]);
const [isComplete, setIsComplete] = useState(false); const [isComplete, setIsComplete] = useState(false);
const [isRequesting, setIsRequesting] = useState(false); const [isRequesting, setIsRequesting] = useState(false);
const memosRef = useRef<Memo[]>([]);
const { tag: tagQuery, text: textQuery } = filterStore.state; const { tag: tagQuery, text: textQuery } = filterStore.state;
const sortedMemos = memosRef.current.sort((a, b) => getTimeStampByDate(b.displayTime) - getTimeStampByDate(a.displayTime));
useEffect(() => { useEffect(() => {
memosRef.current = [];
fetchMemos(); fetchMemos();
}, [tagQuery, textQuery]); }, [tagQuery, textQuery]);
const fetchMemos = async () => { const fetchMemos = async () => {
const filters = [`row_status == "NORMAL"`, `visibilities == [${user ? "'PUBLIC', 'PROTECTED'" : "'PUBLIC'"}]`]; const filters = [`row_status == "NORMAL"`, `visibilities == [${user ? "'PUBLIC', 'PROTECTED'" : "'PUBLIC'"}]`];
if (tagQuery) filters.push(`tags == "${tagQuery}"`); const contentSearch: string[] = [];
if (textQuery) filters.push(`content_search == "${textQuery}"`); if (tagQuery) {
contentSearch.push(`"#${tagQuery}"`);
}
if (textQuery) {
contentSearch.push(`"${textQuery}"`);
}
if (contentSearch.length > 0) {
filters.push(`content_search == [${contentSearch.join(", ")}]`);
}
setIsRequesting(true); setIsRequesting(true);
const data = await memoStore.fetchMemos({ const data = await memoStore.fetchMemos({
limit: DEFAULT_MEMO_LIMIT, limit: DEFAULT_MEMO_LIMIT,
offset: memos.length, offset: memosRef.current.length,
filter: filters.join(" && "), filter: filters.join(" && "),
}); });
setIsRequesting(false); setIsRequesting(false);
setMemos([...memos, ...data]); memosRef.current = [...memosRef.current, ...data];
setIsComplete(data.length < DEFAULT_MEMO_LIMIT); setIsComplete(data.length < DEFAULT_MEMO_LIMIT);
}; };
@ -44,7 +55,7 @@ const Explore = () => {
<MobileHeader /> <MobileHeader />
<div className="relative w-full h-auto flex flex-col justify-start items-start px-4 sm:px-6"> <div className="relative w-full h-auto flex flex-col justify-start items-start px-4 sm:px-6">
<MemoFilter /> <MemoFilter />
{memos.map((memo) => ( {sortedMemos.map((memo) => (
<MemoViewV1 key={memo.id} memo={memo} lazyRendering showCreator showParent /> <MemoViewV1 key={memo.id} memo={memo} lazyRendering showCreator showParent />
))} ))}
@ -54,7 +65,7 @@ const Explore = () => {
</div> </div>
)} )}
{isComplete ? ( {isComplete ? (
memos.length === 0 && ( sortedMemos.length === 0 && (
<div className="w-full mt-12 mb-8 flex flex-col justify-center items-center italic"> <div className="w-full mt-12 mb-8 flex flex-col justify-center items-center italic">
<Empty /> <Empty />
<p className="mt-2 text-gray-600 dark:text-gray-400">{t("message.no-data")}</p> <p className="mt-2 text-gray-600 dark:text-gray-400">{t("message.no-data")}</p>

@ -1,4 +1,4 @@
import { useEffect, useState } from "react"; import { useEffect, useRef, useState } from "react";
import Empty from "@/components/Empty"; import Empty from "@/components/Empty";
import HomeSidebar from "@/components/HomeSidebar"; import HomeSidebar from "@/components/HomeSidebar";
import HomeSidebarDrawer from "@/components/HomeSidebarDrawer"; import HomeSidebarDrawer from "@/components/HomeSidebarDrawer";
@ -7,6 +7,7 @@ import MemoFilter from "@/components/MemoFilter";
import MemoViewV1 from "@/components/MemoViewV1"; import MemoViewV1 from "@/components/MemoViewV1";
import MobileHeader from "@/components/MobileHeader"; import MobileHeader from "@/components/MobileHeader";
import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts"; import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts";
import { getTimeStampByDate } from "@/helpers/datetime";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import useResponsiveWidth from "@/hooks/useResponsiveWidth"; import useResponsiveWidth from "@/hooks/useResponsiveWidth";
import { useFilterStore } from "@/store/module"; import { useFilterStore } from "@/store/module";
@ -20,33 +21,45 @@ const Home = () => {
const user = useCurrentUser(); const user = useCurrentUser();
const filterStore = useFilterStore(); const filterStore = useFilterStore();
const memoStore = useMemoV1Store(); const memoStore = useMemoV1Store();
const [memos, setMemos] = useState<Memo[]>([]);
const [isComplete, setIsComplete] = useState(false); const [isComplete, setIsComplete] = useState(false);
const [isRequesting, setIsRequesting] = useState(false); const [isRequesting, setIsRequesting] = useState(false);
const memosRef = useRef<Memo[]>([]);
const { tag: tagQuery, text: textQuery } = filterStore.state; const { tag: tagQuery, text: textQuery } = filterStore.state;
const sortedMemos = memosRef.current
.sort((a, b) => getTimeStampByDate(b.displayTime) - getTimeStampByDate(a.displayTime))
.sort((a, b) => Number(b.pinned) - Number(a.pinned));
useEffect(() => { useEffect(() => {
memosRef.current = [];
fetchMemos(); fetchMemos();
}, [tagQuery, textQuery]); }, [tagQuery, textQuery]);
const fetchMemos = async () => { const fetchMemos = async () => {
const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`]; const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`];
if (tagQuery) filters.push(`tags == "${tagQuery}"`); const contentSearch: string[] = [];
if (textQuery) filters.push(`content_search == "${textQuery}"`); if (tagQuery) {
contentSearch.push(`"#${tagQuery}"`);
}
if (textQuery) {
contentSearch.push(`"${textQuery}"`);
}
if (contentSearch.length > 0) {
filters.push(`content_search == [${contentSearch.join(", ")}]`);
}
setIsRequesting(true); setIsRequesting(true);
const data = await memoStore.fetchMemos({ const data = await memoStore.fetchMemos({
limit: DEFAULT_MEMO_LIMIT, limit: DEFAULT_MEMO_LIMIT,
offset: memos.length, offset: memosRef.current.length,
filter: filters.join(" && "), filter: filters.join(" && "),
}); });
setIsRequesting(false); setIsRequesting(false);
setMemos([...memos, ...data]); memosRef.current = [...memosRef.current, ...data];
setIsComplete(data.length < DEFAULT_MEMO_LIMIT); setIsComplete(data.length < DEFAULT_MEMO_LIMIT);
}; };
const handleMemoCreated = async (memoId: number) => { const handleMemoCreated = async (memoId: number) => {
const memo = await memoStore.getOrFetchMemoById(memoId); const memo = await memoStore.getOrFetchMemoById(memoId);
setMemos([memo, ...memos]); memosRef.current = [memo, ...memosRef.current];
}; };
return ( return (
@ -57,7 +70,7 @@ const Home = () => {
<MemoEditorV1 className="mb-2" cacheKey="home-memo-editor" onConfirm={handleMemoCreated} /> <MemoEditorV1 className="mb-2" cacheKey="home-memo-editor" onConfirm={handleMemoCreated} />
<div className="flex flex-col justify-start items-start w-full max-w-full overflow-y-scroll pb-28 hide-scrollbar"> <div className="flex flex-col justify-start items-start w-full max-w-full overflow-y-scroll pb-28 hide-scrollbar">
<MemoFilter /> <MemoFilter />
{memos.map((memo) => ( {sortedMemos.map((memo) => (
<MemoViewV1 key={memo.id} memo={memo} lazyRendering showVisibility showPinnedStyle showParent /> <MemoViewV1 key={memo.id} memo={memo} lazyRendering showVisibility showPinnedStyle showParent />
))} ))}
{isRequesting && ( {isRequesting && (
@ -66,7 +79,7 @@ const Home = () => {
</div> </div>
)} )}
{isComplete ? ( {isComplete ? (
memos.length === 0 && ( sortedMemos.length === 0 && (
<div className="w-full mt-12 mb-8 flex flex-col justify-center items-center italic"> <div className="w-full mt-12 mb-8 flex flex-col justify-center items-center italic">
<Empty /> <Empty />
<p className="mt-2 text-gray-600 dark:text-gray-400">{t("message.no-data")}</p> <p className="mt-2 text-gray-600 dark:text-gray-400">{t("message.no-data")}</p>

Loading…
Cancel
Save