diff --git a/server/router/api/v1/memo_service.go b/server/router/api/v1/memo_service.go index 456225ab..24b9ff75 100644 --- a/server/router/api/v1/memo_service.go +++ b/server/router/api/v1/memo_service.go @@ -711,7 +711,7 @@ func (s *APIV1Service) RenameMemoTag(ctx context.Context, request *v1pb.RenameMe memoFind := &store.FindMemo{ CreatorID: &user.ID, - PayloadFind: &store.FindMemoPayload{Tag: &request.OldTag}, + PayloadFind: &store.FindMemoPayload{TagSearch: []string{request.OldTag}}, ExcludeComments: true, } if (request.Parent) != "memos/-" { @@ -765,7 +765,7 @@ func (s *APIV1Service) DeleteMemoTag(ctx context.Context, request *v1pb.DeleteMe memoFind := &store.FindMemo{ CreatorID: &user.ID, - PayloadFind: &store.FindMemoPayload{Tag: &request.Tag}, + PayloadFind: &store.FindMemoPayload{TagSearch: []string{request.Tag}}, ExcludeContent: true, ExcludeComments: true, } @@ -928,11 +928,11 @@ func (s *APIV1Service) buildMemoFindWithFilter(ctx context.Context, find *store. if len(filter.Visibilities) > 0 { find.VisibilityList = filter.Visibilities } - if filter.Tag != nil { + if filter.TagSearch != nil { if find.PayloadFind == nil { find.PayloadFind = &store.FindMemoPayload{} } - find.PayloadFind.Tag = filter.Tag + find.PayloadFind.TagSearch = filter.TagSearch } if filter.OrderByPinned { find.OrderByPinned = filter.OrderByPinned @@ -1039,7 +1039,7 @@ func (s *APIV1Service) getContentLengthLimit(ctx context.Context) (int, error) { var MemoFilterCELAttributes = []cel.EnvOption{ cel.Variable("content_search", cel.ListType(cel.StringType)), cel.Variable("visibilities", cel.ListType(cel.StringType)), - cel.Variable("tag", cel.StringType), + cel.Variable("tag_search", cel.ListType(cel.StringType)), cel.Variable("order_by_pinned", cel.BoolType), cel.Variable("display_time_before", cel.IntType), cel.Variable("display_time_after", cel.IntType), @@ -1058,7 +1058,7 @@ var MemoFilterCELAttributes = []cel.EnvOption{ type MemoFilter struct { ContentSearch []string Visibilities []store.Visibility - Tag *string + TagSearch []string OrderByPinned bool DisplayTimeBefore *int64 DisplayTimeAfter *int64 @@ -1110,9 +1110,13 @@ func findMemoField(callExpr *expr.Expr_Call, filter *MemoFilter) { visibilities = append(visibilities, store.Visibility(value)) } filter.Visibilities = visibilities - } else if idExpr.Name == "tag" { - tag := callExpr.Args[1].GetConstExpr().GetStringValue() - filter.Tag = &tag + } else if idExpr.Name == "tag_search" { + tagSearch := []string{} + for _, expr := range callExpr.Args[1].GetListExpr().GetElements() { + value := expr.GetConstExpr().GetStringValue() + tagSearch = append(tagSearch, value) + } + filter.TagSearch = tagSearch } else if idExpr.Name == "order_by_pinned" { value := callExpr.Args[1].GetConstExpr().GetBoolValue() filter.OrderByPinned = value diff --git a/store/db/mysql/memo.go b/store/db/mysql/memo.go index 76eb8cc0..e45f551c 100644 --- a/store/db/mysql/memo.go +++ b/store/db/mysql/memo.go @@ -90,8 +90,10 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo if v.Raw != nil { where, args = append(where, "`memo`.`payload` = ?"), append(args, *v.Raw) } - if v.Tag != nil { - where, args = append(where, "JSON_CONTAINS(JSON_EXTRACT(`memo`.`payload`, '$.property.tags'), ?)"), append(args, fmt.Sprintf(`["%s"]`, *v.Tag)) + if len(v.TagSearch) != 0 { + for _, tag := range v.TagSearch { + where, args = append(where, "JSON_CONTAINS(JSON_EXTRACT(`memo`.`payload`, '$.property.tags'), ?)"), append(args, fmt.Sprintf(`%%"%s"%%`, tag)) + } } if v.HasLink { where = append(where, "JSON_EXTRACT(`memo`.`payload`, '$.property.hasLink') IS TRUE") diff --git a/store/db/postgres/memo.go b/store/db/postgres/memo.go index c1df397f..49e9e621 100644 --- a/store/db/postgres/memo.go +++ b/store/db/postgres/memo.go @@ -81,8 +81,10 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo if v.Raw != nil { where, args = append(where, "memo.payload = "+placeholder(len(args)+1)), append(args, *v.Raw) } - if v.Tag != nil { - where, args = append(where, "memo.payload->'property'->'tags' @> "+placeholder(len(args)+1)), append(args, fmt.Sprintf(`["%s"]`, *v.Tag)) + if len(v.TagSearch) != 0 { + for _, tag := range v.TagSearch { + where, args = append(where, "memo.payload->'property'->'tags' @> "+placeholder(len(args)+1)), append(args, fmt.Sprintf(`["%s"]`, tag)) + } } if v.HasLink { where = append(where, "(memo.payload->'property'->>'hasLink')::BOOLEAN IS TRUE") diff --git a/store/db/sqlite/memo.go b/store/db/sqlite/memo.go index fe1d1e70..6f6bd713 100644 --- a/store/db/sqlite/memo.go +++ b/store/db/sqlite/memo.go @@ -82,8 +82,10 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo if v.Raw != nil { where, args = append(where, "`memo`.`payload` = ?"), append(args, *v.Raw) } - if v.Tag != nil { - where, args = append(where, "JSON_EXTRACT(`memo`.`payload`, '$.property.tags') LIKE ?"), append(args, fmt.Sprintf(`%%"%s"%%`, *v.Tag)) + if len(v.TagSearch) != 0 { + for _, tag := range v.TagSearch { + where, args = append(where, "JSON_EXTRACT(`memo`.`payload`, '$.property.tags') LIKE ?"), append(args, fmt.Sprintf(`%%"%s"%%`, tag)) + } } if v.HasLink { where = append(where, "JSON_EXTRACT(`memo`.`payload`, '$.property.hasLink') IS TRUE") diff --git a/store/memo.go b/store/memo.go index ce4f4e25..d4aa6d62 100644 --- a/store/memo.go +++ b/store/memo.go @@ -84,7 +84,7 @@ type FindMemo struct { type FindMemoPayload struct { Raw *string - Tag *string + TagSearch []string HasLink bool HasTaskList bool HasCode bool diff --git a/web/src/components/HomeSidebar/TagsSection.tsx b/web/src/components/HomeSidebar/TagsSection.tsx index ef2d5695..2e64dbbd 100644 --- a/web/src/components/HomeSidebar/TagsSection.tsx +++ b/web/src/components/HomeSidebar/TagsSection.tsx @@ -36,12 +36,12 @@ const TagsSection = (props: Props) => { }; const handleTagClick = (tag: string) => { - const isActive = memoFilterStore.getFiltersByFactor("tag").some((filter) => filter.value === tag); + const isActive = memoFilterStore.getFiltersByFactor("tagSearch").some((filter) => filter.value === tag); if (isActive) { - memoFilterStore.removeFilter((f) => f.factor === "tag" && f.value === tag); + memoFilterStore.removeFilter((f) => f.factor === "tagSearch" && f.value === tag); } else { memoFilterStore.addFilter({ - factor: "tag", + factor: "tagSearch", value: tag, }); } diff --git a/web/src/components/MemoContent/Tag.tsx b/web/src/components/MemoContent/Tag.tsx index fa5e50fd..ace025ca 100644 --- a/web/src/components/MemoContent/Tag.tsx +++ b/web/src/components/MemoContent/Tag.tsx @@ -16,12 +16,12 @@ const Tag: React.FC = ({ content }: Props) => { return; } - const isActive = memoFilterStore.getFiltersByFactor("tag").some((filter) => filter.value === content); + const isActive = memoFilterStore.getFiltersByFactor("tagSearch").some((filter) => filter.value === content); if (isActive) { - memoFilterStore.removeFilter((f) => f.factor === "tag" && f.value === content); + memoFilterStore.removeFilter((f) => f.factor === "tagSearch" && f.value === content); } else { memoFilterStore.addFilter({ - factor: "tag", + factor: "tagSearch", value: content, }); } diff --git a/web/src/components/MemoFilters.tsx b/web/src/components/MemoFilters.tsx index c6503480..70fd6f39 100644 --- a/web/src/components/MemoFilters.tsx +++ b/web/src/components/MemoFilters.tsx @@ -47,7 +47,7 @@ const MemoFilters = () => { const FactorIcon = ({ factor, className }: { factor: FilterFactor; className?: string }) => { const iconMap = { - tag: , + tagSearch: , visibility: , contentSearch: , displayTime: , diff --git a/web/src/components/TagTree.tsx b/web/src/components/TagTree.tsx index 573d20f0..d9ae97e6 100644 --- a/web/src/components/TagTree.tsx +++ b/web/src/components/TagTree.tsx @@ -78,17 +78,17 @@ interface TagItemContainerProps { const TagItemContainer: React.FC = (props: TagItemContainerProps) => { const { tag } = props; const memoFilterStore = useMemoFilterStore(); - const tagFilters = memoFilterStore.getFiltersByFactor("tag"); + const tagFilters = memoFilterStore.getFiltersByFactor("tagSearch"); const isActive = tagFilters.some((f) => f.value === tag.text); const hasSubTags = tag.subTags.length > 0; const [showSubTags, toggleSubTags] = useToggle(false); const handleTagClick = () => { if (isActive) { - memoFilterStore.removeFilter((f) => f.factor === "tag" && f.value === tag.text); + memoFilterStore.removeFilter((f) => f.factor === "tagSearch" && f.value === tag.text); } else { memoFilterStore.addFilter({ - factor: "tag", + factor: "tagSearch", value: tag.text, }); } diff --git a/web/src/pages/Archived.tsx b/web/src/pages/Archived.tsx index 417d1937..649f6f9b 100644 --- a/web/src/pages/Archived.tsx +++ b/web/src/pages/Archived.tsx @@ -37,16 +37,20 @@ const Archived = () => { setIsRequesting(true); const filters = [`creator == "${user.name}"`, `row_status == "ARCHIVED"`]; const contentSearch: string[] = []; + const tagSearch: string[] = []; for (const filter of memoFilterStore.filters) { if (filter.factor === "contentSearch") { contentSearch.push(`"${filter.value}"`); - } else if (filter.factor === "tag") { - filters.push(`tag == "${filter.value}"`); + } else if (filter.factor === "tagSearch") { + tagSearch.push(`"${filter.value}"`); } } if (contentSearch.length > 0) { filters.push(`content_search == [${contentSearch.join(", ")}]`); } + if (tagSearch.length > 0) { + filters.push(`tag_search == [${tagSearch.join(", ")}]`); + } const response = await memoStore.fetchMemos({ pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE, filter: filters.join(" && "), diff --git a/web/src/pages/Explore.tsx b/web/src/pages/Explore.tsx index 202392ae..2e52c5ff 100644 --- a/web/src/pages/Explore.tsx +++ b/web/src/pages/Explore.tsx @@ -34,16 +34,20 @@ const Explore = () => { setIsRequesting(true); const filters = [`row_status == "NORMAL"`, `visibilities == [${user ? "'PUBLIC', 'PROTECTED'" : "'PUBLIC'"}]`]; const contentSearch: string[] = []; + const tagSearch: string[] = []; for (const filter of memoFilterStore.filters) { if (filter.factor === "contentSearch") { contentSearch.push(`"${filter.value}"`); - } else if (filter.factor === "tag") { - filters.push(`tag == "${filter.value}"`); + } else if (filter.factor === "tagSearch") { + tagSearch.push(`"${filter.value}"`); } } if (contentSearch.length > 0) { filters.push(`content_search == [${contentSearch.join(", ")}]`); } + if (tagSearch.length > 0) { + filters.push(`tag_search == [${tagSearch.join(", ")}]`); + } const response = await memoStore.fetchMemos({ pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE, filter: filters.join(" && "), diff --git a/web/src/pages/Home.tsx b/web/src/pages/Home.tsx index a8253c2f..28496e2b 100644 --- a/web/src/pages/Home.tsx +++ b/web/src/pages/Home.tsx @@ -39,11 +39,12 @@ const Home = () => { setIsRequesting(true); const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`, `order_by_pinned == true`]; const contentSearch: string[] = []; + const tagSearch: string[] = []; for (const filter of memoFilterStore.filters) { if (filter.factor === "contentSearch") { contentSearch.push(`"${filter.value}"`); - } else if (filter.factor === "tag") { - filters.push(`tag == "${filter.value}"`); + } else if (filter.factor === "tagSearch") { + tagSearch.push(`"${filter.value}"`); } else if (filter.factor === "property.hasLink") { filters.push(`has_link == true`); } else if (filter.factor === "property.hasTaskList") { @@ -55,6 +56,9 @@ const Home = () => { if (contentSearch.length > 0) { filters.push(`content_search == [${contentSearch.join(", ")}]`); } + if (tagSearch.length > 0) { + filters.push(`tag_search == [${tagSearch.join(", ")}]`); + } const response = await memoStore.fetchMemos({ pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE, filter: filters.join(" && "), diff --git a/web/src/pages/UserProfile.tsx b/web/src/pages/UserProfile.tsx index e8a4ce41..78033dab 100644 --- a/web/src/pages/UserProfile.tsx +++ b/web/src/pages/UserProfile.tsx @@ -70,16 +70,20 @@ const UserProfile = () => { setIsRequesting(true); const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`, `order_by_pinned == true`]; const contentSearch: string[] = []; + const tagSearch: string[] = []; for (const filter of memoFilterStore.filters) { if (filter.factor === "contentSearch") { contentSearch.push(`"${filter.value}"`); - } else if (filter.factor === "tag") { - filters.push(`tag == "${filter.value}"`); + } else if (filter.factor === "tagSearch") { + tagSearch.push(`"${filter.value}"`); } } if (contentSearch.length > 0) { filters.push(`content_search == [${contentSearch.join(", ")}]`); } + if (tagSearch.length > 0) { + filters.push(`tag_search == [${tagSearch.join(", ")}]`); + } const response = await memoStore.fetchMemos({ pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE, filter: filters.join(" && "), diff --git a/web/src/store/v1/memoFilter.ts b/web/src/store/v1/memoFilter.ts index e8b2c019..dbf30705 100644 --- a/web/src/store/v1/memoFilter.ts +++ b/web/src/store/v1/memoFilter.ts @@ -3,7 +3,7 @@ import { create } from "zustand"; import { combine } from "zustand/middleware"; export type FilterFactor = - | "tag" + | "tagSearch" | "visibility" | "contentSearch" | "displayTime"