mirror of
https://github.com/usememos/memos.git
synced 2025-03-18 19:50:09 +01:00
chore: fix calendar timestamps
This commit is contained in:
parent
bdc257d837
commit
139090fb8f
@ -357,42 +357,6 @@ paths:
|
||||
$ref: '#/definitions/v1CreateMemoRequest'
|
||||
tags:
|
||||
- MemoService
|
||||
/api/v1/memos/stats:
|
||||
get:
|
||||
summary: GetUserMemosStats gets stats of memos for a user.
|
||||
operationId: MemoService_GetUserMemosStats
|
||||
responses:
|
||||
"200":
|
||||
description: A successful response.
|
||||
schema:
|
||||
$ref: '#/definitions/v1GetUserMemosStatsResponse'
|
||||
default:
|
||||
description: An unexpected error response.
|
||||
schema:
|
||||
$ref: '#/definitions/googlerpcStatus'
|
||||
parameters:
|
||||
- name: name
|
||||
description: |-
|
||||
name is the name of the user to get stats for.
|
||||
Format: users/{id}
|
||||
in: query
|
||||
required: false
|
||||
type: string
|
||||
- name: timezone
|
||||
description: |-
|
||||
timezone location
|
||||
Format: uses tz identifier
|
||||
https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
in: query
|
||||
required: false
|
||||
type: string
|
||||
- name: filter
|
||||
description: Same as ListMemosRequest.filter
|
||||
in: query
|
||||
required: false
|
||||
type: string
|
||||
tags:
|
||||
- MemoService
|
||||
/api/v1/memos:by-uid/{uid}:
|
||||
get:
|
||||
summary: GetMemoByUid gets a memo by uid
|
||||
@ -2417,17 +2381,6 @@ definitions:
|
||||
content:
|
||||
type: string
|
||||
format: byte
|
||||
v1GetUserMemosStatsResponse:
|
||||
type: object
|
||||
properties:
|
||||
stats:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: integer
|
||||
format: int32
|
||||
description: |-
|
||||
stats is the stats of memo creating/updating activities.
|
||||
key is the year-month-day string. e.g. "2020-01-01".
|
||||
v1HTMLElementNode:
|
||||
type: object
|
||||
properties:
|
||||
@ -2555,11 +2508,11 @@ definitions:
|
||||
v1ListMemoPropertiesResponse:
|
||||
type: object
|
||||
properties:
|
||||
properties:
|
||||
entities:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
$ref: '#/definitions/v1MemoProperty'
|
||||
$ref: '#/definitions/v1MemoPropertyEntity'
|
||||
v1ListMemoReactionsResponse:
|
||||
type: object
|
||||
properties:
|
||||
@ -2744,6 +2697,21 @@ definitions:
|
||||
type: boolean
|
||||
hasIncompleteTasks:
|
||||
type: boolean
|
||||
v1MemoPropertyEntity:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
title: |-
|
||||
The name of the memo property.
|
||||
Format: memos/{id}/properties/{property_id}
|
||||
property:
|
||||
$ref: '#/definitions/v1MemoProperty'
|
||||
readOnly: true
|
||||
displayTime:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
v1MemoRelation:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -123,11 +123,6 @@ service MemoService {
|
||||
option (google.api.http) = {get: "/api/v1/{name=memos/*}/comments"};
|
||||
option (google.api.method_signature) = "name";
|
||||
}
|
||||
// GetUserMemosStats gets stats of memos for a user.
|
||||
rpc GetUserMemosStats(GetUserMemosStatsRequest) returns (GetUserMemosStatsResponse) {
|
||||
option (google.api.http) = {get: "/api/v1/memos/stats"};
|
||||
option (google.api.method_signature) = "username";
|
||||
}
|
||||
// ListMemoReactions lists reactions for a memo.
|
||||
rpc ListMemoReactions(ListMemoReactionsRequest) returns (ListMemoReactionsResponse) {
|
||||
option (google.api.http) = {get: "/api/v1/{name=memos/*}/reactions"};
|
||||
@ -281,7 +276,17 @@ message ListMemoPropertiesRequest {
|
||||
}
|
||||
|
||||
message ListMemoPropertiesResponse {
|
||||
repeated MemoProperty properties = 1;
|
||||
repeated MemoPropertyEntity entities = 1;
|
||||
}
|
||||
|
||||
message MemoPropertyEntity {
|
||||
// The name of the memo property.
|
||||
// Format: memos/{id}/properties/{property_id}
|
||||
string name = 1;
|
||||
|
||||
MemoProperty property = 2 [(google.api.field_behavior) = OUTPUT_ONLY];
|
||||
|
||||
google.protobuf.Timestamp display_time = 3 [(google.api.field_behavior) = OUTPUT_ONLY];
|
||||
}
|
||||
|
||||
message RebuildMemoPropertyRequest {
|
||||
@ -377,26 +382,6 @@ message ListMemoCommentsResponse {
|
||||
repeated Memo memos = 1;
|
||||
}
|
||||
|
||||
message GetUserMemosStatsRequest {
|
||||
// name is the name of the user to get stats for.
|
||||
// Format: users/{id}
|
||||
string name = 1;
|
||||
|
||||
// timezone location
|
||||
// Format: uses tz identifier
|
||||
// https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
string timezone = 2;
|
||||
|
||||
// Same as ListMemosRequest.filter
|
||||
string filter = 3;
|
||||
}
|
||||
|
||||
message GetUserMemosStatsResponse {
|
||||
// stats is the stats of memo creating/updating activities.
|
||||
// key is the year-month-day string. e.g. "2020-01-01".
|
||||
map<string, int32> stats = 1;
|
||||
}
|
||||
|
||||
message ListMemoReactionsRequest {
|
||||
// The name of the memo.
|
||||
// Format: memos/{id}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1043,42 +1043,6 @@ func local_request_MemoService_ListMemoComments_0(ctx context.Context, marshaler
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
filter_MemoService_GetUserMemosStats_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
|
||||
)
|
||||
|
||||
func request_MemoService_GetUserMemosStats_0(ctx context.Context, marshaler runtime.Marshaler, client MemoServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq GetUserMemosStatsRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_MemoService_GetUserMemosStats_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.GetUserMemosStats(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_MemoService_GetUserMemosStats_0(ctx context.Context, marshaler runtime.Marshaler, server MemoServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq GetUserMemosStatsRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_MemoService_GetUserMemosStats_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.GetUserMemosStats(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_MemoService_ListMemoReactions_0(ctx context.Context, marshaler runtime.Marshaler, client MemoServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq ListMemoReactionsRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
@ -1699,31 +1663,6 @@ func RegisterMemoServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_MemoService_GetUserMemosStats_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
var annotatedContext context.Context
|
||||
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.MemoService/GetUserMemosStats", runtime.WithHTTPPathPattern("/api/v1/memos/stats"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_MemoService_GetUserMemosStats_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_MemoService_GetUserMemosStats_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_MemoService_ListMemoReactions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
@ -2236,28 +2175,6 @@ func RegisterMemoServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_MemoService_GetUserMemosStats_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
var annotatedContext context.Context
|
||||
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.MemoService/GetUserMemosStats", runtime.WithHTTPPathPattern("/api/v1/memos/stats"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_MemoService_GetUserMemosStats_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_MemoService_GetUserMemosStats_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_MemoService_ListMemoReactions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
@ -2364,8 +2281,6 @@ var (
|
||||
|
||||
pattern_MemoService_ListMemoComments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "comments"}, ""))
|
||||
|
||||
pattern_MemoService_GetUserMemosStats_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "memos", "stats"}, ""))
|
||||
|
||||
pattern_MemoService_ListMemoReactions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "reactions"}, ""))
|
||||
|
||||
pattern_MemoService_UpsertMemoReaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "reactions"}, ""))
|
||||
@ -2410,8 +2325,6 @@ var (
|
||||
|
||||
forward_MemoService_ListMemoComments_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_MemoService_GetUserMemosStats_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_MemoService_ListMemoReactions_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_MemoService_UpsertMemoReaction_0 = runtime.ForwardResponseMessage
|
||||
|
@ -38,7 +38,6 @@ const (
|
||||
MemoService_ListMemoRelations_FullMethodName = "/memos.api.v1.MemoService/ListMemoRelations"
|
||||
MemoService_CreateMemoComment_FullMethodName = "/memos.api.v1.MemoService/CreateMemoComment"
|
||||
MemoService_ListMemoComments_FullMethodName = "/memos.api.v1.MemoService/ListMemoComments"
|
||||
MemoService_GetUserMemosStats_FullMethodName = "/memos.api.v1.MemoService/GetUserMemosStats"
|
||||
MemoService_ListMemoReactions_FullMethodName = "/memos.api.v1.MemoService/ListMemoReactions"
|
||||
MemoService_UpsertMemoReaction_FullMethodName = "/memos.api.v1.MemoService/UpsertMemoReaction"
|
||||
MemoService_DeleteMemoReaction_FullMethodName = "/memos.api.v1.MemoService/DeleteMemoReaction"
|
||||
@ -84,8 +83,6 @@ type MemoServiceClient interface {
|
||||
CreateMemoComment(ctx context.Context, in *CreateMemoCommentRequest, opts ...grpc.CallOption) (*Memo, error)
|
||||
// ListMemoComments lists comments for a memo.
|
||||
ListMemoComments(ctx context.Context, in *ListMemoCommentsRequest, opts ...grpc.CallOption) (*ListMemoCommentsResponse, error)
|
||||
// GetUserMemosStats gets stats of memos for a user.
|
||||
GetUserMemosStats(ctx context.Context, in *GetUserMemosStatsRequest, opts ...grpc.CallOption) (*GetUserMemosStatsResponse, error)
|
||||
// ListMemoReactions lists reactions for a memo.
|
||||
ListMemoReactions(ctx context.Context, in *ListMemoReactionsRequest, opts ...grpc.CallOption) (*ListMemoReactionsResponse, error)
|
||||
// UpsertMemoReaction upserts a reaction for a memo.
|
||||
@ -282,16 +279,6 @@ func (c *memoServiceClient) ListMemoComments(ctx context.Context, in *ListMemoCo
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *memoServiceClient) GetUserMemosStats(ctx context.Context, in *GetUserMemosStatsRequest, opts ...grpc.CallOption) (*GetUserMemosStatsResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetUserMemosStatsResponse)
|
||||
err := c.cc.Invoke(ctx, MemoService_GetUserMemosStats_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *memoServiceClient) ListMemoReactions(ctx context.Context, in *ListMemoReactionsRequest, opts ...grpc.CallOption) (*ListMemoReactionsResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(ListMemoReactionsResponse)
|
||||
@ -362,8 +349,6 @@ type MemoServiceServer interface {
|
||||
CreateMemoComment(context.Context, *CreateMemoCommentRequest) (*Memo, error)
|
||||
// ListMemoComments lists comments for a memo.
|
||||
ListMemoComments(context.Context, *ListMemoCommentsRequest) (*ListMemoCommentsResponse, error)
|
||||
// GetUserMemosStats gets stats of memos for a user.
|
||||
GetUserMemosStats(context.Context, *GetUserMemosStatsRequest) (*GetUserMemosStatsResponse, error)
|
||||
// ListMemoReactions lists reactions for a memo.
|
||||
ListMemoReactions(context.Context, *ListMemoReactionsRequest) (*ListMemoReactionsResponse, error)
|
||||
// UpsertMemoReaction upserts a reaction for a memo.
|
||||
@ -431,9 +416,6 @@ func (UnimplementedMemoServiceServer) CreateMemoComment(context.Context, *Create
|
||||
func (UnimplementedMemoServiceServer) ListMemoComments(context.Context, *ListMemoCommentsRequest) (*ListMemoCommentsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListMemoComments not implemented")
|
||||
}
|
||||
func (UnimplementedMemoServiceServer) GetUserMemosStats(context.Context, *GetUserMemosStatsRequest) (*GetUserMemosStatsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetUserMemosStats not implemented")
|
||||
}
|
||||
func (UnimplementedMemoServiceServer) ListMemoReactions(context.Context, *ListMemoReactionsRequest) (*ListMemoReactionsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListMemoReactions not implemented")
|
||||
}
|
||||
@ -780,24 +762,6 @@ func _MemoService_ListMemoComments_Handler(srv interface{}, ctx context.Context,
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _MemoService_GetUserMemosStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetUserMemosStatsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MemoServiceServer).GetUserMemosStats(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: MemoService_GetUserMemosStats_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MemoServiceServer).GetUserMemosStats(ctx, req.(*GetUserMemosStatsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _MemoService_ListMemoReactions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListMemoReactionsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
@ -931,10 +895,6 @@ var MemoService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "ListMemoComments",
|
||||
Handler: _MemoService_ListMemoComments_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetUserMemosStats",
|
||||
Handler: _MemoService_GetUserMemosStats_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListMemoReactions",
|
||||
Handler: _MemoService_ListMemoReactions_Handler,
|
||||
|
@ -491,61 +491,6 @@ func (s *APIV1Service) ListMemoComments(ctx context.Context, request *v1pb.ListM
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *APIV1Service) GetUserMemosStats(ctx context.Context, request *v1pb.GetUserMemosStatsRequest) (*v1pb.GetUserMemosStatsResponse, error) {
|
||||
userID, err := ExtractUserIDFromName(request.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "invalid user name")
|
||||
}
|
||||
user, err := s.Store.GetUser(ctx, &store.FindUser{
|
||||
ID: &userID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to get user")
|
||||
}
|
||||
if user == nil {
|
||||
return nil, status.Errorf(codes.NotFound, "user not found")
|
||||
}
|
||||
|
||||
normalRowStatus := store.Normal
|
||||
memoFind := &store.FindMemo{
|
||||
CreatorID: &user.ID,
|
||||
RowStatus: &normalRowStatus,
|
||||
ExcludeComments: true,
|
||||
ExcludeContent: true,
|
||||
}
|
||||
if err := s.buildMemoFindWithFilter(ctx, memoFind, request.Filter); err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "failed to build find memos with filter")
|
||||
}
|
||||
|
||||
memos, err := s.Store.ListMemos(ctx, memoFind)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to list memos: %v", err)
|
||||
}
|
||||
|
||||
location, err := time.LoadLocation(request.Timezone)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "invalid timezone location")
|
||||
}
|
||||
|
||||
workspaceMemoRelatedSetting, err := s.Store.GetWorkspaceMemoRelatedSetting(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to get workspace memo related setting")
|
||||
}
|
||||
stats := make(map[string]int32)
|
||||
for _, memo := range memos {
|
||||
displayTs := memo.CreatedTs
|
||||
if workspaceMemoRelatedSetting.DisplayWithUpdateTime {
|
||||
displayTs = memo.UpdatedTs
|
||||
}
|
||||
stats[time.Unix(displayTs, 0).In(location).Format("2006-01-02")]++
|
||||
}
|
||||
|
||||
response := &v1pb.GetUserMemosStatsResponse{
|
||||
Stats: stats,
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *APIV1Service) ExportMemos(ctx context.Context, request *v1pb.ExportMemosRequest) (*v1pb.ExportMemosResponse, error) {
|
||||
normalRowStatus := store.Normal
|
||||
memoFind := &store.FindMemo{
|
||||
@ -614,14 +559,28 @@ func (s *APIV1Service) ListMemoProperties(ctx context.Context, request *v1pb.Lis
|
||||
return nil, status.Errorf(codes.Internal, "failed to list memos")
|
||||
}
|
||||
|
||||
properties := []*v1pb.MemoProperty{}
|
||||
workspaceMemoRelatedSetting, err := s.Store.GetWorkspaceMemoRelatedSetting(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get workspace memo related setting")
|
||||
}
|
||||
|
||||
entities := []*v1pb.MemoPropertyEntity{}
|
||||
for _, memo := range memos {
|
||||
if memo.Payload.Property != nil {
|
||||
properties = append(properties, convertMemoPropertyFromStore(memo.Payload.Property))
|
||||
displayTs := memo.CreatedTs
|
||||
if workspaceMemoRelatedSetting.DisplayWithUpdateTime {
|
||||
displayTs = memo.UpdatedTs
|
||||
}
|
||||
entity := &v1pb.MemoPropertyEntity{
|
||||
Name: fmt.Sprintf("%s%d", MemoNamePrefix, memo.ID),
|
||||
DisplayTime: timestamppb.New(time.Unix(displayTs, 0)),
|
||||
}
|
||||
if memo.Payload.Property != nil {
|
||||
entity.Property = convertMemoPropertyFromStore(memo.Payload.Property)
|
||||
}
|
||||
entities = append(entities, entity)
|
||||
}
|
||||
return &v1pb.ListMemoPropertiesResponse{
|
||||
Properties: properties,
|
||||
Entities: entities,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Tooltip } from "@mui/joy";
|
||||
import clsx from "clsx";
|
||||
import { getNormalizedDateString, getDateWithOffset } from "@/helpers/datetime";
|
||||
import dayjs from "dayjs";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
|
||||
interface Props {
|
||||
@ -29,8 +29,8 @@ const getCellAdditionalStyles = (count: number, maxCount: number) => {
|
||||
const ActivityCalendar = (props: Props) => {
|
||||
const t = useTranslate();
|
||||
const { month: monthStr, data, onClick } = props;
|
||||
const year = new Date(monthStr).getFullYear();
|
||||
const month = new Date(monthStr).getMonth() + 1;
|
||||
const year = dayjs(monthStr).toDate().getFullYear();
|
||||
const month = dayjs(monthStr).toDate().getMonth() + 1;
|
||||
const dayInMonth = new Date(year, month, 0).getDate();
|
||||
const firstDay = new Date(year, month - 1, 1).getDay();
|
||||
const lastDay = new Date(year, month - 1, dayInMonth).getDay();
|
||||
@ -49,14 +49,19 @@ const ActivityCalendar = (props: Props) => {
|
||||
|
||||
return (
|
||||
<div className={clsx("w-full h-auto shrink-0 grid grid-cols-7 grid-flow-row gap-1")}>
|
||||
<div className={clsx("w-6 h-5 text-xs flex justify-center items-center cursor-default opacity-60")}>Su</div>
|
||||
<div className={clsx("w-6 h-5 text-xs flex justify-center items-center cursor-default opacity-60")}>Mo</div>
|
||||
<div className={clsx("w-6 h-5 text-xs flex justify-center items-center cursor-default opacity-60")}>Tu</div>
|
||||
<div className={clsx("w-6 h-5 text-xs flex justify-center items-center cursor-default opacity-60")}>We</div>
|
||||
<div className={clsx("w-6 h-5 text-xs flex justify-center items-center cursor-default opacity-60")}>Th</div>
|
||||
<div className={clsx("w-6 h-5 text-xs flex justify-center items-center cursor-default opacity-60")}>Fr</div>
|
||||
<div className={clsx("w-6 h-5 text-xs flex justify-center items-center cursor-default opacity-60")}>Sa</div>
|
||||
{days.map((day, index) => {
|
||||
const date = getNormalizedDateString(
|
||||
getDateWithOffset(`${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`),
|
||||
);
|
||||
const date = dayjs(`${year}-${month}-${day}`).format("YYYY-MM-DD");
|
||||
const count = data[date] || 0;
|
||||
const isToday = new Date().toDateString() === new Date(date).toDateString();
|
||||
const isToday = dayjs().format("YYYY-MM-DD") === date;
|
||||
const tooltipText = count ? t("memo.count-memos-in-date", { count: count, date: date }) : date;
|
||||
const isSelected = new Date(props.selectedDate).toDateString() === new Date(date).toDateString();
|
||||
const isSelected = dayjs(props.selectedDate).format("YYYY-MM-DD") === date;
|
||||
return day ? (
|
||||
count > 0 ? (
|
||||
<Tooltip className="shrink-0" key={`${date}-${index}`} title={tooltipText} placement="top" arrow>
|
||||
@ -68,7 +73,7 @@ const ActivityCalendar = (props: Props) => {
|
||||
isSelected && "font-bold border-zinc-400 dark:border-zinc-300",
|
||||
!isToday && !isSelected && "border-transparent",
|
||||
)}
|
||||
onClick={() => count && onClick && onClick(new Date(date).toDateString())}
|
||||
onClick={() => count && onClick && onClick(date)}
|
||||
>
|
||||
{day}
|
||||
</div>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Divider, Tooltip } from "@mui/joy";
|
||||
import clsx from "clsx";
|
||||
import dayjs from "dayjs";
|
||||
import { chain } from "lodash-es";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { memoServiceClient } from "@/grpcweb";
|
||||
@ -33,43 +34,36 @@ const UserStatisticsView = () => {
|
||||
const days = Math.ceil((Date.now() - currentUser.createTime!.getTime()) / 86400000);
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
const { properties } = await memoServiceClient.listMemoProperties({
|
||||
const { entities } = await memoServiceClient.listMemoProperties({
|
||||
name: `memos/-`,
|
||||
});
|
||||
const memoStats: UserMemoStats = { link: 0, taskList: 0, code: 0, incompleteTasks: 0 };
|
||||
properties.forEach((property) => {
|
||||
if (property.hasLink) {
|
||||
entities.forEach((entity) => {
|
||||
const { property } = entity;
|
||||
if (property?.hasLink) {
|
||||
memoStats.link += 1;
|
||||
}
|
||||
if (property.hasTaskList) {
|
||||
if (property?.hasTaskList) {
|
||||
memoStats.taskList += 1;
|
||||
}
|
||||
if (property.hasCode) {
|
||||
if (property?.hasCode) {
|
||||
memoStats.code += 1;
|
||||
}
|
||||
if (property.hasIncompleteTasks) {
|
||||
if (property?.hasIncompleteTasks) {
|
||||
memoStats.incompleteTasks += 1;
|
||||
}
|
||||
});
|
||||
const displayTimes = entities.map((entity) => entity.displayTime).filter(Boolean) as Date[];
|
||||
const monthStrGroup = chain(displayTimes)
|
||||
.map((date) => dayjs(date).format("YYYY-MM-DD"))
|
||||
.countBy()
|
||||
.value();
|
||||
setMemoStats(memoStats);
|
||||
setMemoAmount(properties.length);
|
||||
|
||||
const filters = [`row_status == "NORMAL"`];
|
||||
const { stats } = await memoServiceClient.getUserMemosStats({
|
||||
name: currentUser.name,
|
||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
filter: filters.join(" && "),
|
||||
});
|
||||
setActivityStats(
|
||||
Object.fromEntries(
|
||||
Object.entries(stats).filter(([date]) => {
|
||||
return dayjs(date).format("YYYY-MM") === monthString;
|
||||
}),
|
||||
),
|
||||
);
|
||||
setMemoAmount(entities.length);
|
||||
setActivityStats(monthStrGroup);
|
||||
}, [memoStore.stateId]);
|
||||
|
||||
const handleRebuildMemoTags = async () => {
|
||||
const rebuildMemoTags = async () => {
|
||||
await memoServiceClient.rebuildMemoProperty({
|
||||
name: "memos/-",
|
||||
});
|
||||
@ -77,12 +71,17 @@ const UserStatisticsView = () => {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const onCalendarClick = (date: string) => {
|
||||
memoFilterStore.removeFilter((f) => f.factor === "displayTime");
|
||||
memoFilterStore.addFilter({ factor: "displayTime", value: date });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="group w-full border mt-2 py-2 px-3 rounded-lg space-y-0.5 text-gray-500 dark:text-gray-400 bg-zinc-50 dark:bg-zinc-900 dark:border-zinc-800">
|
||||
<div className="w-full mb-2 flex flex-row justify-between items-center">
|
||||
<div className="w-full mb-1 flex flex-row justify-between items-center">
|
||||
<div className="relative text-base font-medium leading-6 flex flex-row items-center dark:text-gray-400">
|
||||
<Icon.CalendarDays className="w-5 h-auto mr-1 opacity-60" strokeWidth={1.5} />
|
||||
<span>{new Date(monthString).toLocaleString(i18n.language, { year: "numeric", month: "long" })}</span>
|
||||
<span>{dayjs(monthString).toDate().toLocaleString(i18n.language, { year: "numeric", month: "long" })}</span>
|
||||
<input
|
||||
className="inset-0 absolute z-1 opacity-0"
|
||||
type="month"
|
||||
@ -97,7 +96,7 @@ const UserStatisticsView = () => {
|
||||
<Icon.MoreVertical className="w-4 h-auto shrink-0 opacity-60" />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<button className="w-auto flex flex-row justify-between items-center gap-2 hover:opacity-80" onClick={handleRebuildMemoTags}>
|
||||
<button className="w-auto flex flex-row justify-between items-center gap-2 hover:opacity-80" onClick={rebuildMemoTags}>
|
||||
<Icon.RefreshCcw className="text-gray-400 w-4 h-auto cursor-pointer opacity-60" />
|
||||
<span className="text-sm shrink-0 text-gray-500 dark:text-gray-400">Refresh</span>
|
||||
</button>
|
||||
@ -106,10 +105,10 @@ const UserStatisticsView = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<ActivityCalendar month={monthString} selectedDate={selectedDate.toDateString()} data={activityStats} />
|
||||
<ActivityCalendar month={monthString} selectedDate={selectedDate.toDateString()} data={activityStats} onClick={onCalendarClick} />
|
||||
{memoAmount > 0 && (
|
||||
<p className="mt-1 w-full text-xs italic opacity-80">
|
||||
<span>{memoAmount}</span> memos in <span>{days}</span> days
|
||||
<span>{memoAmount}</span> memos in <span>{days}</span> {days > 1 ? "days" : "day"}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
@ -4,26 +4,6 @@ export function getTimeStampByDate(t: Date | number | string | any): number {
|
||||
return new Date(t).getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a time string to provided time.
|
||||
*
|
||||
* If no date is provided, the current date is used.
|
||||
*
|
||||
* Output is always ``HH:MM`` (24-hour format)
|
||||
*/
|
||||
export function getTimeString(t?: Date | number | string): string {
|
||||
const tsFromDate = getTimeStampByDate(t ? t : Date.now());
|
||||
const d = new Date(tsFromDate);
|
||||
|
||||
const hours = d.getHours();
|
||||
const mins = d.getMinutes();
|
||||
|
||||
const hoursStr = hours < 10 ? "0" + hours : hours;
|
||||
const minsStr = mins < 10 ? "0" + mins : mins;
|
||||
|
||||
return `${hoursStr}:${minsStr}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a localized date and time string to provided time.
|
||||
*
|
||||
@ -49,51 +29,3 @@ export function getDateTimeString(t?: Date | number | string | any, locale = i18
|
||||
return tsFromDate.toLocaleString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the normalized date string of the provided date.
|
||||
* Format is always `YYYY-MM-DDT00:00`.
|
||||
*
|
||||
* If no date is provided, the current date is used.
|
||||
*/
|
||||
export function getNormalizedTimeString(t?: Date | number | string): string {
|
||||
const date = new Date(t ? t : Date.now());
|
||||
|
||||
const yyyy = date.getFullYear();
|
||||
const M = date.getMonth() + 1;
|
||||
const d = date.getDate();
|
||||
const h = date.getHours();
|
||||
const m = date.getMinutes();
|
||||
|
||||
const MM = M < 10 ? "0" + M : M;
|
||||
const dd = d < 10 ? "0" + d : d;
|
||||
const hh = h < 10 ? "0" + h : h;
|
||||
const mm = m < 10 ? "0" + m : m;
|
||||
|
||||
return `${yyyy}-${MM}-${dd}T${hh}:${mm}`;
|
||||
}
|
||||
|
||||
export function getNormalizedDateString(t?: Date | number | string): string {
|
||||
const date = new Date(t ? t : Date.now());
|
||||
|
||||
const yyyy = date.getFullYear();
|
||||
const M = date.getMonth() + 1;
|
||||
const d = date.getDate();
|
||||
|
||||
const MM = M < 10 ? "0" + M : M;
|
||||
const dd = d < 10 ? "0" + d : d;
|
||||
|
||||
return `${yyyy}-${MM}-${dd}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a new Date object by adjusting the provided date, timestamp, or date string
|
||||
* based on the current timezone offset.
|
||||
*
|
||||
* @param t - The input date, timestamp, or date string (optional). If not provided,
|
||||
* the current date and time will be used.
|
||||
* @returns A new Date object adjusted by the current timezone offset.
|
||||
*/
|
||||
export function getDateWithOffset(t?: Date | number | string): Date {
|
||||
return new Date(getTimeStampByDate(t) + new Date().getTimezoneOffset() * 60 * 1000);
|
||||
}
|
||||
|
@ -9,11 +9,13 @@ import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||
import Loading from "@/pages/Loading";
|
||||
import { Routes } from "@/router";
|
||||
import { useMemoFilterStore } from "@/store/v1";
|
||||
|
||||
const RootLayout = () => {
|
||||
const location = useLocation();
|
||||
const { sm } = useResponsiveWidth();
|
||||
const currentUser = useCurrentUser();
|
||||
const memoFilterStore = useMemoFilterStore();
|
||||
const [collapsed, setCollapsed] = useLocalStorage<boolean>("navigation-collapsed", false);
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
|
||||
@ -27,6 +29,11 @@ const RootLayout = () => {
|
||||
setInitialized(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// When the route changes, remove all filters.
|
||||
memoFilterStore.removeFilter(() => true);
|
||||
}, [location.pathname]);
|
||||
|
||||
return !initialized ? (
|
||||
<Loading />
|
||||
) : (
|
||||
|
@ -51,6 +51,10 @@ const Home = () => {
|
||||
filters.push(`has_task_list == true`);
|
||||
} else if (filter.factor === "property.hasCode") {
|
||||
filters.push(`has_code == true`);
|
||||
} else if (filter.factor === "displayTime") {
|
||||
const timestampAfter = getTimeStampByDate(new Date(filter.value)) / 1000;
|
||||
filters.push(`display_time_after == ${timestampAfter}`);
|
||||
filters.push(`display_time_before == ${timestampAfter + 60 * 60 * 24}`);
|
||||
}
|
||||
}
|
||||
if (contentSearch.length > 0) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user