chore: update timeline page

This commit is contained in:
Steven 2023-12-21 21:24:08 +08:00
parent b14334220f
commit 9361613f23
21 changed files with 2878 additions and 303 deletions

View File

@ -233,6 +233,44 @@ func (s *APIV2Service) DeleteMemo(ctx context.Context, request *apiv2pb.DeleteMe
return &apiv2pb.DeleteMemoResponse{}, nil
}
func (s *APIV2Service) SetMemoResources(ctx context.Context, request *apiv2pb.SetMemoResourcesRequest) (*apiv2pb.SetMemoResourcesResponse, error) {
resources, err := s.Store.ListResources(ctx, &store.FindResource{MemoID: &request.Id})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list resources")
}
// Delete resources that are not in the request.
for _, resource := range resources {
found := false
for _, requestResource := range request.Resources {
if resource.ID == int32(requestResource.Id) {
found = true
break
}
}
if !found {
if err = s.Store.DeleteResource(ctx, &store.DeleteResource{
ID: int32(resource.ID),
MemoID: &request.Id,
}); err != nil {
return nil, status.Errorf(codes.Internal, "failed to delete resource")
}
}
}
// Update resources' memo_id in the request.
for _, resource := range request.Resources {
if _, err := s.Store.UpdateResource(ctx, &store.UpdateResource{
ID: resource.Id,
MemoID: &request.Id,
}); err != nil {
return nil, status.Errorf(codes.Internal, "failed to update resource")
}
}
return &apiv2pb.SetMemoResourcesResponse{}, nil
}
func (s *APIV2Service) ListMemoResources(ctx context.Context, request *apiv2pb.ListMemoResourcesRequest) (*apiv2pb.ListMemoResourcesResponse, error) {
resources, err := s.Store.ListResources(ctx, &store.FindResource{
MemoID: &request.Id,
@ -250,6 +288,51 @@ func (s *APIV2Service) ListMemoResources(ctx context.Context, request *apiv2pb.L
return response, nil
}
func (s *APIV2Service) SetMemoRelations(ctx context.Context, request *apiv2pb.SetMemoRelationsRequest) (*apiv2pb.SetMemoRelationsResponse, error) {
if err := s.Store.DeleteMemoRelation(ctx, &store.DeleteMemoRelation{MemoID: &request.Id}); err != nil {
return nil, status.Errorf(codes.Internal, "failed to delete memo relation")
}
for _, relation := range request.Relations {
if _, err := s.Store.UpsertMemoRelation(ctx, &store.MemoRelation{
MemoID: request.Id,
RelatedMemoID: relation.RelatedMemoId,
Type: convertMemoRelationTypeToStore(relation.Type),
}); err != nil {
return nil, status.Errorf(codes.Internal, "failed to upsert memo relation")
}
}
return &apiv2pb.SetMemoRelationsResponse{}, nil
}
func (s *APIV2Service) ListMemoRelations(ctx context.Context, request *apiv2pb.ListMemoRelationsRequest) (*apiv2pb.ListMemoRelationsResponse, error) {
relationList := []*apiv2pb.MemoRelation{}
tempList, err := s.Store.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &request.Id,
})
if err != nil {
return nil, err
}
for _, relation := range tempList {
relationList = append(relationList, convertMemoRelationFromStore(relation))
}
tempList, err = s.Store.ListMemoRelations(ctx, &store.FindMemoRelation{
RelatedMemoID: &request.Id,
})
if err != nil {
return nil, err
}
for _, relation := range tempList {
relationList = append(relationList, convertMemoRelationFromStore(relation))
}
response := &apiv2pb.ListMemoRelationsResponse{
Relations: relationList,
}
return response, nil
}
func (s *APIV2Service) CreateMemoComment(ctx context.Context, request *apiv2pb.CreateMemoCommentRequest) (*apiv2pb.CreateMemoCommentResponse, error) {
// Create the comment memo first.
createMemoResponse, err := s.CreateMemo(ctx, request.Create)
@ -348,6 +431,36 @@ func (s *APIV2Service) getMemoDisplayWithUpdatedTsSettingValue(ctx context.Conte
return memoDisplayWithUpdatedTs, nil
}
func convertMemoRelationFromStore(memoRelation *store.MemoRelation) *apiv2pb.MemoRelation {
return &apiv2pb.MemoRelation{
MemoId: memoRelation.MemoID,
RelatedMemoId: memoRelation.RelatedMemoID,
Type: convertMemoRelationTypeFromStore(memoRelation.Type),
}
}
func convertMemoRelationTypeFromStore(relationType store.MemoRelationType) apiv2pb.MemoRelation_Type {
switch relationType {
case store.MemoRelationReference:
return apiv2pb.MemoRelation_REFERENCE
case store.MemoRelationComment:
return apiv2pb.MemoRelation_COMMENT
default:
return apiv2pb.MemoRelation_TYPE_UNSPECIFIED
}
}
func convertMemoRelationTypeToStore(relationType apiv2pb.MemoRelation_Type) store.MemoRelationType {
switch relationType {
case apiv2pb.MemoRelation_REFERENCE:
return store.MemoRelationReference
case apiv2pb.MemoRelation_COMMENT:
return store.MemoRelationComment
default:
return store.MemoRelationReference
}
}
func convertVisibilityFromStore(visibility store.Visibility) apiv2pb.Visibility {
switch visibility {
case store.Private:

View File

@ -0,0 +1,17 @@
syntax = "proto3";
package memos.api.v2;
option go_package = "gen/api/v2";
message MemoRelation {
int32 memo_id = 1;
int32 related_memo_id = 2;
enum Type {
TYPE_UNSPECIFIED = 0;
REFERENCE = 1;
COMMENT = 2;
}
Type type = 3;
}

View File

@ -4,6 +4,7 @@ package memos.api.v2;
import "api/v2/common.proto";
import "api/v2/markdown_service.proto";
import "api/v2/memo_relation_service.proto";
import "api/v2/resource_service.proto";
import "google/api/annotations.proto";
import "google/api/client.proto";
@ -42,11 +43,32 @@ service MemoService {
option (google.api.method_signature) = "id";
}
rpc SetMemoResources(SetMemoResourcesRequest) returns (SetMemoResourcesResponse) {
option (google.api.http) = {
post: "/api/v2/memos/{id}/resources"
body: "*"
};
option (google.api.method_signature) = "id";
}
rpc ListMemoResources(ListMemoResourcesRequest) returns (ListMemoResourcesResponse) {
option (google.api.http) = {get: "/api/v2/memos/{id}/resources"};
option (google.api.method_signature) = "id";
}
rpc SetMemoRelations(SetMemoRelationsRequest) returns (SetMemoRelationsResponse) {
option (google.api.http) = {
post: "/api/v2/memos/{id}/relations"
body: "*"
};
option (google.api.method_signature) = "id";
}
rpc ListMemoRelations(ListMemoRelationsRequest) returns (ListMemoRelationsResponse) {
option (google.api.http) = {get: "/api/v2/memos/{id}/relations"};
option (google.api.method_signature) = "id";
}
rpc CreateMemoComment(CreateMemoCommentRequest) returns (CreateMemoCommentResponse) {
option (google.api.http) = {post: "/api/v2/memos/{id}/comments"};
option (google.api.method_signature) = "id";
@ -139,6 +161,14 @@ message DeleteMemoRequest {
message DeleteMemoResponse {}
message SetMemoResourcesRequest {
int32 id = 1;
repeated Resource resources = 2;
}
message SetMemoResourcesResponse {}
message ListMemoResourcesRequest {
int32 id = 1;
}
@ -147,6 +177,22 @@ message ListMemoResourcesResponse {
repeated Resource resources = 1;
}
message SetMemoRelationsRequest {
int32 id = 1;
repeated MemoRelation relations = 2;
}
message SetMemoRelationsResponse {}
message ListMemoRelationsRequest {
int32 id = 1;
}
message ListMemoRelationsResponse {
repeated MemoRelation relations = 1;
}
message CreateMemoCommentRequest {
// id is the memo id to create comment for.
int32 id = 1;

View File

@ -91,6 +91,11 @@
- [MarkdownService](#memos-api-v2-MarkdownService)
- [api/v2/memo_relation_service.proto](#api_v2_memo_relation_service-proto)
- [MemoRelation](#memos-api-v2-MemoRelation)
- [MemoRelation.Type](#memos-api-v2-MemoRelation-Type)
- [api/v2/resource_service.proto](#api_v2_resource_service-proto)
- [CreateResourceRequest](#memos-api-v2-CreateResourceRequest)
- [CreateResourceResponse](#memos-api-v2-CreateResourceResponse)
@ -115,11 +120,17 @@
- [GetMemoResponse](#memos-api-v2-GetMemoResponse)
- [ListMemoCommentsRequest](#memos-api-v2-ListMemoCommentsRequest)
- [ListMemoCommentsResponse](#memos-api-v2-ListMemoCommentsResponse)
- [ListMemoRelationsRequest](#memos-api-v2-ListMemoRelationsRequest)
- [ListMemoRelationsResponse](#memos-api-v2-ListMemoRelationsResponse)
- [ListMemoResourcesRequest](#memos-api-v2-ListMemoResourcesRequest)
- [ListMemoResourcesResponse](#memos-api-v2-ListMemoResourcesResponse)
- [ListMemosRequest](#memos-api-v2-ListMemosRequest)
- [ListMemosResponse](#memos-api-v2-ListMemosResponse)
- [Memo](#memos-api-v2-Memo)
- [SetMemoRelationsRequest](#memos-api-v2-SetMemoRelationsRequest)
- [SetMemoRelationsResponse](#memos-api-v2-SetMemoRelationsResponse)
- [SetMemoResourcesRequest](#memos-api-v2-SetMemoResourcesRequest)
- [SetMemoResourcesResponse](#memos-api-v2-SetMemoResourcesResponse)
- [UpdateMemoRequest](#memos-api-v2-UpdateMemoRequest)
- [UpdateMemoResponse](#memos-api-v2-UpdateMemoResponse)
@ -1317,6 +1328,52 @@
<a name="api_v2_memo_relation_service-proto"></a>
<p align="right"><a href="#top">Top</a></p>
## api/v2/memo_relation_service.proto
<a name="memos-api-v2-MemoRelation"></a>
### MemoRelation
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| memo_id | [int32](#int32) | | |
| related_memo_id | [int32](#int32) | | |
| type | [MemoRelation.Type](#memos-api-v2-MemoRelation-Type) | | |
<a name="memos-api-v2-MemoRelation-Type"></a>
### MemoRelation.Type
| Name | Number | Description |
| ---- | ------ | ----------- |
| TYPE_UNSPECIFIED | 0 | |
| REFERENCE | 1 | |
| COMMENT | 2 | |
<a name="api_v2_resource_service-proto"></a>
<p align="right"><a href="#top">Top</a></p>
@ -1635,6 +1692,36 @@
<a name="memos-api-v2-ListMemoRelationsRequest"></a>
### ListMemoRelationsRequest
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| id | [int32](#int32) | | |
<a name="memos-api-v2-ListMemoRelationsResponse"></a>
### ListMemoRelationsResponse
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| relations | [MemoRelation](#memos-api-v2-MemoRelation) | repeated | |
<a name="memos-api-v2-ListMemoResourcesRequest"></a>
### ListMemoResourcesRequest
@ -1721,6 +1808,58 @@
<a name="memos-api-v2-SetMemoRelationsRequest"></a>
### SetMemoRelationsRequest
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| id | [int32](#int32) | | |
| relations | [MemoRelation](#memos-api-v2-MemoRelation) | repeated | |
<a name="memos-api-v2-SetMemoRelationsResponse"></a>
### SetMemoRelationsResponse
<a name="memos-api-v2-SetMemoResourcesRequest"></a>
### SetMemoResourcesRequest
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| id | [int32](#int32) | | |
| resources | [Resource](#memos-api-v2-Resource) | repeated | |
<a name="memos-api-v2-SetMemoResourcesResponse"></a>
### SetMemoResourcesResponse
<a name="memos-api-v2-UpdateMemoRequest"></a>
### UpdateMemoRequest
@ -1785,7 +1924,10 @@
| GetMemo | [GetMemoRequest](#memos-api-v2-GetMemoRequest) | [GetMemoResponse](#memos-api-v2-GetMemoResponse) | |
| UpdateMemo | [UpdateMemoRequest](#memos-api-v2-UpdateMemoRequest) | [UpdateMemoResponse](#memos-api-v2-UpdateMemoResponse) | |
| DeleteMemo | [DeleteMemoRequest](#memos-api-v2-DeleteMemoRequest) | [DeleteMemoResponse](#memos-api-v2-DeleteMemoResponse) | |
| SetMemoResources | [SetMemoResourcesRequest](#memos-api-v2-SetMemoResourcesRequest) | [SetMemoResourcesResponse](#memos-api-v2-SetMemoResourcesResponse) | |
| ListMemoResources | [ListMemoResourcesRequest](#memos-api-v2-ListMemoResourcesRequest) | [ListMemoResourcesResponse](#memos-api-v2-ListMemoResourcesResponse) | |
| SetMemoRelations | [SetMemoRelationsRequest](#memos-api-v2-SetMemoRelationsRequest) | [SetMemoRelationsResponse](#memos-api-v2-SetMemoRelationsResponse) | |
| ListMemoRelations | [ListMemoRelationsRequest](#memos-api-v2-ListMemoRelationsRequest) | [ListMemoRelationsResponse](#memos-api-v2-ListMemoRelationsResponse) | |
| CreateMemoComment | [CreateMemoCommentRequest](#memos-api-v2-CreateMemoCommentRequest) | [CreateMemoCommentResponse](#memos-api-v2-CreateMemoCommentResponse) | |
| ListMemoComments | [ListMemoCommentsRequest](#memos-api-v2-ListMemoCommentsRequest) | [ListMemoCommentsResponse](#memos-api-v2-ListMemoCommentsResponse) | |

View File

@ -0,0 +1,232 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.31.0
// protoc (unknown)
// source: api/v2/memo_relation_service.proto
package apiv2
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type MemoRelation_Type int32
const (
MemoRelation_TYPE_UNSPECIFIED MemoRelation_Type = 0
MemoRelation_REFERENCE MemoRelation_Type = 1
MemoRelation_COMMENT MemoRelation_Type = 2
)
// Enum value maps for MemoRelation_Type.
var (
MemoRelation_Type_name = map[int32]string{
0: "TYPE_UNSPECIFIED",
1: "REFERENCE",
2: "COMMENT",
}
MemoRelation_Type_value = map[string]int32{
"TYPE_UNSPECIFIED": 0,
"REFERENCE": 1,
"COMMENT": 2,
}
)
func (x MemoRelation_Type) Enum() *MemoRelation_Type {
p := new(MemoRelation_Type)
*p = x
return p
}
func (x MemoRelation_Type) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (MemoRelation_Type) Descriptor() protoreflect.EnumDescriptor {
return file_api_v2_memo_relation_service_proto_enumTypes[0].Descriptor()
}
func (MemoRelation_Type) Type() protoreflect.EnumType {
return &file_api_v2_memo_relation_service_proto_enumTypes[0]
}
func (x MemoRelation_Type) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use MemoRelation_Type.Descriptor instead.
func (MemoRelation_Type) EnumDescriptor() ([]byte, []int) {
return file_api_v2_memo_relation_service_proto_rawDescGZIP(), []int{0, 0}
}
type MemoRelation struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
MemoId int32 `protobuf:"varint,1,opt,name=memo_id,json=memoId,proto3" json:"memo_id,omitempty"`
RelatedMemoId int32 `protobuf:"varint,2,opt,name=related_memo_id,json=relatedMemoId,proto3" json:"related_memo_id,omitempty"`
Type MemoRelation_Type `protobuf:"varint,3,opt,name=type,proto3,enum=memos.api.v2.MemoRelation_Type" json:"type,omitempty"`
}
func (x *MemoRelation) Reset() {
*x = MemoRelation{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_memo_relation_service_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MemoRelation) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MemoRelation) ProtoMessage() {}
func (x *MemoRelation) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_memo_relation_service_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MemoRelation.ProtoReflect.Descriptor instead.
func (*MemoRelation) Descriptor() ([]byte, []int) {
return file_api_v2_memo_relation_service_proto_rawDescGZIP(), []int{0}
}
func (x *MemoRelation) GetMemoId() int32 {
if x != nil {
return x.MemoId
}
return 0
}
func (x *MemoRelation) GetRelatedMemoId() int32 {
if x != nil {
return x.RelatedMemoId
}
return 0
}
func (x *MemoRelation) GetType() MemoRelation_Type {
if x != nil {
return x.Type
}
return MemoRelation_TYPE_UNSPECIFIED
}
var File_api_v2_memo_relation_service_proto protoreflect.FileDescriptor
var file_api_v2_memo_relation_service_proto_rawDesc = []byte{
0x0a, 0x22, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f, 0x6d, 0x65, 0x6d, 0x6f, 0x5f, 0x72, 0x65,
0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x76, 0x32, 0x22, 0xbe, 0x01, 0x0a, 0x0c, 0x4d, 0x65, 0x6d, 0x6f, 0x52, 0x65, 0x6c, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x65, 0x6d, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f,
0x72, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x5f, 0x69, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65,
0x6d, 0x6f, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01,
0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76,
0x32, 0x2e, 0x4d, 0x65, 0x6d, 0x6f, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x54,
0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x38, 0x0a, 0x04, 0x54, 0x79, 0x70,
0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43,
0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x52, 0x45, 0x46, 0x45, 0x52,
0x45, 0x4e, 0x43, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x4d, 0x4d, 0x45, 0x4e,
0x54, 0x10, 0x02, 0x42, 0xb0, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x65, 0x6d, 0x6f,
0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x42, 0x18, 0x4d, 0x65, 0x6d, 0x6f, 0x52, 0x65,
0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f,
0x74, 0x6f, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
0x2f, 0x75, 0x73, 0x65, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2f, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2f,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32,
0x3b, 0x61, 0x70, 0x69, 0x76, 0x32, 0xa2, 0x02, 0x03, 0x4d, 0x41, 0x58, 0xaa, 0x02, 0x0c, 0x4d,
0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x56, 0x32, 0xca, 0x02, 0x0c, 0x4d, 0x65,
0x6d, 0x6f, 0x73, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x32, 0xe2, 0x02, 0x18, 0x4d, 0x65, 0x6d,
0x6f, 0x73, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x32, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74,
0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0e, 0x4d, 0x65, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x41,
0x70, 0x69, 0x3a, 0x3a, 0x56, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_api_v2_memo_relation_service_proto_rawDescOnce sync.Once
file_api_v2_memo_relation_service_proto_rawDescData = file_api_v2_memo_relation_service_proto_rawDesc
)
func file_api_v2_memo_relation_service_proto_rawDescGZIP() []byte {
file_api_v2_memo_relation_service_proto_rawDescOnce.Do(func() {
file_api_v2_memo_relation_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_v2_memo_relation_service_proto_rawDescData)
})
return file_api_v2_memo_relation_service_proto_rawDescData
}
var file_api_v2_memo_relation_service_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_api_v2_memo_relation_service_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_api_v2_memo_relation_service_proto_goTypes = []interface{}{
(MemoRelation_Type)(0), // 0: memos.api.v2.MemoRelation.Type
(*MemoRelation)(nil), // 1: memos.api.v2.MemoRelation
}
var file_api_v2_memo_relation_service_proto_depIdxs = []int32{
0, // 0: memos.api.v2.MemoRelation.type:type_name -> memos.api.v2.MemoRelation.Type
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_api_v2_memo_relation_service_proto_init() }
func file_api_v2_memo_relation_service_proto_init() {
if File_api_v2_memo_relation_service_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_api_v2_memo_relation_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MemoRelation); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_api_v2_memo_relation_service_proto_rawDesc,
NumEnums: 1,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_api_v2_memo_relation_service_proto_goTypes,
DependencyIndexes: file_api_v2_memo_relation_service_proto_depIdxs,
EnumInfos: file_api_v2_memo_relation_service_proto_enumTypes,
MessageInfos: file_api_v2_memo_relation_service_proto_msgTypes,
}.Build()
File_api_v2_memo_relation_service_proto = out.File
file_api_v2_memo_relation_service_proto_rawDesc = nil
file_api_v2_memo_relation_service_proto_goTypes = nil
file_api_v2_memo_relation_service_proto_depIdxs = nil
}

File diff suppressed because it is too large Load Diff

View File

@ -273,6 +273,74 @@ func local_request_MemoService_DeleteMemo_0(ctx context.Context, marshaler runti
}
func request_MemoService_SetMemoResources_0(ctx context.Context, marshaler runtime.Marshaler, client MemoServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SetMemoResourcesRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
msg, err := client.SetMemoResources(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_MemoService_SetMemoResources_0(ctx context.Context, marshaler runtime.Marshaler, server MemoServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SetMemoResourcesRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
msg, err := server.SetMemoResources(ctx, &protoReq)
return msg, metadata, err
}
func request_MemoService_ListMemoResources_0(ctx context.Context, marshaler runtime.Marshaler, client MemoServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListMemoResourcesRequest
var metadata runtime.ServerMetadata
@ -325,6 +393,126 @@ func local_request_MemoService_ListMemoResources_0(ctx context.Context, marshale
}
func request_MemoService_SetMemoRelations_0(ctx context.Context, marshaler runtime.Marshaler, client MemoServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SetMemoRelationsRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
msg, err := client.SetMemoRelations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_MemoService_SetMemoRelations_0(ctx context.Context, marshaler runtime.Marshaler, server MemoServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SetMemoRelationsRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
msg, err := server.SetMemoRelations(ctx, &protoReq)
return msg, metadata, err
}
func request_MemoService_ListMemoRelations_0(ctx context.Context, marshaler runtime.Marshaler, client MemoServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListMemoRelationsRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
msg, err := client.ListMemoRelations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_MemoService_ListMemoRelations_0(ctx context.Context, marshaler runtime.Marshaler, server MemoServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListMemoRelationsRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
msg, err := server.ListMemoRelations(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_MemoService_CreateMemoComment_0 = &utilities.DoubleArray{Encoding: map[string]int{"id": 0}, Base: []int{1, 2, 0, 0}, Check: []int{0, 1, 2, 2}}
)
@ -578,6 +766,31 @@ func RegisterMemoServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
})
mux.Handle("POST", pattern_MemoService_SetMemoResources_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.v2.MemoService/SetMemoResources", runtime.WithHTTPPathPattern("/api/v2/memos/{id}/resources"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_MemoService_SetMemoResources_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_SetMemoResources_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_MemoService_ListMemoResources_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -603,6 +816,56 @@ func RegisterMemoServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
})
mux.Handle("POST", pattern_MemoService_SetMemoRelations_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.v2.MemoService/SetMemoRelations", runtime.WithHTTPPathPattern("/api/v2/memos/{id}/relations"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_MemoService_SetMemoRelations_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_SetMemoRelations_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_MemoService_ListMemoRelations_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.v2.MemoService/ListMemoRelations", runtime.WithHTTPPathPattern("/api/v2/memos/{id}/relations"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_MemoService_ListMemoRelations_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_ListMemoRelations_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_MemoService_CreateMemoComment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -804,6 +1067,28 @@ func RegisterMemoServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
})
mux.Handle("POST", pattern_MemoService_SetMemoResources_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.v2.MemoService/SetMemoResources", runtime.WithHTTPPathPattern("/api/v2/memos/{id}/resources"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_MemoService_SetMemoResources_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_SetMemoResources_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_MemoService_ListMemoResources_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -826,6 +1111,50 @@ func RegisterMemoServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
})
mux.Handle("POST", pattern_MemoService_SetMemoRelations_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.v2.MemoService/SetMemoRelations", runtime.WithHTTPPathPattern("/api/v2/memos/{id}/relations"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_MemoService_SetMemoRelations_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_SetMemoRelations_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_MemoService_ListMemoRelations_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.v2.MemoService/ListMemoRelations", runtime.WithHTTPPathPattern("/api/v2/memos/{id}/relations"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_MemoService_ListMemoRelations_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_ListMemoRelations_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_MemoService_CreateMemoComment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -884,8 +1213,14 @@ var (
pattern_MemoService_DeleteMemo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v2", "memos", "id"}, ""))
pattern_MemoService_SetMemoResources_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v2", "memos", "id", "resources"}, ""))
pattern_MemoService_ListMemoResources_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v2", "memos", "id", "resources"}, ""))
pattern_MemoService_SetMemoRelations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v2", "memos", "id", "relations"}, ""))
pattern_MemoService_ListMemoRelations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v2", "memos", "id", "relations"}, ""))
pattern_MemoService_CreateMemoComment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v2", "memos", "id", "comments"}, ""))
pattern_MemoService_ListMemoComments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v2", "memos", "id", "comments"}, ""))
@ -902,8 +1237,14 @@ var (
forward_MemoService_DeleteMemo_0 = runtime.ForwardResponseMessage
forward_MemoService_SetMemoResources_0 = runtime.ForwardResponseMessage
forward_MemoService_ListMemoResources_0 = runtime.ForwardResponseMessage
forward_MemoService_SetMemoRelations_0 = runtime.ForwardResponseMessage
forward_MemoService_ListMemoRelations_0 = runtime.ForwardResponseMessage
forward_MemoService_CreateMemoComment_0 = runtime.ForwardResponseMessage
forward_MemoService_ListMemoComments_0 = runtime.ForwardResponseMessage

View File

@ -24,7 +24,10 @@ const (
MemoService_GetMemo_FullMethodName = "/memos.api.v2.MemoService/GetMemo"
MemoService_UpdateMemo_FullMethodName = "/memos.api.v2.MemoService/UpdateMemo"
MemoService_DeleteMemo_FullMethodName = "/memos.api.v2.MemoService/DeleteMemo"
MemoService_SetMemoResources_FullMethodName = "/memos.api.v2.MemoService/SetMemoResources"
MemoService_ListMemoResources_FullMethodName = "/memos.api.v2.MemoService/ListMemoResources"
MemoService_SetMemoRelations_FullMethodName = "/memos.api.v2.MemoService/SetMemoRelations"
MemoService_ListMemoRelations_FullMethodName = "/memos.api.v2.MemoService/ListMemoRelations"
MemoService_CreateMemoComment_FullMethodName = "/memos.api.v2.MemoService/CreateMemoComment"
MemoService_ListMemoComments_FullMethodName = "/memos.api.v2.MemoService/ListMemoComments"
)
@ -38,7 +41,10 @@ type MemoServiceClient interface {
GetMemo(ctx context.Context, in *GetMemoRequest, opts ...grpc.CallOption) (*GetMemoResponse, error)
UpdateMemo(ctx context.Context, in *UpdateMemoRequest, opts ...grpc.CallOption) (*UpdateMemoResponse, error)
DeleteMemo(ctx context.Context, in *DeleteMemoRequest, opts ...grpc.CallOption) (*DeleteMemoResponse, error)
SetMemoResources(ctx context.Context, in *SetMemoResourcesRequest, opts ...grpc.CallOption) (*SetMemoResourcesResponse, error)
ListMemoResources(ctx context.Context, in *ListMemoResourcesRequest, opts ...grpc.CallOption) (*ListMemoResourcesResponse, error)
SetMemoRelations(ctx context.Context, in *SetMemoRelationsRequest, opts ...grpc.CallOption) (*SetMemoRelationsResponse, error)
ListMemoRelations(ctx context.Context, in *ListMemoRelationsRequest, opts ...grpc.CallOption) (*ListMemoRelationsResponse, error)
CreateMemoComment(ctx context.Context, in *CreateMemoCommentRequest, opts ...grpc.CallOption) (*CreateMemoCommentResponse, error)
ListMemoComments(ctx context.Context, in *ListMemoCommentsRequest, opts ...grpc.CallOption) (*ListMemoCommentsResponse, error)
}
@ -96,6 +102,15 @@ func (c *memoServiceClient) DeleteMemo(ctx context.Context, in *DeleteMemoReques
return out, nil
}
func (c *memoServiceClient) SetMemoResources(ctx context.Context, in *SetMemoResourcesRequest, opts ...grpc.CallOption) (*SetMemoResourcesResponse, error) {
out := new(SetMemoResourcesResponse)
err := c.cc.Invoke(ctx, MemoService_SetMemoResources_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *memoServiceClient) ListMemoResources(ctx context.Context, in *ListMemoResourcesRequest, opts ...grpc.CallOption) (*ListMemoResourcesResponse, error) {
out := new(ListMemoResourcesResponse)
err := c.cc.Invoke(ctx, MemoService_ListMemoResources_FullMethodName, in, out, opts...)
@ -105,6 +120,24 @@ func (c *memoServiceClient) ListMemoResources(ctx context.Context, in *ListMemoR
return out, nil
}
func (c *memoServiceClient) SetMemoRelations(ctx context.Context, in *SetMemoRelationsRequest, opts ...grpc.CallOption) (*SetMemoRelationsResponse, error) {
out := new(SetMemoRelationsResponse)
err := c.cc.Invoke(ctx, MemoService_SetMemoRelations_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *memoServiceClient) ListMemoRelations(ctx context.Context, in *ListMemoRelationsRequest, opts ...grpc.CallOption) (*ListMemoRelationsResponse, error) {
out := new(ListMemoRelationsResponse)
err := c.cc.Invoke(ctx, MemoService_ListMemoRelations_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *memoServiceClient) CreateMemoComment(ctx context.Context, in *CreateMemoCommentRequest, opts ...grpc.CallOption) (*CreateMemoCommentResponse, error) {
out := new(CreateMemoCommentResponse)
err := c.cc.Invoke(ctx, MemoService_CreateMemoComment_FullMethodName, in, out, opts...)
@ -132,7 +165,10 @@ type MemoServiceServer interface {
GetMemo(context.Context, *GetMemoRequest) (*GetMemoResponse, error)
UpdateMemo(context.Context, *UpdateMemoRequest) (*UpdateMemoResponse, error)
DeleteMemo(context.Context, *DeleteMemoRequest) (*DeleteMemoResponse, error)
SetMemoResources(context.Context, *SetMemoResourcesRequest) (*SetMemoResourcesResponse, error)
ListMemoResources(context.Context, *ListMemoResourcesRequest) (*ListMemoResourcesResponse, error)
SetMemoRelations(context.Context, *SetMemoRelationsRequest) (*SetMemoRelationsResponse, error)
ListMemoRelations(context.Context, *ListMemoRelationsRequest) (*ListMemoRelationsResponse, error)
CreateMemoComment(context.Context, *CreateMemoCommentRequest) (*CreateMemoCommentResponse, error)
ListMemoComments(context.Context, *ListMemoCommentsRequest) (*ListMemoCommentsResponse, error)
mustEmbedUnimplementedMemoServiceServer()
@ -157,9 +193,18 @@ func (UnimplementedMemoServiceServer) UpdateMemo(context.Context, *UpdateMemoReq
func (UnimplementedMemoServiceServer) DeleteMemo(context.Context, *DeleteMemoRequest) (*DeleteMemoResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteMemo not implemented")
}
func (UnimplementedMemoServiceServer) SetMemoResources(context.Context, *SetMemoResourcesRequest) (*SetMemoResourcesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetMemoResources not implemented")
}
func (UnimplementedMemoServiceServer) ListMemoResources(context.Context, *ListMemoResourcesRequest) (*ListMemoResourcesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListMemoResources not implemented")
}
func (UnimplementedMemoServiceServer) SetMemoRelations(context.Context, *SetMemoRelationsRequest) (*SetMemoRelationsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetMemoRelations not implemented")
}
func (UnimplementedMemoServiceServer) ListMemoRelations(context.Context, *ListMemoRelationsRequest) (*ListMemoRelationsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListMemoRelations not implemented")
}
func (UnimplementedMemoServiceServer) CreateMemoComment(context.Context, *CreateMemoCommentRequest) (*CreateMemoCommentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateMemoComment not implemented")
}
@ -269,6 +314,24 @@ func _MemoService_DeleteMemo_Handler(srv interface{}, ctx context.Context, dec f
return interceptor(ctx, in, info, handler)
}
func _MemoService_SetMemoResources_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SetMemoResourcesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MemoServiceServer).SetMemoResources(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MemoService_SetMemoResources_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MemoServiceServer).SetMemoResources(ctx, req.(*SetMemoResourcesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _MemoService_ListMemoResources_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListMemoResourcesRequest)
if err := dec(in); err != nil {
@ -287,6 +350,42 @@ func _MemoService_ListMemoResources_Handler(srv interface{}, ctx context.Context
return interceptor(ctx, in, info, handler)
}
func _MemoService_SetMemoRelations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SetMemoRelationsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MemoServiceServer).SetMemoRelations(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MemoService_SetMemoRelations_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MemoServiceServer).SetMemoRelations(ctx, req.(*SetMemoRelationsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _MemoService_ListMemoRelations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListMemoRelationsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MemoServiceServer).ListMemoRelations(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MemoService_ListMemoRelations_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MemoServiceServer).ListMemoRelations(ctx, req.(*ListMemoRelationsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _MemoService_CreateMemoComment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreateMemoCommentRequest)
if err := dec(in); err != nil {
@ -350,10 +449,22 @@ var MemoService_ServiceDesc = grpc.ServiceDesc{
MethodName: "DeleteMemo",
Handler: _MemoService_DeleteMemo_Handler,
},
{
MethodName: "SetMemoResources",
Handler: _MemoService_SetMemoResources_Handler,
},
{
MethodName: "ListMemoResources",
Handler: _MemoService_ListMemoResources_Handler,
},
{
MethodName: "SetMemoRelations",
Handler: _MemoService_SetMemoRelations_Handler,
},
{
MethodName: "ListMemoRelations",
Handler: _MemoService_ListMemoRelations_Handler,
},
{
MethodName: "CreateMemoComment",
Handler: _MemoService_CreateMemoComment_Handler,

View File

@ -0,0 +1,52 @@
import { IconButton } from "@mui/joy";
import { useEffect } from "react";
import Icon from "@/components/Icon";
import OverflowTip from "@/components/kit/OverflowTip";
import { useTagStore } from "@/store/module";
interface Props {
onTagSelectorClick: (tag: string) => void;
}
const TagSelector = (props: Props) => {
const { onTagSelectorClick } = props;
const tagStore = useTagStore();
const tags = tagStore.state.tags;
useEffect(() => {
(async () => {
try {
await tagStore.fetchTags();
} catch (error) {
// do nothing.
}
})();
}, []);
return (
<IconButton className="relative group flex flex-row justify-center items-center p-1 w-auto h-auto mr-1 select-none rounded cursor-pointer text-gray-600 dark:text-gray-400 hover:bg-gray-300 dark:hover:bg-zinc-800 hover:shadow">
<Icon.Hash className="w-5 h-5 mx-auto" />
<div className="hidden flex-row justify-start items-start flex-wrap absolute top-8 left-0 mt-1 p-1 z-1 rounded w-52 h-auto max-h-48 overflow-y-auto font-mono shadow bg-zinc-200 dark:bg-zinc-600 group-hover:flex">
{tags.length > 0 ? (
tags.map((tag) => {
return (
<div
className="w-auto max-w-full text-black dark:text-gray-300 cursor-pointer rounded text-sm leading-6 px-2 hover:bg-zinc-300 dark:hover:bg-zinc-700 shrink-0"
onClick={() => onTagSelectorClick(tag)}
key={tag}
>
<OverflowTip>#{tag}</OverflowTip>
</div>
);
})
) : (
<p className="italic text-sm ml-2" onClick={(e) => e.stopPropagation()}>
No tags found
</p>
)}
</div>
</IconButton>
);
};
export default TagSelector;

View File

@ -0,0 +1,136 @@
import classNames from "classnames";
import { useEffect, useRef, useState } from "react";
import getCaretCoordinates from "textarea-caret";
import OverflowTip from "@/components/kit/OverflowTip";
import { useTagStore } from "@/store/module";
import { EditorRefActions } from ".";
type Props = {
editorRef: React.RefObject<HTMLTextAreaElement>;
editorActions: React.ForwardedRef<EditorRefActions>;
};
type Position = { left: number; top: number; height: number };
const TagSuggestions = ({ editorRef, editorActions }: Props) => {
const [position, setPosition] = useState<Position | null>(null);
const hide = () => setPosition(null);
const { state } = useTagStore();
const tagsRef = useRef(state.tags);
tagsRef.current = state.tags;
const [selected, select] = useState(0);
const selectedRef = useRef(selected);
selectedRef.current = selected;
const getCurrentWord = (): [word: string, startIndex: number] => {
const editor = editorRef.current;
if (!editor) return ["", 0];
const cursorPos = editor.selectionEnd;
const before = editor.value.slice(0, cursorPos).match(/\S*$/) || { 0: "", index: cursorPos };
const after = editor.value.slice(cursorPos).match(/^\S*/) || { 0: "" };
return [before[0] + after[0], before.index ?? cursorPos];
};
const suggestionsRef = useRef<string[]>([]);
suggestionsRef.current = (() => {
const input = getCurrentWord()[0].slice(1).toLowerCase();
const customMatches = (tag: string, input: string) => {
const tagLowerCase = tag.toLowerCase();
const inputLowerCase = input.toLowerCase();
let inputIndex = 0;
for (let i = 0; i < tagLowerCase.length; i++) {
if (tagLowerCase[i] === inputLowerCase[inputIndex]) {
inputIndex++;
if (inputIndex === inputLowerCase.length) {
return true;
}
}
}
return false;
};
const matchedTags = tagsRef.current.filter((tag) => customMatches(tag, input));
return matchedTags.slice(0, 5);
})();
const isVisibleRef = useRef(false);
isVisibleRef.current = !!(position && suggestionsRef.current.length > 0);
const autocomplete = (tag: string) => {
if (!editorActions || !("current" in editorActions) || !editorActions.current) return;
const [word, index] = getCurrentWord();
editorActions.current.removeText(index, word.length);
editorActions.current.insertText(`#${tag}`);
hide();
};
const handleKeyDown = (e: KeyboardEvent) => {
if (!isVisibleRef.current) return;
const suggestions = suggestionsRef.current;
const selected = selectedRef.current;
if (["Escape", "ArrowLeft", "ArrowRight"].includes(e.code)) hide();
if ("ArrowDown" === e.code) {
select((selected + 1) % suggestions.length);
e.preventDefault();
e.stopPropagation();
}
if ("ArrowUp" === e.code) {
select((selected - 1 + suggestions.length) % suggestions.length);
e.preventDefault();
e.stopPropagation();
}
if (["Enter", "Tab"].includes(e.code)) {
autocomplete(suggestions[selected]);
e.preventDefault();
e.stopPropagation();
}
};
const handleInput = () => {
if (!editorRef.current) return;
select(0);
const [word, index] = getCurrentWord();
const isActive = word.startsWith("#") && !word.slice(1).includes("#");
isActive ? setPosition(getCaretCoordinates(editorRef.current, index)) : hide();
};
const listenersAreRegisteredRef = useRef(false);
const registerListeners = () => {
const editor = editorRef.current;
if (!editor || listenersAreRegisteredRef.current) return;
editor.addEventListener("click", hide);
editor.addEventListener("blur", hide);
editor.addEventListener("keydown", handleKeyDown);
editor.addEventListener("input", handleInput);
listenersAreRegisteredRef.current = true;
};
useEffect(registerListeners, [!!editorRef.current]);
if (!isVisibleRef.current || !position) return null;
return (
<div
className="z-20 p-1 mt-1 -ml-2 absolute max-w-[12rem] gap-px rounded font-mono flex flex-col justify-start items-start overflow-auto shadow bg-zinc-200 dark:bg-zinc-600"
style={{ left: position.left, top: position.top + position.height }}
>
{suggestionsRef.current.map((tag, i) => (
<div
key={tag}
onMouseDown={() => autocomplete(tag)}
className={classNames(
"rounded p-1 px-2 w-full truncate text-sm dark:text-gray-300 cursor-pointer hover:bg-zinc-300 dark:hover:bg-zinc-700",
i === selected ? "bg-zinc-300 dark:bg-zinc-700" : ""
)}
>
<OverflowTip>#{tag}</OverflowTip>
</div>
))}
</div>
);
};
export default TagSuggestions;

View File

@ -0,0 +1,162 @@
import classNames from "classnames";
import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef } from "react";
import TagSuggestions from "./TagSuggestions";
export interface EditorRefActions {
focus: FunctionType;
scrollToCursor: FunctionType;
insertText: (text: string, prefix?: string, suffix?: string) => void;
removeText: (start: number, length: number) => void;
setContent: (text: string) => void;
getContent: () => string;
getSelectedContent: () => string;
getCursorPosition: () => number;
setCursorPosition: (startPos: number, endPos?: number) => void;
getCursorLineNumber: () => number;
getLine: (lineNumber: number) => string;
setLine: (lineNumber: number, text: string) => void;
}
interface Props {
className: string;
initialContent: string;
placeholder: string;
tools?: ReactNode;
onContentChange: (content: string) => void;
onPaste: (event: React.ClipboardEvent) => void;
}
const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef<EditorRefActions>) {
const { className, initialContent, placeholder, onPaste, onContentChange: handleContentChangeCallback } = props;
const editorRef = useRef<HTMLTextAreaElement>(null);
useEffect(() => {
if (editorRef.current && initialContent) {
editorRef.current.value = initialContent;
handleContentChangeCallback(initialContent);
}
}, []);
useEffect(() => {
if (editorRef.current) {
updateEditorHeight();
}
}, [editorRef.current?.value]);
const updateEditorHeight = () => {
if (editorRef.current) {
editorRef.current.style.height = "auto";
editorRef.current.style.height = (editorRef.current.scrollHeight ?? 0) + "px";
}
};
useImperativeHandle(
ref,
() => ({
focus: () => {
editorRef.current?.focus();
},
scrollToCursor: () => {
if (editorRef.current) {
editorRef.current.scrollTop = editorRef.current.scrollHeight;
}
},
insertText: (content = "", prefix = "", suffix = "") => {
if (!editorRef.current) {
return;
}
const cursorPosition = editorRef.current.selectionStart;
const endPosition = editorRef.current.selectionEnd;
const prevValue = editorRef.current.value;
const value =
prevValue.slice(0, cursorPosition) +
prefix +
(content || prevValue.slice(cursorPosition, endPosition)) +
suffix +
prevValue.slice(endPosition);
editorRef.current.value = value;
editorRef.current.focus();
editorRef.current.selectionEnd = endPosition + prefix.length + content.length;
handleContentChangeCallback(editorRef.current.value);
updateEditorHeight();
},
removeText: (start: number, length: number) => {
if (!editorRef.current) {
return;
}
const prevValue = editorRef.current.value;
const value = prevValue.slice(0, start) + prevValue.slice(start + length);
editorRef.current.value = value;
editorRef.current.focus();
editorRef.current.selectionEnd = start;
handleContentChangeCallback(editorRef.current.value);
updateEditorHeight();
},
setContent: (text: string) => {
if (editorRef.current) {
editorRef.current.value = text;
handleContentChangeCallback(editorRef.current.value);
updateEditorHeight();
}
},
getContent: (): string => {
return editorRef.current?.value ?? "";
},
getCursorPosition: (): number => {
return editorRef.current?.selectionStart ?? 0;
},
getSelectedContent: () => {
const start = editorRef.current?.selectionStart;
const end = editorRef.current?.selectionEnd;
return editorRef.current?.value.slice(start, end) ?? "";
},
setCursorPosition: (startPos: number, endPos?: number) => {
const _endPos = isNaN(endPos as number) ? startPos : (endPos as number);
editorRef.current?.setSelectionRange(startPos, _endPos);
},
getCursorLineNumber: () => {
const cursorPosition = editorRef.current?.selectionStart ?? 0;
const lines = editorRef.current?.value.slice(0, cursorPosition).split("\n") ?? [];
return lines.length - 1;
},
getLine: (lineNumber: number) => {
return editorRef.current?.value.split("\n")[lineNumber] ?? "";
},
setLine: (lineNumber: number, text: string) => {
const lines = editorRef.current?.value.split("\n") ?? [];
lines[lineNumber] = text;
if (editorRef.current) {
editorRef.current.value = lines.join("\n");
editorRef.current.focus();
handleContentChangeCallback(editorRef.current.value);
updateEditorHeight();
}
},
}),
[]
);
const handleEditorInput = useCallback(() => {
handleContentChangeCallback(editorRef.current?.value ?? "");
updateEditorHeight();
}, []);
return (
<div className={classNames("flex flex-col justify-start items-start relative w-full h-auto bg-inherit dark:text-gray-200", className)}>
<textarea
className="w-full h-full max-h-[300px] my-1 text-base resize-none overflow-x-hidden overflow-y-auto bg-transparent outline-none whitespace-pre-wrap word-break"
rows={1}
placeholder={placeholder}
ref={editorRef}
onPaste={onPaste}
onInput={handleEditorInput}
></textarea>
<TagSuggestions editorRef={editorRef} editorActions={ref} />
</div>
);
});
export default Editor;

View File

@ -0,0 +1,59 @@
import { useEffect } from "react";
import { useGlobalStore, useTagStore } from "@/store/module";
import MemoEditor from ".";
import { generateDialog } from "../Dialog";
import Icon from "../Icon";
interface Props extends DialogProps {
memoId?: MemoId;
relationList?: MemoRelation[];
}
const MemoEditorDialog: React.FC<Props> = ({ memoId, relationList, destroy }: Props) => {
const globalStore = useGlobalStore();
const tagStore = useTagStore();
const { systemStatus } = globalStore.state;
useEffect(() => {
tagStore.fetchTags();
}, []);
const handleCloseBtnClick = () => {
destroy();
};
return (
<>
<div className="dialog-header-container">
<div className="flex flex-row justify-start items-center">
<img className="w-5 h-auto rounded-full shadow" src={systemStatus.customizedProfile.logoUrl} alt="" />
<p className="ml-1 text-black opacity-80 dark:text-gray-200">{systemStatus.customizedProfile.name}</p>
</div>
<button className="btn close-btn" onClick={handleCloseBtnClick}>
<Icon.X />
</button>
</div>
<div className="flex flex-col justify-start items-start max-w-full w-[36rem]">
<MemoEditor
className="border-none !p-0 -mb-2"
cacheKey={`memo-editor-${memoId}`}
memoId={memoId}
relationList={relationList}
onConfirm={handleCloseBtnClick}
/>
</div>
</>
);
};
export default function showMemoEditorDialog(props: Pick<Props, "memoId" | "relationList"> = {}): void {
generateDialog(
{
className: "memo-editor-dialog",
dialogName: "memo-editor-dialog",
containerClassName: "dark:!bg-zinc-700",
},
MemoEditorDialog,
props
);
}

View File

@ -0,0 +1,56 @@
import { useEffect, useState } from "react";
import { useMemoCacheStore } from "@/store/v1";
import { MemoRelation, MemoRelation_Type } from "@/types/proto/api/v2/memo_relation_service";
import Icon from "../Icon";
interface Props {
relationList: MemoRelation[];
setRelationList: (relationList: MemoRelation[]) => void;
}
const RelationListView = (props: Props) => {
const { relationList, setRelationList } = props;
const memoCacheStore = useMemoCacheStore();
const [referencingMemoList, setReferencingMemoList] = useState<Memo[]>([]);
useEffect(() => {
(async () => {
const requests = relationList
.filter((relation) => relation.type === MemoRelation_Type.REFERENCE)
.map(async (relation) => {
return await memoCacheStore.getOrFetchMemoById(relation.relatedMemoId);
});
const list = await Promise.all(requests);
setReferencingMemoList(list);
})();
}, [relationList]);
const handleDeleteRelation = async (memo: Memo) => {
setRelationList(relationList.filter((relation) => relation.relatedMemoId !== memo.id));
};
return (
<>
{referencingMemoList.length > 0 && (
<div className="w-full flex flex-row gap-2 mt-2 flex-wrap">
{referencingMemoList.map((memo) => {
return (
<div
key={memo.id}
className="w-auto max-w-xs overflow-hidden flex flex-row justify-start items-center bg-gray-100 dark:bg-zinc-800 hover:opacity-80 rounded-md text-sm p-1 px-2 text-gray-500 cursor-pointer hover:line-through"
onClick={() => handleDeleteRelation(memo)}
>
<Icon.Link className="w-4 h-auto shrink-0 opacity-80" />
<span className="px-1 shrink-0 opacity-80">#{memo.id}</span>
<span className="max-w-full text-ellipsis whitespace-nowrap overflow-hidden">{memo.content}</span>
<Icon.X className="w-4 h-auto hover:opacity-80 shrink-0 ml-1" />
</div>
);
})}
</div>
)}
</>
);
};
export default RelationListView;

View File

@ -0,0 +1,42 @@
import { Resource } from "@/types/proto/api/v2/resource_service";
import Icon from "../Icon";
import ResourceIcon from "../ResourceIcon";
interface Props {
resourceList: Resource[];
setResourceList: (resourceList: Resource[]) => void;
}
const ResourceListView = (props: Props) => {
const { resourceList, setResourceList } = props;
const handleDeleteResource = async (resourceId: ResourceId) => {
setResourceList(resourceList.filter((resource) => resource.id !== resourceId));
};
return (
<>
{resourceList.length > 0 && (
<div className="w-full flex flex-row justify-start flex-wrap gap-2 mt-2">
{resourceList.map((resource) => {
return (
<div
key={resource.id}
className="max-w-full flex flex-row justify-start items-center flex-nowrap gap-x-1 bg-gray-100 dark:bg-zinc-800 px-2 py-1 rounded text-gray-500"
>
<ResourceIcon resource={resource} className="!w-4 !h-4 !opacity-100" />
<span className="text-sm max-w-[8rem] truncate">{resource.filename}</span>
<Icon.X
className="w-4 h-auto cursor-pointer opacity-60 hover:opacity-100"
onClick={() => handleDeleteResource(resource.id)}
/>
</div>
);
})}
</div>
)}
</>
);
};
export default ResourceListView;

View File

@ -0,0 +1,454 @@
import { Select, Option, Button, IconButton, Divider } from "@mui/joy";
import { uniqBy } from "lodash-es";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import useLocalStorage from "react-use/lib/useLocalStorage";
import { memoServiceClient } from "@/grpcweb";
import { TAB_SPACE_WIDTH, UNKNOWN_ID } from "@/helpers/consts";
import { useGlobalStore, useResourceStore } from "@/store/module";
import { useMemoV1Store, useUserV1Store } from "@/store/v1";
import { MemoRelation, MemoRelation_Type } from "@/types/proto/api/v2/memo_relation_service";
import { Visibility } from "@/types/proto/api/v2/memo_service";
import { Resource } from "@/types/proto/api/v2/resource_service";
import { UserSetting } from "@/types/proto/api/v2/user_service";
import { useTranslate } from "@/utils/i18n";
import { convertVisibilityFromString, convertVisibilityToString } from "@/utils/memo";
import showCreateMemoRelationDialog from "../CreateMemoRelationDialog";
import showCreateResourceDialog from "../CreateResourceDialog";
import Icon from "../Icon";
import VisibilityIconV1 from "../VisibilityIconV1";
import TagSelector from "./ActionButton/TagSelector";
import Editor, { EditorRefActions } from "./Editor";
import RelationListView from "./RelationListView";
import ResourceListView from "./ResourceListView";
interface Props {
className?: string;
editorClassName?: string;
cacheKey?: string;
memoId?: number;
relationList?: MemoRelation[];
onConfirm?: (memoId: number) => void;
}
interface State {
memoVisibility: Visibility;
resourceList: Resource[];
relationList: MemoRelation[];
isUploadingResource: boolean;
isRequesting: boolean;
}
const MemoEditor = (props: Props) => {
const { className, editorClassName, cacheKey, memoId, onConfirm } = props;
const { i18n } = useTranslation();
const t = useTranslate();
const contentCacheKey = `memo-editor-${cacheKey}`;
const [contentCache, setContentCache] = useLocalStorage<string>(contentCacheKey, "");
const {
state: { systemStatus },
} = useGlobalStore();
const userV1Store = useUserV1Store();
const memoStore = useMemoV1Store();
const resourceStore = useResourceStore();
const [state, setState] = useState<State>({
memoVisibility: Visibility.PRIVATE,
resourceList: [],
relationList: props.relationList ?? [],
isUploadingResource: false,
isRequesting: false,
});
const [hasContent, setHasContent] = useState<boolean>(false);
const editorRef = useRef<EditorRefActions>(null);
const userSetting = userV1Store.userSetting as UserSetting;
const referenceRelations = memoId
? state.relationList.filter(
(relation) => relation.memoId === memoId && relation.relatedMemoId !== memoId && relation.type === MemoRelation_Type.REFERENCE
)
: state.relationList.filter((relation) => relation.type === MemoRelation_Type.REFERENCE);
useEffect(() => {
editorRef.current?.setContent(contentCache || "");
}, []);
useEffect(() => {
let visibility = userSetting.memoVisibility;
if (systemStatus.disablePublicMemos && visibility === "PUBLIC") {
visibility = "PRIVATE";
}
setState((prevState) => ({
...prevState,
memoVisibility: convertVisibilityFromString(visibility),
}));
}, [userSetting.memoVisibility, systemStatus.disablePublicMemos]);
useEffect(() => {
if (memoId) {
memoStore.getOrFetchMemoById(memoId ?? UNKNOWN_ID).then((memo) => {
if (memo) {
handleEditorFocus();
setState((prevState) => ({
...prevState,
memoVisibility: memo.visibility,
}));
if (!contentCache) {
editorRef.current?.setContent(memo.content ?? "");
}
}
});
}
}, [memoId]);
const handleKeyDown = (event: React.KeyboardEvent) => {
if (!editorRef.current) {
return;
}
const isMetaKey = event.ctrlKey || event.metaKey;
if (isMetaKey) {
if (event.key === "Enter") {
handleSaveBtnClick();
return;
}
}
if (event.key === "Tab") {
event.preventDefault();
const tabSpace = " ".repeat(TAB_SPACE_WIDTH);
const cursorPosition = editorRef.current.getCursorPosition();
const selectedContent = editorRef.current.getSelectedContent();
editorRef.current.insertText(tabSpace);
if (selectedContent) {
editorRef.current.setCursorPosition(cursorPosition + TAB_SPACE_WIDTH);
}
return;
}
};
const handleMemoVisibilityChange = (visibility: Visibility) => {
setState((prevState) => ({
...prevState,
memoVisibility: visibility,
}));
};
const handleUploadFileBtnClick = () => {
showCreateResourceDialog({
onConfirm: (resourceList) => {
setState((prevState) => ({
...prevState,
resourceList: [...prevState.resourceList, ...resourceList],
}));
},
});
};
const handleAddMemoRelationBtnClick = () => {
showCreateMemoRelationDialog({
onConfirm: (memoIdList) => {
setState((prevState) => ({
...prevState,
relationList: uniqBy(
[
...memoIdList.map((id) => ({ memoId: memoId || UNKNOWN_ID, relatedMemoId: id, type: MemoRelation_Type.REFERENCE })),
...state.relationList,
].filter((relation) => relation.relatedMemoId !== (memoId || UNKNOWN_ID)),
"relatedMemoId"
),
}));
},
});
};
const handleSetResourceList = (resourceList: Resource[]) => {
setState((prevState) => ({
...prevState,
resourceList,
}));
};
const handleSetRelationList = (relationList: MemoRelation[]) => {
setState((prevState) => ({
...prevState,
relationList,
}));
};
const handleUploadResource = async (file: File) => {
setState((state) => {
return {
...state,
isUploadingResource: true,
};
});
let resource = undefined;
try {
resource = await resourceStore.createResourceWithBlob(file);
} catch (error: any) {
console.error(error);
toast.error(typeof error === "string" ? error : error.response.data.message);
}
setState((state) => {
return {
...state,
isUploadingResource: false,
};
});
return resource;
};
const uploadMultiFiles = async (files: FileList) => {
const uploadedResourceList: Resource[] = [];
for (const file of files) {
const resource = await handleUploadResource(file);
if (resource) {
uploadedResourceList.push(resource);
if (memoId) {
await resourceStore.updateResource({
resource: Resource.fromPartial({
id: resource.id,
memoId,
}),
updateMask: ["memo_id"],
});
}
}
}
if (uploadedResourceList.length > 0) {
setState((prevState) => ({
...prevState,
resourceList: [...prevState.resourceList, ...uploadedResourceList],
}));
}
};
const handleDropEvent = async (event: React.DragEvent) => {
if (event.dataTransfer && event.dataTransfer.files.length > 0) {
event.preventDefault();
await uploadMultiFiles(event.dataTransfer.files);
}
};
const handlePasteEvent = async (event: React.ClipboardEvent) => {
if (event.clipboardData && event.clipboardData.files.length > 0) {
event.preventDefault();
await uploadMultiFiles(event.clipboardData.files);
}
};
const handleContentChange = (content: string) => {
setHasContent(content !== "");
if (content !== "") {
setContentCache(content);
} else {
localStorage.removeItem(contentCacheKey);
}
};
const handleSaveBtnClick = async () => {
if (state.isRequesting) {
return;
}
setState((state) => {
return {
...state,
isRequesting: true,
};
});
const content = editorRef.current?.getContent() ?? "";
try {
if (memoId && memoId !== UNKNOWN_ID) {
const prevMemo = await memoStore.getOrFetchMemoById(memoId ?? UNKNOWN_ID);
if (prevMemo) {
const memo = await memoStore.updateMemo(
{
id: prevMemo.id,
content,
visibility: state.memoVisibility,
},
["content", "visibility"]
);
await memoServiceClient.setMemoResources({
id: memo.id,
resources: state.resourceList,
});
await memoServiceClient.setMemoRelations({
id: memo.id,
relations: state.relationList,
});
if (onConfirm) {
onConfirm(memo.id);
}
}
} else {
const memo = await memoStore.createMemo({
content,
visibility: state.memoVisibility,
});
await memoServiceClient.setMemoResources({
id: memo.id,
resources: state.resourceList,
});
await memoServiceClient.setMemoRelations({
id: memo.id,
relations: state.relationList,
});
if (onConfirm) {
onConfirm(memo.id);
}
}
editorRef.current?.setContent("");
} catch (error: any) {
console.error(error);
toast.error(error.response.data.message);
}
setState((state) => {
return {
...state,
isRequesting: false,
};
});
setState((prevState) => ({
...prevState,
resourceList: [],
}));
};
const handleCheckBoxBtnClick = () => {
if (!editorRef.current) {
return;
}
const currentPosition = editorRef.current?.getCursorPosition();
const currentLineNumber = editorRef.current?.getCursorLineNumber();
const currentLine = editorRef.current?.getLine(currentLineNumber);
let newLine = "";
let cursorChange = 0;
if (/^- \[( |x|X)\] /.test(currentLine)) {
newLine = currentLine.replace(/^- \[( |x|X)\] /, "");
cursorChange = -6;
} else if (/^\d+\. |- /.test(currentLine)) {
const match = currentLine.match(/^\d+\. |- /) ?? [""];
newLine = currentLine.replace(/^\d+\. |- /, "- [ ] ");
cursorChange = -match[0].length + 6;
} else {
newLine = "- [ ] " + currentLine;
cursorChange = 6;
}
editorRef.current?.setLine(currentLineNumber, newLine);
editorRef.current.setCursorPosition(currentPosition + cursorChange);
editorRef.current?.scrollToCursor();
};
const handleCodeBlockBtnClick = () => {
if (!editorRef.current) {
return;
}
const cursorPosition = editorRef.current.getCursorPosition();
const prevValue = editorRef.current.getContent().slice(0, cursorPosition);
if (prevValue === "" || prevValue.endsWith("\n")) {
editorRef.current?.insertText("", "```\n", "\n```");
} else {
editorRef.current?.insertText("", "\n```\n", "\n```");
}
editorRef.current?.scrollToCursor();
};
const handleTagSelectorClick = useCallback((tag: string) => {
editorRef.current?.insertText(`#${tag} `);
}, []);
const handleEditorFocus = () => {
editorRef.current?.focus();
};
const editorConfig = useMemo(
() => ({
className: editorClassName ?? "",
initialContent: "",
placeholder: t("editor.placeholder"),
onContentChange: handleContentChange,
onPaste: handlePasteEvent,
}),
[i18n.language]
);
const allowSave = (hasContent || state.resourceList.length > 0) && !state.isUploadingResource && !state.isRequesting;
return (
<div
className={`${
className ?? ""
} relative w-full flex flex-col justify-start items-start bg-white dark:bg-zinc-700 px-4 pt-4 rounded-lg border border-gray-200 dark:border-zinc-600`}
tabIndex={0}
onKeyDown={handleKeyDown}
onDrop={handleDropEvent}
onFocus={handleEditorFocus}
>
<Editor ref={editorRef} {...editorConfig} />
<div className="relative w-full flex flex-row justify-between items-center pt-2">
<div className="flex flex-row justify-start items-center">
<TagSelector onTagSelectorClick={(tag) => handleTagSelectorClick(tag)} />
<IconButton
className="flex flex-row justify-center items-center p-1 w-auto h-auto mr-1 select-none rounded cursor-pointer text-gray-600 dark:text-gray-400 hover:bg-gray-300 dark:hover:bg-zinc-800 hover:shadow"
onClick={handleUploadFileBtnClick}
>
<Icon.Image className="w-5 h-5 mx-auto" />
</IconButton>
<IconButton
className="flex flex-row justify-center items-center p-1 w-auto h-auto mr-1 select-none rounded cursor-pointer text-gray-600 dark:text-gray-400 hover:bg-gray-300 dark:hover:bg-zinc-800 hover:shadow"
onClick={handleAddMemoRelationBtnClick}
>
<Icon.Link className="w-5 h-5 mx-auto" />
</IconButton>
<IconButton
className="flex flex-row justify-center items-center p-1 w-auto h-auto mr-1 select-none rounded cursor-pointer text-gray-600 dark:text-gray-400 hover:bg-gray-300 dark:hover:bg-zinc-800 hover:shadow"
onClick={handleCheckBoxBtnClick}
>
<Icon.CheckSquare className="w-5 h-5 mx-auto" />
</IconButton>
<IconButton
className="flex flex-row justify-center items-center p-1 w-auto h-auto mr-1 select-none rounded cursor-pointer text-gray-600 dark:text-gray-400 hover:bg-gray-300 dark:hover:bg-zinc-800 hover:shadow"
onClick={handleCodeBlockBtnClick}
>
<Icon.Code className="w-5 h-5 mx-auto" />
</IconButton>
</div>
</div>
<ResourceListView resourceList={state.resourceList} setResourceList={handleSetResourceList} />
<RelationListView relationList={referenceRelations} setRelationList={handleSetRelationList} />
<Divider className="!mt-2" />
<div className="w-full flex flex-row justify-between items-center py-3 dark:border-t-zinc-500">
<div className="relative flex flex-row justify-start items-center" onFocus={(e) => e.stopPropagation()}>
<Select
variant="plain"
value={state.memoVisibility}
startDecorator={<VisibilityIconV1 visibility={state.memoVisibility} />}
onChange={(_, visibility) => {
if (visibility) {
handleMemoVisibilityChange(visibility);
}
}}
>
{[Visibility.PRIVATE, Visibility.PROTECTED, Visibility.PUBLIC].map((item) => (
<Option key={item} value={item} className="whitespace-nowrap">
{t(`memo.visibility.${convertVisibilityToString(item).toLowerCase()}` as any)}
</Option>
))}
</Select>
</div>
<div className="shrink-0 flex flex-row justify-end items-center">
<Button color="success" disabled={!allowSave} onClick={handleSaveBtnClick}>
{t("editor.save")}
</Button>
</div>
</div>
</div>
);
};
export default MemoEditor;

View File

@ -0,0 +1,83 @@
import { Tooltip } from "@mui/joy";
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { useMemoV1Store } from "@/store/v1";
import { MemoRelation } from "@/types/proto/api/v2/memo_relation_service";
import { Memo } from "@/types/proto/api/v2/memo_service";
import Icon from "./Icon";
interface Props {
memo: Memo;
relationList: MemoRelation[];
}
const MemoRelationListViewV1 = (props: Props) => {
const { memo, relationList } = props;
const memoStore = useMemoV1Store();
const [referencingMemoList, setReferencingMemoList] = useState<Memo[]>([]);
const [referencedMemoList, setReferencedMemoList] = useState<Memo[]>([]);
useEffect(() => {
(async () => {
const referencingMemoList = await Promise.all(
relationList
.filter((relation) => relation.memoId === memo.id && relation.relatedMemoId !== memo.id)
.map((relation) => memoStore.getOrFetchMemoById(relation.relatedMemoId))
);
setReferencingMemoList(referencingMemoList);
const referencedMemoList = await Promise.all(
relationList
.filter((relation) => relation.memoId !== memo.id && relation.relatedMemoId === memo.id)
.map((relation) => memoStore.getOrFetchMemoById(relation.memoId))
);
setReferencedMemoList(referencedMemoList);
})();
}, [memo, relationList]);
return (
<>
{referencingMemoList.length > 0 && (
<div className="w-full mt-2 flex flex-row justify-start items-center flex-wrap gap-2">
{referencingMemoList.map((memo) => {
return (
<div key={memo.id} className="block w-auto max-w-[50%]">
<Link
className="px-2 border rounded-md w-auto text-sm leading-6 flex flex-row justify-start items-center flex-nowrap text-gray-600 dark:text-gray-300 dark:border-gray-600 hover:shadow hover:opacity-80"
to={`/m/${memo.id}`}
>
<Tooltip title="Reference" placement="top">
<Icon.Link className="w-4 h-auto shrink-0 opacity-70" />
</Tooltip>
<span className="opacity-70 mx-1">#{memo.id}</span>
<span className="truncate">{memo.content}</span>
</Link>
</div>
);
})}
</div>
)}
{referencedMemoList.length > 0 && (
<div className="w-full mt-2 flex flex-row justify-start items-center flex-wrap gap-2">
{referencedMemoList.map((memo) => {
return (
<div key={memo.id} className="block w-auto max-w-[50%]">
<Link
className="px-2 border rounded-md w-auto text-sm leading-6 flex flex-row justify-start items-center flex-nowrap text-gray-600 dark:text-gray-300 dark:border-gray-600 hover:shadow hover:opacity-80"
to={`/m/${memo.id}`}
>
<Tooltip title="Backlink" placement="top">
<Icon.Milestone className="w-4 h-auto shrink-0 opacity-70" />
</Tooltip>
<span className="opacity-70 mx-1">#{memo.id}</span>
<span className="truncate">{memo.content}</span>
</Link>
</div>
);
})}
</div>
)}
</>
);
};
export default MemoRelationListViewV1;

View File

@ -0,0 +1,45 @@
import { useEffect, useState } from "react";
import Icon from "@/components/Icon";
import MemoContentV1 from "@/components/MemoContentV1";
import MemoResourceListView from "@/components/MemoResourceListView";
import { getTimeString } from "@/helpers/datetime";
import { useMemoV1Store } from "@/store/v1";
import { MemoRelation, MemoRelation_Type } from "@/types/proto/api/v2/memo_relation_service";
import { Memo } from "@/types/proto/api/v2/memo_service";
import { Resource } from "@/types/proto/api/v2/resource_service";
import MemoRelationListViewV1 from "./MemoRelationListViewV1";
interface Props {
memo: Memo;
}
const TimelineMemo = (props: Props) => {
const { memo } = props;
const memoStore = useMemoV1Store();
const [resources, setResources] = useState<Resource[]>([]);
const [relations, setRelations] = useState<MemoRelation[]>([]);
useEffect(() => {
memoStore.fetchMemoResources(memo.id).then((resources: Resource[]) => {
setResources(resources);
});
memoStore.fetchMemoRelations(memo.id).then((relations: MemoRelation[]) => {
setRelations(relations.filter((relation) => relation.type === MemoRelation_Type.REFERENCE));
});
}, [memo.id]);
return (
<div className="relative w-full flex flex-col justify-start items-start">
<div className="w-full flex flex-row justify-start items-center mt-0.5 mb-1 text-sm font-mono text-gray-500 dark:text-gray-400">
<span className="opacity-80">{getTimeString(memo.displayTime)}</span>
<Icon.Dot className="w-5 h-auto opacity-60" />
<span className="opacity-60">#{memo.id}</span>
</div>
<MemoContentV1 content={memo.content} nodes={memo.nodes} />
<MemoResourceListView resourceList={resources} />
<MemoRelationListViewV1 memo={memo} relationList={relations} />
</div>
);
};
export default TimelineMemo;

View File

@ -0,0 +1,27 @@
import classNames from "classnames";
import { Visibility } from "@/types/proto/api/v2/memo_service";
import Icon from "./Icon";
interface Props {
visibility: Visibility;
}
const VisibilityIcon = (props: Props) => {
const { visibility } = props;
let VIcon = null;
if (visibility === Visibility.PRIVATE) {
VIcon = Icon.Lock;
} else if (visibility === Visibility.PROTECTED) {
VIcon = Icon.Users;
} else if (visibility === Visibility.PUBLIC) {
VIcon = Icon.Globe2;
}
if (!VIcon) {
return null;
}
return <VIcon className={classNames("w-4 h-auto text-gray-400")} />;
};
export default VisibilityIcon;

View File

@ -1,61 +1,42 @@
import { Button } from "@mui/joy";
import { last } from "lodash-es";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import useToggle from "react-use/lib/useToggle";
import Empty from "@/components/Empty";
import Icon from "@/components/Icon";
import MemoContentV1 from "@/components/MemoContentV1";
import MemoEditor from "@/components/MemoEditor";
import MemoRelationListView from "@/components/MemoRelationListView";
import MemoResourceListView from "@/components/MemoResourceListView";
import MemoEditorV1 from "@/components/MemoEditorV1";
import MobileHeader from "@/components/MobileHeader";
import TimelineMemo from "@/components/TimelineMemo";
import DatePicker from "@/components/kit/DatePicker";
import { DAILY_TIMESTAMP, DEFAULT_MEMO_LIMIT } from "@/helpers/consts";
import { getDateStampByDate, getNormalizedDateString, getTimeStampByDate, getTimeString } from "@/helpers/datetime";
import { DAILY_TIMESTAMP } from "@/helpers/consts";
import { getDateStampByDate, getNormalizedDateString, getTimeStampByDate } from "@/helpers/datetime";
import useCurrentUser from "@/hooks/useCurrentUser";
import { useMemoStore } from "@/store/module";
import { extractUsernameFromName } from "@/store/v1";
import { useMemoV1Store } from "@/store/v1";
import { Memo } from "@/types/proto/api/v2/memo_service";
import { useTranslate } from "@/utils/i18n";
const Timeline = () => {
const t = useTranslate();
const memoStore = useMemoStore();
const memoStore = useMemoV1Store();
const currentUser = useCurrentUser();
const currentDateStamp = getDateStampByDate(getNormalizedDateString()) as number;
const [selectedDateStamp, setSelectedDateStamp] = useState<number>(currentDateStamp as number);
const [memos, setMemos] = useState<Memo[]>([]);
const [showDatePicker, toggleShowDatePicker] = useToggle(false);
const dailyMemos = memoStore.state.memos
.filter((m) => {
const displayTimestamp = getTimeStampByDate(m.displayTs);
const selectedDateStampWithOffset = selectedDateStamp;
return (
m.rowStatus === "NORMAL" &&
m.creatorUsername === extractUsernameFromName(currentUser.name) &&
displayTimestamp >= selectedDateStampWithOffset &&
displayTimestamp < selectedDateStampWithOffset + DAILY_TIMESTAMP
);
})
.sort((a, b) => getTimeStampByDate(a.displayTs) - getTimeStampByDate(b.displayTs));
const sortedMemos = memos.sort((a, b) => getTimeStampByDate(a.createTime) - getTimeStampByDate(b.createTime));
useEffect(() => {
let offset = 0;
const fetchMoreMemos = async () => {
try {
const fetchedMemos = await memoStore.fetchMemos("", DEFAULT_MEMO_LIMIT, offset);
offset += fetchedMemos.length;
if (fetchedMemos.length === DEFAULT_MEMO_LIMIT) {
const lastMemo = last(fetchedMemos);
if (lastMemo && lastMemo.displayTs > selectedDateStamp) {
await fetchMoreMemos();
}
}
} catch (error: any) {
console.error(error);
toast.error(error.response.data.message);
}
};
fetchMoreMemos();
const filters = [
`creator == "${currentUser.name}"`,
`created_ts_after == ${selectedDateStamp / 1000}`,
`created_ts_before == ${(selectedDateStamp + DAILY_TIMESTAMP) / 1000}`,
];
memoStore
.fetchMemos({
filter: filters.join(" && "),
})
.then((memos: Memo[]) => {
setMemos(memos);
});
}, [selectedDateStamp]);
const handleDataPickerChange = (datestamp: number): void => {
@ -63,6 +44,12 @@ const Timeline = () => {
toggleShowDatePicker(false);
};
const handleMemoCreate = async (id: number) => {
await memoStore.getOrFetchMemoById(id).then((memo: Memo) => {
setMemos([memo, ...memos]);
});
};
return (
<section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8">
<MobileHeader />
@ -96,28 +83,21 @@ const Timeline = () => {
/>
</div>
<div className="w-full h-auto flex flex-col justify-start items-start px-2 pb-4 bg-white dark:bg-zinc-700">
{dailyMemos.length === 0 && (
{sortedMemos.length === 0 && (
<div className="w-full mt-4 mb-8 flex flex-col justify-center items-center italic">
<Empty />
<p className="mt-4 text-gray-600 dark:text-gray-400">{t("message.no-data")}</p>
</div>
)}
<div className="flex flex-col justify-start items-start w-full mt-2">
{dailyMemos.map((memo, index) => (
{sortedMemos.map((memo, index) => (
<div
key={`${memo.id}-${memo.displayTs}`}
key={`${memo.id}-${memo.createTime}`}
className="relative w-full flex flex-col justify-start items-start pl-8 sm:pl-12 pt-2 pb-4"
>
<div className="w-full flex flex-row justify-start items-center mt-0.5 mb-1 text-sm font-mono text-gray-500 dark:text-gray-400">
<span className="opacity-80">{getTimeString(memo.displayTs)}</span>
<Icon.Dot className="w-5 h-auto opacity-60" />
<span className="opacity-60">#{memo.id}</span>
</div>
<MemoContentV1 content={memo.content} />
<MemoResourceListView resourceList={memo.resourceList} />
<MemoRelationListView memo={memo} relationList={memo.relationList.filter((relation) => relation.type === "REFERENCE")} />
<TimelineMemo memo={memo} />
<div className="absolute left-1 sm:left-2 top-3 h-full">
{index !== dailyMemos.length - 1 && (
{index !== sortedMemos.length - 1 && (
<div className="absolute top-2 left-[7px] h-full w-0.5 bg-gray-400 dark:bg-gray-500 block"></div>
)}
<div className="border-4 rounded-full border-white relative dark:border-zinc-700">
@ -126,10 +106,9 @@ const Timeline = () => {
</div>
</div>
))}
{selectedDateStamp === currentDateStamp && (
<div className="w-full pl-0 sm:pl-12 sm:mt-4">
<MemoEditor cacheKey="timeline-editor" />
<MemoEditorV1 cacheKey="timeline-editor" onConfirm={handleMemoCreate} />
</div>
)}
</div>

View File

@ -1,11 +1,15 @@
import { create } from "zustand";
import { combine } from "zustand/middleware";
import { memoServiceClient } from "@/grpcweb";
import { Memo } from "@/types/proto/api/v2/memo_service";
import { CreateMemoRequest, ListMemosRequest, Memo } from "@/types/proto/api/v2/memo_service";
export const useMemoV1Store = create(
combine({ memoById: new Map<number, Memo>() }, (set, get) => ({
getState: () => get(),
fetchMemos: async (request: Partial<ListMemosRequest>) => {
const { memos } = await memoServiceClient.listMemos(request);
return memos;
},
getOrFetchMemoById: async (id: MemoId) => {
const memo = get().memoById.get(id);
if (memo) {
@ -29,6 +33,18 @@ export const useMemoV1Store = create(
getMemoById: (id: number) => {
return get().memoById.get(id);
},
createMemo: async (request: CreateMemoRequest) => {
const { memo } = await memoServiceClient.createMemo(request);
if (!memo) {
throw new Error("Memo not found");
}
set((state) => {
state.memoById.set(memo.id, memo);
return state;
});
return memo;
},
updateMemo: async (update: Partial<Memo>, updateMask: string[]) => {
const { memo } = await memoServiceClient.updateMemo({
id: update.id!,
@ -55,5 +71,17 @@ export const useMemoV1Store = create(
return state;
});
},
fetchMemoResources: async (id: number) => {
const { resources } = await memoServiceClient.listMemoResources({
id,
});
return resources;
},
fetchMemoRelations: async (id: number) => {
const { relations } = await memoServiceClient.listMemoRelations({
id,
});
return relations;
},
}))
);

27
web/src/utils/memo.ts Normal file
View File

@ -0,0 +1,27 @@
import { Visibility } from "@/types/proto/api/v2/memo_service";
export const convertVisibilityFromString = (visibility: string) => {
switch (visibility) {
case "PUBLIC":
return Visibility.PUBLIC;
case "PROTECTED":
return Visibility.PROTECTED;
case "PRIVATE":
return Visibility.PRIVATE;
default:
return Visibility.PUBLIC;
}
};
export const convertVisibilityToString = (visibility: Visibility) => {
switch (visibility) {
case Visibility.PUBLIC:
return "PUBLIC";
case Visibility.PROTECTED:
return "PROTECTED";
case Visibility.PRIVATE:
return "PRIVATE";
default:
return "PRIVATE";
}
};