diff --git a/server/router/api/v1/memo_service_filter.go b/server/router/api/v1/memo_service_filter.go index 513253dd..b4f261f7 100644 --- a/server/router/api/v1/memo_service_filter.go +++ b/server/router/api/v1/memo_service_filter.go @@ -52,6 +52,10 @@ func (s *APIV1Service) buildMemoFindWithFilter(ctx context.Context, find *store. find.CreatedTsBefore = filterExpr.DisplayTimeBefore } } + if filterExpr.Pinned { + pinned := true + find.Pinned = &pinned + } if filterExpr.HasLink { find.PayloadFind.HasLink = true } @@ -74,6 +78,7 @@ var MemoFilterCELAttributes = []cel.EnvOption{ cel.Variable("tag_search", cel.ListType(cel.StringType)), cel.Variable("display_time_before", cel.IntType), cel.Variable("display_time_after", cel.IntType), + cel.Variable("pinned", cel.BoolType), cel.Variable("has_link", cel.BoolType), cel.Variable("has_task_list", cel.BoolType), cel.Variable("has_code", cel.BoolType), @@ -85,6 +90,7 @@ type MemoFilter struct { TagSearch []string DisplayTimeBefore *int64 DisplayTimeAfter *int64 + Pinned bool HasLink bool HasTaskList bool HasCode bool @@ -134,6 +140,9 @@ func findMemoField(callExpr *exprv1.Expr_Call, filter *MemoFilter) { } else if idExpr.Name == "display_time_after" { displayTimeAfter := callExpr.Args[1].GetConstExpr().GetInt64Value() filter.DisplayTimeAfter = &displayTimeAfter + } else if idExpr.Name == "pinned" { + value := callExpr.Args[1].GetConstExpr().GetBoolValue() + filter.Pinned = value } else if idExpr.Name == "has_link" { value := callExpr.Args[1].GetConstExpr().GetBoolValue() filter.HasLink = value diff --git a/web/src/components/MemoFilters.tsx b/web/src/components/MemoFilters.tsx index cbf1eb70..cd1771a6 100644 --- a/web/src/components/MemoFilters.tsx +++ b/web/src/components/MemoFilters.tsx @@ -1,5 +1,5 @@ import { isEqual } from "lodash-es"; -import { CalendarIcon, CheckCircleIcon, CodeIcon, EyeIcon, HashIcon, LinkIcon, SearchIcon, XIcon } from "lucide-react"; +import { CalendarIcon, CheckCircleIcon, CodeIcon, EyeIcon, HashIcon, LinkIcon, PinIcon, SearchIcon, XIcon } from "lucide-react"; import { useEffect } from "react"; import { useSearchParams } from "react-router-dom"; import { FilterFactor, getMemoFilterKey, MemoFilter, stringifyFilters, useMemoFilterStore } from "@/store/v1"; @@ -68,6 +68,7 @@ const FactorIcon = ({ factor, className }: { factor: FilterFactor; className?: s visibility: , contentSearch: , displayTime: , + pinned: , "property.hasLink": , "property.hasTaskList": , "property.hasCode": , diff --git a/web/src/components/StatisticsView.tsx b/web/src/components/StatisticsView.tsx index 86abbe63..2eabb14f 100644 --- a/web/src/components/StatisticsView.tsx +++ b/web/src/components/StatisticsView.tsx @@ -1,12 +1,15 @@ import { Tooltip } from "@mui/joy"; import dayjs from "dayjs"; import { countBy } from "lodash-es"; -import { CheckCircleIcon, ChevronRightIcon, ChevronLeftIcon, Code2Icon, LinkIcon, ListTodoIcon } from "lucide-react"; +import { CheckCircleIcon, ChevronRightIcon, ChevronLeftIcon, Code2Icon, LinkIcon, ListTodoIcon, PinIcon } from "lucide-react"; import { observer } from "mobx-react-lite"; import { useState } from "react"; import DatePicker from "react-datepicker"; +import { matchPath, useLocation } from "react-router-dom"; import useAsyncEffect from "@/hooks/useAsyncEffect"; +import useCurrentUser from "@/hooks/useCurrentUser"; import i18n from "@/i18n"; +import { Routes } from "@/router"; import { useMemoFilterStore } from "@/store/v1"; import { userStore } from "@/store/v2"; import { UserStats_MemoTypeStats } from "@/types/proto/api/v1/user_service"; @@ -17,7 +20,9 @@ import "react-datepicker/dist/react-datepicker.css"; const StatisticsView = observer(() => { const t = useTranslate(); + const location = useLocation(); const memoFilterStore = useMemoFilterStore(); + const currentUser = useCurrentUser(); const [memoTypeStats, setMemoTypeStats] = useState(UserStats_MemoTypeStats.fromPartial({})); const [activityStats, setActivityStats] = useState>({}); const [selectedDate] = useState(new Date()); @@ -92,7 +97,22 @@ const StatisticsView = observer(() => { onClick={onCalendarClick} /> -
+
+ {matchPath(Routes.ROOT, location.pathname) && + currentUser && + userStore.state.currentUserStats && + userStore.state.currentUserStats.pinnedMemos.length > 0 && ( +
memoFilterStore.addFilter({ factor: "pinned", value: "" })} + > +
+ + Pinned +
+ {userStore.state.currentUserStats.pinnedMemos.length} +
+ )}
memoFilterStore.addFilter({ factor: "property.hasLink", value: "" })} diff --git a/web/src/pages/Home.tsx b/web/src/pages/Home.tsx index c2291ac2..20aed79e 100644 --- a/web/src/pages/Home.tsx +++ b/web/src/pages/Home.tsx @@ -23,6 +23,8 @@ const Home = observer(() => { contentSearch.push(`"${filter.value}"`); } else if (filter.factor === "tagSearch") { tagSearch.push(`"${filter.value}"`); + } else if (filter.factor === "pinned") { + conditions.push(`pinned == true`); } else if (filter.factor === "property.hasLink") { conditions.push(`has_link == true`); } else if (filter.factor === "property.hasTaskList") { diff --git a/web/src/pages/UserProfile.tsx b/web/src/pages/UserProfile.tsx index 02c954c3..b0be2c3a 100644 --- a/web/src/pages/UserProfile.tsx +++ b/web/src/pages/UserProfile.tsx @@ -6,7 +6,6 @@ import { observer } from "mobx-react-lite"; import { useEffect, useMemo, useState } from "react"; import { toast } from "react-hot-toast"; import { useParams } from "react-router-dom"; -import MemoFilters from "@/components/MemoFilters"; import MemoView from "@/components/MemoView"; import PagedMemoList from "@/components/PagedMemoList"; import UserAvatar from "@/components/UserAvatar"; @@ -78,7 +77,7 @@ const UserProfile = observer(() => { return (
-
+
{!loadingState.isLoading && (user ? ( <> @@ -99,7 +98,6 @@ const UserProfile = observer(() => {

- ( diff --git a/web/src/store/v1/memoFilter.ts b/web/src/store/v1/memoFilter.ts index 644c8062..6e30202e 100644 --- a/web/src/store/v1/memoFilter.ts +++ b/web/src/store/v1/memoFilter.ts @@ -7,6 +7,7 @@ export type FilterFactor = | "visibility" | "contentSearch" | "displayTime" + | "pinned" | "property.hasLink" | "property.hasTaskList" | "property.hasCode";