Commit 9265b8e2 authored by Steven's avatar Steven

chore: update tags filter

parent 2317204c
......@@ -1514,6 +1514,13 @@ paths:
required: true
type: string
pattern: memos/[^/]+
- name: filter
description: |-
Filter is used to filter memos.
Format: "creator == users/{uid} && visibilities == ['PUBLIC', 'PROTECTED']"
in: query
required: false
type: string
tags:
- MemoService
/api/v1/{parent}/tags/{tag}:
......
......@@ -264,6 +264,10 @@ message ListMemoTagsRequest {
// The parent, who owns the tags.
// Format: memos/{id}. Use "memos/-" to list all tags.
string parent = 1;
// Filter is used to filter memos.
// Format: "creator == users/{uid} && visibilities == ['PUBLIC', 'PROTECTED']"
string filter = 2;
}
message ListMemoTagsResponse {
......
This diff is collapsed.
......@@ -419,6 +419,10 @@ func local_request_MemoService_RebuildMemoProperty_0(ctx context.Context, marsha
}
var (
filter_MemoService_ListMemoTags_0 = &utilities.DoubleArray{Encoding: map[string]int{"parent": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
)
func request_MemoService_ListMemoTags_0(ctx context.Context, marshaler runtime.Marshaler, client MemoServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListMemoTagsRequest
var metadata runtime.ServerMetadata
......@@ -440,6 +444,13 @@ func request_MemoService_ListMemoTags_0(ctx context.Context, marshaler runtime.M
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "parent", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_MemoService_ListMemoTags_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ListMemoTags(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
......@@ -466,6 +477,13 @@ func local_request_MemoService_ListMemoTags_0(ctx context.Context, marshaler run
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "parent", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_MemoService_ListMemoTags_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ListMemoTags(ctx, &protoReq)
return msg, metadata, err
......
......@@ -16,6 +16,7 @@ var authenticationAllowlistMethods = map[string]bool{
"/memos.api.v1.UserService/SearchUsers": true,
"/memos.api.v1.MemoService/GetMemo": true,
"/memos.api.v1.MemoService/ListMemos": true,
"/memos.api.v1.MemoService/ListMemoTags": true,
"/memos.api.v1.MemoService/SearchMemos": true,
"/memos.api.v1.MarkdownService/GetLinkMetadata": true,
"/memos.api.v1.ResourceService/GetResourceBinary": true,
......
......@@ -595,14 +595,8 @@ func (s *APIV1Service) RebuildMemoProperty(ctx context.Context, request *v1pb.Re
}
func (s *APIV1Service) ListMemoTags(ctx context.Context, request *v1pb.ListMemoTagsRequest) (*v1pb.ListMemoTagsResponse, error) {
user, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get current user")
}
normalRowStatus := store.Normal
memoFind := &store.FindMemo{
CreatorID: &user.ID,
RowStatus: &normalRowStatus,
ExcludeComments: true,
// Default exclude content for performance.
......@@ -615,6 +609,9 @@ func (s *APIV1Service) ListMemoTags(ctx context.Context, request *v1pb.ListMemoT
}
memoFind.ID = &memoID
}
if err := s.buildMemoFindWithFilter(ctx, memoFind, request.Filter); err != nil {
return nil, status.Errorf(codes.Internal, "failed to build find memos with filter: %v", err)
}
memos, err := s.Store.ListMemos(ctx, memoFind)
if err != nil {
......@@ -815,7 +812,6 @@ func convertVisibilityToStore(visibility v1pb.Visibility) store.Visibility {
}
func (s *APIV1Service) buildMemoFindWithFilter(ctx context.Context, find *store.FindMemo, filter string) error {
user, _ := getCurrentUser(ctx, s.Store)
if find == nil {
find = &store.FindMemo{}
}
......@@ -894,6 +890,7 @@ func (s *APIV1Service) buildMemoFindWithFilter(ctx context.Context, find *store.
}
}
user, _ := getCurrentUser(ctx, s.Store)
// If the user is not authenticated, only public memos are visible.
if user == nil {
if filter == "" {
......
import clsx from "clsx";
import SearchBar from "@/components/SearchBar";
import UsersSection from "./UsersSection";
import TagsSection from "../HomeSidebar/TagsSection";
interface Props {
className?: string;
......@@ -15,7 +15,7 @@ const ExploreSidebar = (props: Props) => {
)}
>
<SearchBar />
<UsersSection />
<TagsSection hideTips={true} />
</aside>
);
};
......
import { Dropdown, Menu, MenuButton, MenuItem, Tooltip } from "@mui/joy";
import clsx from "clsx";
import toast from "react-hot-toast";
import { useLocation } from "react-router-dom";
import useDebounce from "react-use/lib/useDebounce";
import { memoServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
import { Routes } from "@/router";
import { useFilterStore } from "@/store/module";
import { useMemoList, useTagStore } from "@/store/v1";
import { useTranslate } from "@/utils/i18n";
......@@ -10,21 +13,34 @@ import { showCommonDialog } from "../Dialog/CommonDialog";
import Icon from "../Icon";
import showRenameTagDialog from "../RenameTagDialog";
const TagsSection = () => {
interface Props {
hideTips?: boolean;
}
const TagsSection = (props: Props) => {
const t = useTranslate();
const location = useLocation();
const user = useCurrentUser();
const tagStore = useTagStore();
const memoList = useMemoList();
const tagAmounts = Object.entries(tagStore.getState().tagAmounts)
.sort((a, b) => a[0].localeCompare(b[0]))
.sort((a, b) => b[1] - a[1]);
useDebounce(
() => {
tagStore.fetchTags();
},
300,
[memoList.size()],
);
useDebounce(() => fetchTags(), 300, [memoList.size(), location.pathname]);
const fetchTags = async () => {
const filters = [`row_status == "NORMAL"`];
if (user) {
if (location.pathname === Routes.EXPLORE) {
filters.push(`visibilities == ["PUBLIC", "PROTECTED"]`);
}
filters.push(`creator == "${user.name}"`);
} else {
filters.push(`visibilities == ["PUBLIC"]`);
}
await tagStore.fetchTags(filters.join(" && "));
};
const handleRebuildMemoTags = () => {
showCommonDialog({
......@@ -36,7 +52,7 @@ const TagsSection = () => {
await memoServiceClient.rebuildMemoProperty({
name: "memos/-",
});
await tagStore.fetchTags({ skipCache: true });
await fetchTags();
toast.success("Rebuild tags successfully");
},
});
......@@ -46,14 +62,16 @@ const TagsSection = () => {
<div className="flex flex-col justify-start items-start w-full mt-3 px-1 h-auto shrink-0 flex-nowrap hide-scrollbar">
<div className="group flex flex-row justify-start items-center w-full gap-1 mb-1">
<span className="text-sm leading-6 font-mono text-gray-400 select-none">{t("common.tags")}</span>
<div className={clsx("group-hover:block", tagAmounts.length > 0 ? "hidden" : "")}>
<Tooltip title={"Rebuild"} placement="top">
<Icon.RefreshCcw
className="text-gray-400 w-4 h-auto cursor-pointer opacity-60 hover:opacity-100"
onClick={handleRebuildMemoTags}
/>
</Tooltip>
</div>
{!props.hideTips && (
<div className={clsx("group-hover:block", tagAmounts.length > 0 ? "hidden" : "")}>
<Tooltip title={"Rebuild"} placement="top">
<Icon.RefreshCcw
className="text-gray-400 w-4 h-auto cursor-pointer opacity-60 hover:opacity-100"
onClick={handleRebuildMemoTags}
/>
</Tooltip>
</div>
)}
</div>
{tagAmounts.length > 0 ? (
<div className="w-full flex flex-row justify-start items-center relative flex-wrap gap-x-2 gap-y-1">
......@@ -62,10 +80,12 @@ const TagsSection = () => {
))}
</div>
) : (
<div className="p-2 border border-dashed dark:border-zinc-800 rounded-md flex flex-row justify-start items-start gap-1 text-gray-400 dark:text-gray-500">
<Icon.Tags />
<p className="mt-0.5 text-sm leading-snug italic">{t("tag.create-tags-guide")}</p>
</div>
!props.hideTips && (
<div className="p-2 border border-dashed dark:border-zinc-800 rounded-md flex flex-row justify-start items-start gap-1 text-gray-400 dark:text-gray-500">
<Icon.Tags />
<p className="mt-0.5 text-sm leading-snug italic">{t("tag.create-tags-guide")}</p>
</div>
)
)}
</div>
);
......@@ -101,7 +121,7 @@ const TagContainer: React.FC<TagContainerProps> = (props: TagContainerProps) =>
parent: "memos/-",
tag: tag,
});
await tagStore.fetchTags({ skipCache: true });
await tagStore.fetchTags(undefined, { skipCache: true });
toast.success(t("message.deleted-successfully"));
},
});
......
......@@ -19,7 +19,7 @@ const MemoEditorDialog: React.FC<Props> = ({
const tagStore = useTagStore();
useEffect(() => {
tagStore.fetchTags({ skipCache: false });
tagStore.fetchTags(undefined, { skipCache: false });
}, []);
const handleCloseBtnClick = () => {
......
......@@ -43,7 +43,7 @@ const RenameTagDialog: React.FC<Props> = (props: Props) => {
});
toast.success("Rename tag successfully");
filterStore.setTagFilter(newName);
tagStore.fetchTags({ skipCache: true });
tagStore.fetchTags(undefined, { skipCache: true });
} catch (error: any) {
console.error(error);
toast.error(error.details);
......
......@@ -20,12 +20,12 @@ export const useTagStore = create(
.sort((a, b) => b[1] - a[1])
.map(([tag]) => tag);
},
fetchTags: async (options?: { skipCache: boolean }) => {
fetchTags: async (filter?: string, options?: { skipCache: boolean }) => {
const { tagAmounts: cache } = get();
if (cache.length > 0 && !options?.skipCache) {
return cache;
}
const { tagAmounts } = await memoServiceClient.listMemoTags({ parent: "memos/-" });
const { tagAmounts } = await memoServiceClient.listMemoTags({ parent: "memos/-", filter });
set({ tagAmounts });
},
})),
......
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