feat: implement search multi tags

This commit is contained in:
Steven
2024-07-26 08:40:40 +08:00
parent c6a09d9353
commit edc3b578d6
14 changed files with 61 additions and 35 deletions

View File

@ -711,7 +711,7 @@ func (s *APIV1Service) RenameMemoTag(ctx context.Context, request *v1pb.RenameMe
memoFind := &store.FindMemo{ memoFind := &store.FindMemo{
CreatorID: &user.ID, CreatorID: &user.ID,
PayloadFind: &store.FindMemoPayload{Tag: &request.OldTag}, PayloadFind: &store.FindMemoPayload{TagSearch: []string{request.OldTag}},
ExcludeComments: true, ExcludeComments: true,
} }
if (request.Parent) != "memos/-" { if (request.Parent) != "memos/-" {
@ -765,7 +765,7 @@ func (s *APIV1Service) DeleteMemoTag(ctx context.Context, request *v1pb.DeleteMe
memoFind := &store.FindMemo{ memoFind := &store.FindMemo{
CreatorID: &user.ID, CreatorID: &user.ID,
PayloadFind: &store.FindMemoPayload{Tag: &request.Tag}, PayloadFind: &store.FindMemoPayload{TagSearch: []string{request.Tag}},
ExcludeContent: true, ExcludeContent: true,
ExcludeComments: true, ExcludeComments: true,
} }
@ -928,11 +928,11 @@ func (s *APIV1Service) buildMemoFindWithFilter(ctx context.Context, find *store.
if len(filter.Visibilities) > 0 { if len(filter.Visibilities) > 0 {
find.VisibilityList = filter.Visibilities find.VisibilityList = filter.Visibilities
} }
if filter.Tag != nil { if filter.TagSearch != nil {
if find.PayloadFind == nil { if find.PayloadFind == nil {
find.PayloadFind = &store.FindMemoPayload{} find.PayloadFind = &store.FindMemoPayload{}
} }
find.PayloadFind.Tag = filter.Tag find.PayloadFind.TagSearch = filter.TagSearch
} }
if filter.OrderByPinned { if filter.OrderByPinned {
find.OrderByPinned = filter.OrderByPinned find.OrderByPinned = filter.OrderByPinned
@ -1039,7 +1039,7 @@ func (s *APIV1Service) getContentLengthLimit(ctx context.Context) (int, error) {
var MemoFilterCELAttributes = []cel.EnvOption{ var MemoFilterCELAttributes = []cel.EnvOption{
cel.Variable("content_search", cel.ListType(cel.StringType)), cel.Variable("content_search", cel.ListType(cel.StringType)),
cel.Variable("visibilities", 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("order_by_pinned", cel.BoolType),
cel.Variable("display_time_before", cel.IntType), cel.Variable("display_time_before", cel.IntType),
cel.Variable("display_time_after", cel.IntType), cel.Variable("display_time_after", cel.IntType),
@ -1058,7 +1058,7 @@ var MemoFilterCELAttributes = []cel.EnvOption{
type MemoFilter struct { type MemoFilter struct {
ContentSearch []string ContentSearch []string
Visibilities []store.Visibility Visibilities []store.Visibility
Tag *string TagSearch []string
OrderByPinned bool OrderByPinned bool
DisplayTimeBefore *int64 DisplayTimeBefore *int64
DisplayTimeAfter *int64 DisplayTimeAfter *int64
@ -1110,9 +1110,13 @@ func findMemoField(callExpr *expr.Expr_Call, filter *MemoFilter) {
visibilities = append(visibilities, store.Visibility(value)) visibilities = append(visibilities, store.Visibility(value))
} }
filter.Visibilities = visibilities filter.Visibilities = visibilities
} else if idExpr.Name == "tag" { } else if idExpr.Name == "tag_search" {
tag := callExpr.Args[1].GetConstExpr().GetStringValue() tagSearch := []string{}
filter.Tag = &tag 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" { } else if idExpr.Name == "order_by_pinned" {
value := callExpr.Args[1].GetConstExpr().GetBoolValue() value := callExpr.Args[1].GetConstExpr().GetBoolValue()
filter.OrderByPinned = value filter.OrderByPinned = value

View File

@ -90,8 +90,10 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
if v.Raw != nil { if v.Raw != nil {
where, args = append(where, "`memo`.`payload` = ?"), append(args, *v.Raw) where, args = append(where, "`memo`.`payload` = ?"), append(args, *v.Raw)
} }
if v.Tag != nil { if len(v.TagSearch) != 0 {
where, args = append(where, "JSON_CONTAINS(JSON_EXTRACT(`memo`.`payload`, '$.property.tags'), ?)"), append(args, fmt.Sprintf(`["%s"]`, *v.Tag)) 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 { if v.HasLink {
where = append(where, "JSON_EXTRACT(`memo`.`payload`, '$.property.hasLink') IS TRUE") where = append(where, "JSON_EXTRACT(`memo`.`payload`, '$.property.hasLink') IS TRUE")

View File

@ -81,8 +81,10 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
if v.Raw != nil { if v.Raw != nil {
where, args = append(where, "memo.payload = "+placeholder(len(args)+1)), append(args, *v.Raw) where, args = append(where, "memo.payload = "+placeholder(len(args)+1)), append(args, *v.Raw)
} }
if v.Tag != nil { if len(v.TagSearch) != 0 {
where, args = append(where, "memo.payload->'property'->'tags' @> "+placeholder(len(args)+1)), append(args, fmt.Sprintf(`["%s"]`, *v.Tag)) 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 { if v.HasLink {
where = append(where, "(memo.payload->'property'->>'hasLink')::BOOLEAN IS TRUE") where = append(where, "(memo.payload->'property'->>'hasLink')::BOOLEAN IS TRUE")

View File

@ -82,8 +82,10 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
if v.Raw != nil { if v.Raw != nil {
where, args = append(where, "`memo`.`payload` = ?"), append(args, *v.Raw) where, args = append(where, "`memo`.`payload` = ?"), append(args, *v.Raw)
} }
if v.Tag != nil { if len(v.TagSearch) != 0 {
where, args = append(where, "JSON_EXTRACT(`memo`.`payload`, '$.property.tags') LIKE ?"), append(args, fmt.Sprintf(`%%"%s"%%`, *v.Tag)) 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 { if v.HasLink {
where = append(where, "JSON_EXTRACT(`memo`.`payload`, '$.property.hasLink') IS TRUE") where = append(where, "JSON_EXTRACT(`memo`.`payload`, '$.property.hasLink') IS TRUE")

View File

@ -84,7 +84,7 @@ type FindMemo struct {
type FindMemoPayload struct { type FindMemoPayload struct {
Raw *string Raw *string
Tag *string TagSearch []string
HasLink bool HasLink bool
HasTaskList bool HasTaskList bool
HasCode bool HasCode bool

View File

@ -36,12 +36,12 @@ const TagsSection = (props: Props) => {
}; };
const handleTagClick = (tag: string) => { 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) { if (isActive) {
memoFilterStore.removeFilter((f) => f.factor === "tag" && f.value === tag); memoFilterStore.removeFilter((f) => f.factor === "tagSearch" && f.value === tag);
} else { } else {
memoFilterStore.addFilter({ memoFilterStore.addFilter({
factor: "tag", factor: "tagSearch",
value: tag, value: tag,
}); });
} }

View File

@ -16,12 +16,12 @@ const Tag: React.FC<Props> = ({ content }: Props) => {
return; return;
} }
const isActive = memoFilterStore.getFiltersByFactor("tag").some((filter) => filter.value === content); const isActive = memoFilterStore.getFiltersByFactor("tagSearch").some((filter) => filter.value === content);
if (isActive) { if (isActive) {
memoFilterStore.removeFilter((f) => f.factor === "tag" && f.value === content); memoFilterStore.removeFilter((f) => f.factor === "tagSearch" && f.value === content);
} else { } else {
memoFilterStore.addFilter({ memoFilterStore.addFilter({
factor: "tag", factor: "tagSearch",
value: content, value: content,
}); });
} }

View File

@ -47,7 +47,7 @@ const MemoFilters = () => {
const FactorIcon = ({ factor, className }: { factor: FilterFactor; className?: string }) => { const FactorIcon = ({ factor, className }: { factor: FilterFactor; className?: string }) => {
const iconMap = { const iconMap = {
tag: <Icon.Tag className={className} />, tagSearch: <Icon.Tag className={className} />,
visibility: <Icon.Eye className={className} />, visibility: <Icon.Eye className={className} />,
contentSearch: <Icon.Search className={className} />, contentSearch: <Icon.Search className={className} />,
displayTime: <Icon.Calendar className={className} />, displayTime: <Icon.Calendar className={className} />,

View File

@ -78,17 +78,17 @@ interface TagItemContainerProps {
const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContainerProps) => { const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContainerProps) => {
const { tag } = props; const { tag } = props;
const memoFilterStore = useMemoFilterStore(); const memoFilterStore = useMemoFilterStore();
const tagFilters = memoFilterStore.getFiltersByFactor("tag"); const tagFilters = memoFilterStore.getFiltersByFactor("tagSearch");
const isActive = tagFilters.some((f) => f.value === tag.text); const isActive = tagFilters.some((f) => f.value === tag.text);
const hasSubTags = tag.subTags.length > 0; const hasSubTags = tag.subTags.length > 0;
const [showSubTags, toggleSubTags] = useToggle(false); const [showSubTags, toggleSubTags] = useToggle(false);
const handleTagClick = () => { const handleTagClick = () => {
if (isActive) { if (isActive) {
memoFilterStore.removeFilter((f) => f.factor === "tag" && f.value === tag.text); memoFilterStore.removeFilter((f) => f.factor === "tagSearch" && f.value === tag.text);
} else { } else {
memoFilterStore.addFilter({ memoFilterStore.addFilter({
factor: "tag", factor: "tagSearch",
value: tag.text, value: tag.text,
}); });
} }

View File

@ -37,16 +37,20 @@ const Archived = () => {
setIsRequesting(true); setIsRequesting(true);
const filters = [`creator == "${user.name}"`, `row_status == "ARCHIVED"`]; const filters = [`creator == "${user.name}"`, `row_status == "ARCHIVED"`];
const contentSearch: string[] = []; const contentSearch: string[] = [];
const tagSearch: string[] = [];
for (const filter of memoFilterStore.filters) { for (const filter of memoFilterStore.filters) {
if (filter.factor === "contentSearch") { if (filter.factor === "contentSearch") {
contentSearch.push(`"${filter.value}"`); contentSearch.push(`"${filter.value}"`);
} else if (filter.factor === "tag") { } else if (filter.factor === "tagSearch") {
filters.push(`tag == "${filter.value}"`); tagSearch.push(`"${filter.value}"`);
} }
} }
if (contentSearch.length > 0) { if (contentSearch.length > 0) {
filters.push(`content_search == [${contentSearch.join(", ")}]`); filters.push(`content_search == [${contentSearch.join(", ")}]`);
} }
if (tagSearch.length > 0) {
filters.push(`tag_search == [${tagSearch.join(", ")}]`);
}
const response = await memoStore.fetchMemos({ const response = await memoStore.fetchMemos({
pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE, pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE,
filter: filters.join(" && "), filter: filters.join(" && "),

View File

@ -34,16 +34,20 @@ const Explore = () => {
setIsRequesting(true); setIsRequesting(true);
const filters = [`row_status == "NORMAL"`, `visibilities == [${user ? "'PUBLIC', 'PROTECTED'" : "'PUBLIC'"}]`]; const filters = [`row_status == "NORMAL"`, `visibilities == [${user ? "'PUBLIC', 'PROTECTED'" : "'PUBLIC'"}]`];
const contentSearch: string[] = []; const contentSearch: string[] = [];
const tagSearch: string[] = [];
for (const filter of memoFilterStore.filters) { for (const filter of memoFilterStore.filters) {
if (filter.factor === "contentSearch") { if (filter.factor === "contentSearch") {
contentSearch.push(`"${filter.value}"`); contentSearch.push(`"${filter.value}"`);
} else if (filter.factor === "tag") { } else if (filter.factor === "tagSearch") {
filters.push(`tag == "${filter.value}"`); tagSearch.push(`"${filter.value}"`);
} }
} }
if (contentSearch.length > 0) { if (contentSearch.length > 0) {
filters.push(`content_search == [${contentSearch.join(", ")}]`); filters.push(`content_search == [${contentSearch.join(", ")}]`);
} }
if (tagSearch.length > 0) {
filters.push(`tag_search == [${tagSearch.join(", ")}]`);
}
const response = await memoStore.fetchMemos({ const response = await memoStore.fetchMemos({
pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE, pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE,
filter: filters.join(" && "), filter: filters.join(" && "),

View File

@ -39,11 +39,12 @@ const Home = () => {
setIsRequesting(true); setIsRequesting(true);
const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`, `order_by_pinned == true`]; const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`, `order_by_pinned == true`];
const contentSearch: string[] = []; const contentSearch: string[] = [];
const tagSearch: string[] = [];
for (const filter of memoFilterStore.filters) { for (const filter of memoFilterStore.filters) {
if (filter.factor === "contentSearch") { if (filter.factor === "contentSearch") {
contentSearch.push(`"${filter.value}"`); contentSearch.push(`"${filter.value}"`);
} else if (filter.factor === "tag") { } else if (filter.factor === "tagSearch") {
filters.push(`tag == "${filter.value}"`); tagSearch.push(`"${filter.value}"`);
} else if (filter.factor === "property.hasLink") { } else if (filter.factor === "property.hasLink") {
filters.push(`has_link == true`); filters.push(`has_link == true`);
} else if (filter.factor === "property.hasTaskList") { } else if (filter.factor === "property.hasTaskList") {
@ -55,6 +56,9 @@ const Home = () => {
if (contentSearch.length > 0) { if (contentSearch.length > 0) {
filters.push(`content_search == [${contentSearch.join(", ")}]`); filters.push(`content_search == [${contentSearch.join(", ")}]`);
} }
if (tagSearch.length > 0) {
filters.push(`tag_search == [${tagSearch.join(", ")}]`);
}
const response = await memoStore.fetchMemos({ const response = await memoStore.fetchMemos({
pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE, pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE,
filter: filters.join(" && "), filter: filters.join(" && "),

View File

@ -70,16 +70,20 @@ const UserProfile = () => {
setIsRequesting(true); setIsRequesting(true);
const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`, `order_by_pinned == true`]; const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`, `order_by_pinned == true`];
const contentSearch: string[] = []; const contentSearch: string[] = [];
const tagSearch: string[] = [];
for (const filter of memoFilterStore.filters) { for (const filter of memoFilterStore.filters) {
if (filter.factor === "contentSearch") { if (filter.factor === "contentSearch") {
contentSearch.push(`"${filter.value}"`); contentSearch.push(`"${filter.value}"`);
} else if (filter.factor === "tag") { } else if (filter.factor === "tagSearch") {
filters.push(`tag == "${filter.value}"`); tagSearch.push(`"${filter.value}"`);
} }
} }
if (contentSearch.length > 0) { if (contentSearch.length > 0) {
filters.push(`content_search == [${contentSearch.join(", ")}]`); filters.push(`content_search == [${contentSearch.join(", ")}]`);
} }
if (tagSearch.length > 0) {
filters.push(`tag_search == [${tagSearch.join(", ")}]`);
}
const response = await memoStore.fetchMemos({ const response = await memoStore.fetchMemos({
pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE, pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE,
filter: filters.join(" && "), filter: filters.join(" && "),

View File

@ -3,7 +3,7 @@ import { create } from "zustand";
import { combine } from "zustand/middleware"; import { combine } from "zustand/middleware";
export type FilterFactor = export type FilterFactor =
| "tag" | "tagSearch"
| "visibility" | "visibility"
| "contentSearch" | "contentSearch"
| "displayTime" | "displayTime"