Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
139090fb
Commit
139090fb
authored
Jul 27, 2024
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: fix calendar timestamps
parent
bdc257d8
Changes
11
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
543 additions
and
893 deletions
+543
-893
apidocs.swagger.yaml
docs/apidocs.swagger.yaml
+17
-49
memo_service.proto
proto/api/v1/memo_service.proto
+11
-26
memo_service.pb.go
proto/gen/api/v1/memo_service.pb.go
+447
-529
memo_service.pb.gw.go
proto/gen/api/v1/memo_service.pb.gw.go
+0
-87
memo_service_grpc.pb.go
proto/gen/api/v1/memo_service_grpc.pb.go
+0
-40
memo_service.go
server/router/api/v1/memo_service.go
+17
-58
ActivityCalendar.tsx
web/src/components/ActivityCalendar.tsx
+14
-9
UserStatisticsView.tsx
web/src/components/UserStatisticsView.tsx
+26
-27
datetime.ts
web/src/helpers/datetime.ts
+0
-68
RootLayout.tsx
web/src/layouts/RootLayout.tsx
+7
-0
Home.tsx
web/src/pages/Home.tsx
+4
-0
No files found.
docs/apidocs.swagger.yaml
View file @
139090fb
...
...
@@ -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
:
proper
ties
:
enti
ties
:
type
:
array
items
:
type
:
object
$ref
:
'
#/definitions/v1MemoProperty'
$ref
:
'
#/definitions/v1MemoProperty
Entity
'
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
:
...
...
proto/api/v1/memo_service.proto
View file @
139090fb
...
...
@@ -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}
...
...
proto/gen/api/v1/memo_service.pb.go
View file @
139090fb
This diff is collapsed.
Click to expand it.
proto/gen/api/v1/memo_service.pb.gw.go
View file @
139090fb
...
...
@@ -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
...
...
proto/gen/api/v1/memo_service_grpc.pb.go
View file @
139090fb
...
...
@@ -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
,
...
...
server/router/api/v1/memo_service.go
View file @
139090fb
...
...
@@ -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
{
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
{
properties
=
append
(
properties
,
convertMemoPropertyFromStore
(
memo
.
Payload
.
Property
)
)
entity
.
Property
=
convertMemoPropertyFromStore
(
memo
.
Payload
.
Property
)
}
entities
=
append
(
entities
,
entity
)
}
return
&
v1pb
.
ListMemoPropertiesResponse
{
Properties
:
proper
ties
,
Entities
:
enti
ties
,
},
nil
}
...
...
web/src/components/ActivityCalendar.tsx
View file @
139090fb
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
>
...
...
web/src/components/UserStatisticsView.tsx
View file @
139090fb
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
{
proper
ties
}
=
await
memoServiceClient
.
listMemoProperties
({
const
{
enti
ties
}
=
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
handleR
ebuildMemoTags
=
async
()
=>
{
const
r
ebuildMemoTags
=
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=
{
handleR
ebuildMemoTags
}
>
<
button
className=
"w-auto flex flex-row justify-between items-center gap-2 hover:opacity-80"
onClick=
{
r
ebuildMemoTags
}
>
<
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
>
...
...
web/src/helpers/datetime.ts
View file @
139090fb
...
...
@@ -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
);
}
web/src/layouts/RootLayout.tsx
View file @
139090fb
...
...
@@ -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
/>
)
:
(
...
...
web/src/pages/Home.tsx
View file @
139090fb
...
...
@@ -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
)
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment