mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
feat: impl resources list page
This commit is contained in:
@ -2,11 +2,13 @@ package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
|
||||
"github.com/usememos/memos/store"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
type ResourceService struct {
|
||||
@ -29,6 +31,7 @@ func (s *ResourceService) ListResources(ctx context.Context, _ *apiv2pb.ListReso
|
||||
}
|
||||
resources, err := s.Store.ListResources(ctx, &store.FindResource{
|
||||
CreatorID: &user.ID,
|
||||
HasRelatedMemo: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to list tags: %v", err)
|
||||
@ -44,7 +47,7 @@ func (s *ResourceService) ListResources(ctx context.Context, _ *apiv2pb.ListReso
|
||||
func convertResourceFromStore(resource *store.Resource) *apiv2pb.Resource {
|
||||
return &apiv2pb.Resource{
|
||||
Id: resource.ID,
|
||||
CreatedTs: resource.CreatedTs,
|
||||
CreatedTs: timestamppb.New(time.Unix(resource.CreatedTs, 0)),
|
||||
Filename: resource.Filename,
|
||||
ExternalLink: resource.ExternalLink,
|
||||
Type: resource.Type,
|
||||
|
@ -3,6 +3,7 @@ syntax = "proto3";
|
||||
package memos.api.v2;
|
||||
|
||||
import "google/api/annotations.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
option go_package = "gen/api/v2";
|
||||
|
||||
@ -14,7 +15,7 @@ service ResourceService {
|
||||
|
||||
message Resource {
|
||||
int32 id = 1;
|
||||
int64 created_ts = 2;
|
||||
google.protobuf.Timestamp created_ts = 2;
|
||||
string filename = 3;
|
||||
string external_link = 4;
|
||||
string type = 5;
|
||||
|
@ -257,7 +257,7 @@
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| id | [int32](#int32) | | |
|
||||
| created_ts | [int64](#int64) | | |
|
||||
| created_ts | [google.protobuf.Timestamp](#google-protobuf-Timestamp) | | |
|
||||
| filename | [string](#string) | | |
|
||||
| external_link | [string](#string) | | |
|
||||
| type | [string](#string) | | |
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
_ "google.golang.org/genproto/googleapis/api/annotations"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
@ -27,7 +28,7 @@ type Resource struct {
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
CreatedTs int64 `protobuf:"varint,2,opt,name=created_ts,json=createdTs,proto3" json:"created_ts,omitempty"`
|
||||
CreatedTs *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=created_ts,json=createdTs,proto3" json:"created_ts,omitempty"`
|
||||
Filename string `protobuf:"bytes,3,opt,name=filename,proto3" json:"filename,omitempty"`
|
||||
ExternalLink string `protobuf:"bytes,4,opt,name=external_link,json=externalLink,proto3" json:"external_link,omitempty"`
|
||||
Type string `protobuf:"bytes,5,opt,name=type,proto3" json:"type,omitempty"`
|
||||
@ -74,11 +75,11 @@ func (x *Resource) GetId() int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Resource) GetCreatedTs() int64 {
|
||||
func (x *Resource) GetCreatedTs() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.CreatedTs
|
||||
}
|
||||
return 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Resource) GetFilename() string {
|
||||
@ -208,48 +209,52 @@ var file_api_v2_resource_service_proto_rawDesc = []byte{
|
||||
0x65, 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, 0x1a, 0x1c, 0x67,
|
||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe3, 0x01, 0x0a, 0x08,
|
||||
0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61,
|
||||
0x74, 0x65, 0x64, 0x5f, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72,
|
||||
0x65, 0x61, 0x74, 0x65, 0x64, 0x54, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f,
|
||||
0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x78, 0x74, 0x65,
|
||||
0x72, 0x6e, 0x61, 0x6c, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65,
|
||||
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04,
|
||||
0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65,
|
||||
0x12, 0x2b, 0x0a, 0x0f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x6d, 0x6f,
|
||||
0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x0d, 0x72, 0x65, 0x6c,
|
||||
0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x12, 0x0a,
|
||||
0x10, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x5f, 0x69,
|
||||
0x64, 0x22, 0x16, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
|
||||
0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4d, 0x0a, 0x15, 0x4c, 0x69, 0x73,
|
||||
0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x34, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18,
|
||||
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70,
|
||||
0x69, 0x2e, 0x76, 0x32, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72,
|
||||
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x32, 0x86, 0x01, 0x0a, 0x0f, 0x52, 0x65, 0x73,
|
||||
0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x73, 0x0a, 0x0d,
|
||||
0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x22, 0x2e,
|
||||
0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x73,
|
||||
0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x23, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32,
|
||||
0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11,
|
||||
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
|
||||
0x73, 0x42, 0xac, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e,
|
||||
0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x42, 0x14, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
|
||||
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,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d,
|
||||
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xff, 0x01, 0x0a,
|
||||
0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65,
|
||||
0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74,
|
||||
0x65, 0x64, 0x54, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x6e,
|
||||
0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61,
|
||||
0x6c, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a,
|
||||
0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x2b, 0x0a,
|
||||
0x0f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x5f, 0x69, 0x64,
|
||||
0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x65,
|
||||
0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x72,
|
||||
0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x5f, 0x69, 0x64, 0x22, 0x16,
|
||||
0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4d, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65,
|
||||
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||
0x34, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,
|
||||
0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76,
|
||||
0x32, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f,
|
||||
0x75, 0x72, 0x63, 0x65, 0x73, 0x32, 0x86, 0x01, 0x0a, 0x0f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72,
|
||||
0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x73, 0x0a, 0x0d, 0x4c, 0x69, 0x73,
|
||||
0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x22, 0x2e, 0x6d, 0x65, 0x6d,
|
||||
0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65,
|
||||
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23,
|
||||
0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69,
|
||||
0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x61, 0x70,
|
||||
0x69, 0x2f, 0x76, 0x32, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x42, 0xac,
|
||||
0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69,
|
||||
0x2e, 0x76, 0x32, 0x42, 0x14, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 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 (
|
||||
@ -269,16 +274,18 @@ var file_api_v2_resource_service_proto_goTypes = []interface{}{
|
||||
(*Resource)(nil), // 0: memos.api.v2.Resource
|
||||
(*ListResourcesRequest)(nil), // 1: memos.api.v2.ListResourcesRequest
|
||||
(*ListResourcesResponse)(nil), // 2: memos.api.v2.ListResourcesResponse
|
||||
(*timestamppb.Timestamp)(nil), // 3: google.protobuf.Timestamp
|
||||
}
|
||||
var file_api_v2_resource_service_proto_depIdxs = []int32{
|
||||
0, // 0: memos.api.v2.ListResourcesResponse.resources:type_name -> memos.api.v2.Resource
|
||||
1, // 1: memos.api.v2.ResourceService.ListResources:input_type -> memos.api.v2.ListResourcesRequest
|
||||
2, // 2: memos.api.v2.ResourceService.ListResources:output_type -> memos.api.v2.ListResourcesResponse
|
||||
2, // [2:3] is the sub-list for method output_type
|
||||
1, // [1:2] 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
|
||||
3, // 0: memos.api.v2.Resource.created_ts:type_name -> google.protobuf.Timestamp
|
||||
0, // 1: memos.api.v2.ListResourcesResponse.resources:type_name -> memos.api.v2.Resource
|
||||
1, // 2: memos.api.v2.ResourceService.ListResources:input_type -> memos.api.v2.ListResourcesRequest
|
||||
2, // 3: memos.api.v2.ResourceService.ListResources:output_type -> memos.api.v2.ListResourcesResponse
|
||||
3, // [3:4] is the sub-list for method output_type
|
||||
2, // [2:3] is the sub-list for method input_type
|
||||
2, // [2:2] is the sub-list for extension type_name
|
||||
2, // [2:2] is the sub-list for extension extendee
|
||||
0, // [0:2] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_api_v2_resource_service_proto_init() }
|
||||
|
@ -33,6 +33,7 @@ type FindResource struct {
|
||||
CreatorID *int32
|
||||
Filename *string
|
||||
MemoID *int32
|
||||
HasRelatedMemo bool
|
||||
Limit *int
|
||||
Offset *int
|
||||
}
|
||||
@ -96,6 +97,9 @@ func (s *Store) ListResources(ctx context.Context, find *FindResource) ([]*Resou
|
||||
if v := find.MemoID; v != nil {
|
||||
where, args = append(where, "resource.id in (SELECT resource_id FROM memo_resource WHERE memo_id = ?)"), append(args, *v)
|
||||
}
|
||||
if find.HasRelatedMemo {
|
||||
where = append(where, "memo_resource.memo_id IS NOT NULL")
|
||||
}
|
||||
|
||||
fields := []string{"resource.id", "resource.filename", "resource.external_link", "resource.type", "resource.size", "resource.creator_id", "resource.created_ts", "resource.updated_ts", "internal_path"}
|
||||
if find.GetBlob {
|
||||
@ -110,7 +114,7 @@ func (s *Store) ListResources(ctx context.Context, find *FindResource) ([]*Resou
|
||||
LEFT JOIN memo_resource ON resource.id = memo_resource.resource_id
|
||||
WHERE %s
|
||||
GROUP BY resource.id
|
||||
ORDER BY resource.id DESC
|
||||
ORDER BY resource.created_ts DESC
|
||||
`, strings.Join(fields, ", "), strings.Join(where, " AND "))
|
||||
if find.Limit != nil {
|
||||
query = fmt.Sprintf("%s LIMIT %d", query, *find.Limit)
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Autocomplete, Button, Input, List, ListItem, Option, Select, Typography } from "@mui/joy";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { Resource } from "@/types/proto/api/v2/resource_service_pb";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
import { useResourceStore } from "../store/module";
|
||||
import { generateDialog } from "./Dialog";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Resource } from "@/types/proto/api/v2/resource_service_pb";
|
||||
import Icon from "../Icon";
|
||||
import ResourceIcon from "../ResourceIcon";
|
||||
|
||||
@ -23,7 +24,7 @@ const ResourceListView = (props: Props) => {
|
||||
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-auto !opacity-100" />
|
||||
<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"
|
||||
|
@ -8,6 +8,7 @@ import { TAB_SPACE_WIDTH, UNKNOWN_ID } from "@/helpers/consts";
|
||||
import { clearContentQueryParam } from "@/helpers/utils";
|
||||
import { getMatchedNodes } from "@/labs/marked";
|
||||
import { useFilterStore, useGlobalStore, useMemoStore, useResourceStore, useTagStore, useUserStore } from "@/store/module";
|
||||
import { Resource } from "@/types/proto/api/v2/resource_service_pb";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
import showCreateResourceDialog from "../CreateResourceDialog";
|
||||
import Icon from "../Icon";
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Resource } from "@/types/proto/api/v2/resource_service_pb";
|
||||
import { getResourceUrl } from "@/utils/resource";
|
||||
import Icon from "./Icon";
|
||||
import ResourceIcon from "./ResourceIcon";
|
||||
|
||||
interface Props {
|
||||
resource: Resource;
|
||||
@ -16,7 +17,7 @@ const MemoResource: React.FC<Props> = (props: Props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={`w-auto flex flex-row justify-start items-center hover:opacity-80 ${className}`}>
|
||||
<div className={`w-auto flex flex-row justify-start items-center text-gray-500 dark:text-gray-400 hover:opacity-80 ${className}`}>
|
||||
{resource.type.startsWith("audio") ? (
|
||||
<>
|
||||
<audio controls>
|
||||
@ -25,8 +26,8 @@ const MemoResource: React.FC<Props> = (props: Props) => {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Icon.FileText className="w-4 h-auto mr-1 text-gray-500" />
|
||||
<span className="text-gray-500 text-sm max-w-[256px] truncate font-mono cursor-pointer" onClick={handlePreviewBtnClick}>
|
||||
<ResourceIcon className="!w-4 !h-4 mr-1" resource={resource} />
|
||||
<span className="text-sm max-w-[256px] truncate cursor-pointer" onClick={handlePreviewBtnClick}>
|
||||
{resource.filename}
|
||||
</span>
|
||||
</>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import classNames from "classnames";
|
||||
import { absolutifyLink } from "@/helpers/utils";
|
||||
import { Resource } from "@/types/proto/api/v2/resource_service_pb";
|
||||
import { getResourceType, getResourceUrl } from "@/utils/resource";
|
||||
import MemoResource from "./MemoResource";
|
||||
import showPreviewImageDialog from "./PreviewImageDialog";
|
||||
@ -42,7 +43,7 @@ const MemoResourceListView: React.FC<Props> = (props: Props) => {
|
||||
<>
|
||||
{imageResourceList.length > 0 &&
|
||||
(imageResourceList.length === 1 ? (
|
||||
<div className="mt-2 max-w-[90%] max-h-64 flex justify-center items-center shadow rounded overflow-hidden hide-scrollbar hover:shadow-md">
|
||||
<div className="mt-2 max-w-[90%] max-h-64 flex justify-center items-center border rounded overflow-hidden hide-scrollbar hover:shadow-md">
|
||||
<img
|
||||
className="cursor-pointer min-h-full w-auto min-w-full object-cover"
|
||||
src={getResourceUrl(imageResourceList[0])}
|
||||
@ -63,7 +64,7 @@ const MemoResourceListView: React.FC<Props> = (props: Props) => {
|
||||
return (
|
||||
<SquareDiv
|
||||
key={resource.id}
|
||||
className="flex justify-center items-center shadow rounded overflow-hidden hide-scrollbar hover:shadow-md"
|
||||
className="flex justify-center items-center border dark:border-zinc-900 rounded overflow-hidden hide-scrollbar hover:shadow-md"
|
||||
>
|
||||
<img
|
||||
className="cursor-pointer min-h-full w-auto min-w-full object-cover"
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { getDateTimeString } from "@/helpers/datetime";
|
||||
import { Resource } from "@/types/proto/api/v2/resource_service_pb";
|
||||
import ResourceIcon from "./ResourceIcon";
|
||||
|
||||
interface Props {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { Resource } from "@/types/proto/api/v2/resource_service_pb";
|
||||
import { getResourceType, getResourceUrl } from "@/utils/resource";
|
||||
import Icon from "./Icon";
|
||||
import showPreviewImageDialog from "./PreviewImageDialog";
|
||||
@ -18,38 +19,52 @@ const ResourceIcon = (props: Props) => {
|
||||
const className = classNames("w-full h-auto", props.className);
|
||||
const strokeWidth = props.strokeWidth;
|
||||
|
||||
switch (resourceType) {
|
||||
case "image/*":
|
||||
const previewResource = () => {
|
||||
window.open(resourceUrl);
|
||||
};
|
||||
|
||||
if (resourceType === "image/*") {
|
||||
return (
|
||||
<SquareDiv className={classNames(className, "flex items-center justify-center overflow-clip")}>
|
||||
<img
|
||||
className="max-w-full max-h-full object-cover shadow"
|
||||
className="min-w-full min-h-full object-cover shadow"
|
||||
src={resource.externalLink ? resourceUrl : resourceUrl + "?thumbnail=1"}
|
||||
onClick={() => showPreviewImageDialog(resourceUrl)}
|
||||
/>
|
||||
</SquareDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const getResourceIcon = () => {
|
||||
switch (resourceType) {
|
||||
case "video/*":
|
||||
return <Icon.FileVideo2 strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
|
||||
return <Icon.FileVideo2 strokeWidth={strokeWidth} className="w-full h-auto" />;
|
||||
case "audio/*":
|
||||
return <Icon.FileAudio strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
|
||||
return <Icon.FileAudio strokeWidth={strokeWidth} className="w-full h-auto" />;
|
||||
case "text/*":
|
||||
return <Icon.FileText strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
|
||||
return <Icon.FileText strokeWidth={strokeWidth} className="w-full h-auto" />;
|
||||
case "application/epub+zip":
|
||||
return <Icon.Book strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
|
||||
return <Icon.Book strokeWidth={strokeWidth} className="w-full h-auto" />;
|
||||
case "application/pdf":
|
||||
return <Icon.Book strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
|
||||
return <Icon.Book strokeWidth={strokeWidth} className="w-full h-auto" />;
|
||||
case "application/msword":
|
||||
return <Icon.FileEdit strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
|
||||
return <Icon.FileEdit strokeWidth={strokeWidth} className="w-full h-auto" />;
|
||||
case "application/msexcel":
|
||||
return <Icon.SheetIcon strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
|
||||
return <Icon.SheetIcon strokeWidth={strokeWidth} className="w-full h-auto" />;
|
||||
case "application/zip":
|
||||
return <Icon.FileArchiveIcon strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
|
||||
return <Icon.FileArchiveIcon onClick={previewResource} strokeWidth={strokeWidth} className="w-full h-auto" />;
|
||||
case "application/x-java-archive":
|
||||
return <Icon.BinaryIcon strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
|
||||
return <Icon.BinaryIcon strokeWidth={strokeWidth} className="w-full h-auto" />;
|
||||
default:
|
||||
return <Icon.File strokeWidth={strokeWidth} className={classNames(className, "opacity-50")} />;
|
||||
return <Icon.File strokeWidth={strokeWidth} className="w-full h-auto" />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div onClick={previewResource} className={classNames(className, "max-w-[4rem] opacity-50")}>
|
||||
{getResourceIcon()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ResourceIcon);
|
||||
|
@ -64,7 +64,7 @@ const AccessTokenSection = () => {
|
||||
Access Tokens
|
||||
<LearnMore className="ml-2" url="https://usememos.com/docs/local-storage" />
|
||||
</p>
|
||||
<p className="text-sm text-gray-700">A list of all access tokens for your account.</p>
|
||||
<p className="text-sm text-gray-700 dark:text-gray-500">A list of all access tokens for your account.</p>
|
||||
</div>
|
||||
<div className="mt-4 sm:mt-0">
|
||||
<Button
|
||||
@ -81,7 +81,7 @@ const AccessTokenSection = () => {
|
||||
<div className="mt-2 flow-root">
|
||||
<div className="overflow-x-auto">
|
||||
<div className="inline-block min-w-full py-2 align-middle">
|
||||
<table className="min-w-full divide-y divide-gray-300">
|
||||
<table className="min-w-full divide-y divide-gray-300 dark:divide-gray-400">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-400">
|
||||
@ -101,7 +101,7 @@ const AccessTokenSection = () => {
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200">
|
||||
<tbody className="divide-y divide-gray-200 dark:divide-gray-500">
|
||||
{userAccessTokens.map((userAccessToken) => (
|
||||
<tr key={userAccessToken.accessToken}>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-900 dark:text-gray-400 flex flex-row justify-start items-center gap-x-1">
|
||||
|
@ -1,4 +1,5 @@
|
||||
import axios from "axios";
|
||||
import { Resource } from "@/types/proto/api/v2/resource_service_pb";
|
||||
import { GetUserResponse } from "@/types/proto/api/v2/user_service_pb";
|
||||
|
||||
export function getSystemStatus() {
|
||||
@ -143,17 +144,6 @@ export function getResourceList() {
|
||||
return axios.get<Resource[]>("/api/v1/resource");
|
||||
}
|
||||
|
||||
export function getResourceListWithLimit(resourceFind?: ResourceFind) {
|
||||
const queryList = [];
|
||||
if (resourceFind?.offset) {
|
||||
queryList.push(`offset=${resourceFind.offset}`);
|
||||
}
|
||||
if (resourceFind?.limit) {
|
||||
queryList.push(`limit=${resourceFind.limit}`);
|
||||
}
|
||||
return axios.get<Resource[]>(`/api/v1/resource?${queryList.join("&")}`);
|
||||
}
|
||||
|
||||
export function createResource(resourceCreate: ResourceCreate) {
|
||||
return axios.post<Resource>("/api/v1/resource", resourceCreate);
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ export function getTimeString(t?: Date | number | string): string {
|
||||
* - "pt-BR" locale: "30/01/2023 22:05:00"
|
||||
* - "pl" locale: "30.01.2023, 22:05:00"
|
||||
*/
|
||||
export function getDateTimeString(t?: Date | number | string, locale = i18n.language): string {
|
||||
export function getDateTimeString(t?: Date | number | string | any, locale = i18n.language): string {
|
||||
const tsFromDate = getTimeStampByDate(t ? t : Date.now());
|
||||
|
||||
return new Date(tsFromDate).toLocaleDateString(locale, {
|
||||
|
@ -1,28 +1,44 @@
|
||||
import { useEffect } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import axios from "axios";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import Empty from "@/components/Empty";
|
||||
import Icon from "@/components/Icon";
|
||||
import MobileHeader from "@/components/MobileHeader";
|
||||
import ResourceCard from "@/components/ResourceCard";
|
||||
import ResourceIcon from "@/components/ResourceIcon";
|
||||
import useLoading from "@/hooks/useLoading";
|
||||
import { useResourceStore } from "@/store/module";
|
||||
import { ListResourcesResponse, Resource } from "@/types/proto/api/v2/resource_service_pb";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
|
||||
const fetchAllResources = async () => {
|
||||
const { data } = await axios.get<ListResourcesResponse>("/api/v2/resources");
|
||||
return data.resources;
|
||||
};
|
||||
|
||||
function groupResourcesByDate(resources: Resource[]) {
|
||||
const grouped = new Map<number, Resource[]>();
|
||||
resources.forEach((item) => {
|
||||
const date = new Date(item.createdTs as any);
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
const timestamp = Date.UTC(year, month - 1, 1);
|
||||
if (!grouped.has(timestamp)) {
|
||||
grouped.set(timestamp, []);
|
||||
}
|
||||
grouped.get(timestamp)?.push(item);
|
||||
});
|
||||
return grouped;
|
||||
}
|
||||
|
||||
const Resources = () => {
|
||||
const t = useTranslate();
|
||||
const loadingState = useLoading();
|
||||
const resourceStore = useResourceStore();
|
||||
const resources = resourceStore.state.resources;
|
||||
const [resources, setResources] = useState<Resource[]>([]);
|
||||
const groupedResources = groupResourcesByDate(resources);
|
||||
|
||||
useEffect(() => {
|
||||
resourceStore
|
||||
.fetchResourceList()
|
||||
.then(() => {
|
||||
fetchAllResources().then((resources) => {
|
||||
setResources(resources);
|
||||
loadingState.setFinish();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
toast.error(error.response.data.message);
|
||||
});
|
||||
}, []);
|
||||
|
||||
@ -41,22 +57,51 @@ const Resources = () => {
|
||||
<p className="w-full text-center text-base my-6 mt-8">{t("resource.fetching-data")}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={
|
||||
resources.length === 0
|
||||
? "flex flex-col justify-start items-start w-full"
|
||||
: "w-full h-auto grid grid-cols-2 md:grid-cols-4 gap-6"
|
||||
}
|
||||
>
|
||||
<>
|
||||
{resources.length === 0 ? (
|
||||
<div className="w-full mt-8 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>
|
||||
) : (
|
||||
resources.map((resource) => <ResourceCard key={resource.id} resource={resource}></ResourceCard>)
|
||||
)}
|
||||
<div className={"w-full h-auto px-2 flex flex-col justify-start items-start gap-y-8"}>
|
||||
{Array.from(groupedResources.entries()).map(([timestamp, resources]) => {
|
||||
const date = new Date(timestamp);
|
||||
return (
|
||||
<div key={timestamp} className="w-full flex flex-row justify-start items-start">
|
||||
<div className="w-16 sm:w-24 pt-4 sm:pl-4 flex flex-col justify-start items-start">
|
||||
<span className="text-sm opacity-60">{date.getFullYear()}</span>
|
||||
<span className="font-medium text-xl">{date.toLocaleString("default", { month: "short" })}</span>
|
||||
</div>
|
||||
<div className="w-full max-w-[calc(100%-4rem)] sm:max-w-[calc(100%-6rem)] flex flex-row justify-start items-start gap-4 flex-wrap">
|
||||
{resources.map((resource) => {
|
||||
return (
|
||||
<div key={resource.id} className="w-auto h-auto flex flex-col justify-start items-start">
|
||||
<div className="w-24 h-24 flex justify-center items-center sm:w-32 sm:h-32 border dark:border-zinc-900 overflow-clip rounded cursor-pointer hover:shadow hover:opacity-80">
|
||||
<ResourceIcon resource={resource} strokeWidth={0.5} />
|
||||
</div>
|
||||
<div className="w-full flex flex-row justify-between items-center mt-1 px-1">
|
||||
<div>
|
||||
<p className="text-xs text-gray-400">{new Date(resource.createdTs as any).toLocaleDateString()}</p>
|
||||
</div>
|
||||
<Link
|
||||
className="flex flex-row justify-start items-center text-gray-400 hover:underline hover:text-blue-600"
|
||||
to={`/m/${resource.relatedMemoId}`}
|
||||
target="_blank"
|
||||
>
|
||||
<span className="text-xs ml-0.5">#{resource.relatedMemoId}</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,17 +1,10 @@
|
||||
import * as api from "@/helpers/api";
|
||||
import { Resource } from "@/types/proto/api/v2/resource_service_pb";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
import store, { useAppSelector } from "../";
|
||||
import { deleteResource, patchResource, setResources } from "../reducer/resource";
|
||||
import { useGlobalStore } from "./global";
|
||||
|
||||
const convertResponseModelResource = (resource: Resource): Resource => {
|
||||
return {
|
||||
...resource,
|
||||
createdTs: resource.createdTs * 1000,
|
||||
updatedTs: resource.updatedTs * 1000,
|
||||
};
|
||||
};
|
||||
|
||||
export const useResourceStore = () => {
|
||||
const state = useAppSelector((state) => state.resource);
|
||||
const t = useTranslate();
|
||||
@ -24,14 +17,12 @@ export const useResourceStore = () => {
|
||||
return store.getState().resource;
|
||||
},
|
||||
async fetchResourceList(): Promise<Resource[]> {
|
||||
const { data } = await api.getResourceList();
|
||||
const resourceList = data.map((m) => convertResponseModelResource(m));
|
||||
const { data: resourceList } = await api.getResourceList();
|
||||
store.dispatch(setResources(resourceList));
|
||||
return resourceList;
|
||||
},
|
||||
async createResource(resourceCreate: ResourceCreate): Promise<Resource> {
|
||||
const { data } = await api.createResource(resourceCreate);
|
||||
const resource = convertResponseModelResource(data);
|
||||
const { data: resource } = await api.createResource(resourceCreate);
|
||||
const resourceList = state.resources;
|
||||
store.dispatch(setResources([resource, ...resourceList]));
|
||||
return resource;
|
||||
@ -44,8 +35,7 @@ export const useResourceStore = () => {
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file, filename);
|
||||
const { data } = await api.createResourceWithBlob(formData);
|
||||
const resource = convertResponseModelResource(data);
|
||||
const { data: resource } = await api.createResourceWithBlob(formData);
|
||||
const resourceList = state.resources;
|
||||
store.dispatch(setResources([resource, ...resourceList]));
|
||||
return resource;
|
||||
@ -60,8 +50,7 @@ export const useResourceStore = () => {
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file, filename);
|
||||
const { data } = await api.createResourceWithBlob(formData);
|
||||
const resource = convertResponseModelResource(data);
|
||||
const { data: resource } = await api.createResourceWithBlob(formData);
|
||||
newResourceList = [resource, ...newResourceList];
|
||||
}
|
||||
const resourceList = state.resources;
|
||||
@ -73,8 +62,7 @@ export const useResourceStore = () => {
|
||||
store.dispatch(deleteResource(id));
|
||||
},
|
||||
async patchResource(resourcePatch: ResourcePatch): Promise<Resource> {
|
||||
const { data } = await api.patchResource(resourcePatch);
|
||||
const resource = convertResponseModelResource(data);
|
||||
const { data: resource } = await api.patchResource(resourcePatch);
|
||||
store.dispatch(patchResource(resource));
|
||||
return resource;
|
||||
},
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { uniqBy } from "lodash-es";
|
||||
import { Resource } from "@/types/proto/api/v2/resource_service_pb";
|
||||
|
||||
interface State {
|
||||
resources: Resource[];
|
||||
|
2
web/src/types/modules/memo.d.ts
vendored
2
web/src/types/modules/memo.d.ts
vendored
@ -16,7 +16,7 @@ interface Memo {
|
||||
pinned: boolean;
|
||||
|
||||
creatorName: string;
|
||||
resourceList: Resource[];
|
||||
resourceList: any[];
|
||||
relationList: MemoRelation[];
|
||||
}
|
||||
|
||||
|
12
web/src/types/modules/resource.d.ts
vendored
12
web/src/types/modules/resource.d.ts
vendored
@ -1,17 +1,5 @@
|
||||
type ResourceId = number;
|
||||
|
||||
interface Resource {
|
||||
id: ResourceId;
|
||||
|
||||
createdTs: number;
|
||||
updatedTs: number;
|
||||
|
||||
filename: string;
|
||||
externalLink: string;
|
||||
type: string;
|
||||
size: string;
|
||||
}
|
||||
|
||||
interface ResourceCreate {
|
||||
filename: string;
|
||||
externalLink: string;
|
||||
|
@ -3,7 +3,7 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf";
|
||||
import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage, Timestamp } from "@bufbuild/protobuf";
|
||||
import { Message, proto3 } from "@bufbuild/protobuf";
|
||||
|
||||
/**
|
||||
@ -16,9 +16,9 @@ export declare class Resource extends Message<Resource> {
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* @generated from field: int64 created_ts = 2;
|
||||
* @generated from field: google.protobuf.Timestamp created_ts = 2;
|
||||
*/
|
||||
createdTs: bigint;
|
||||
createdTs?: Timestamp;
|
||||
|
||||
/**
|
||||
* @generated from field: string filename = 3;
|
||||
|
@ -3,7 +3,7 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { proto3 } from "@bufbuild/protobuf";
|
||||
import { proto3, Timestamp } from "@bufbuild/protobuf";
|
||||
|
||||
/**
|
||||
* @generated from message memos.api.v2.Resource
|
||||
@ -12,7 +12,7 @@ export const Resource = proto3.makeMessageType(
|
||||
"memos.api.v2.Resource",
|
||||
() => [
|
||||
{ no: 1, name: "id", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
|
||||
{ no: 2, name: "created_ts", kind: "scalar", T: 3 /* ScalarType.INT64 */ },
|
||||
{ no: 2, name: "created_ts", kind: "message", T: Timestamp },
|
||||
{ no: 3, name: "filename", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
{ no: 4, name: "external_link", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
{ no: 5, name: "type", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
|
7
web/src/types/resourceItem.d.ts
vendored
7
web/src/types/resourceItem.d.ts
vendored
@ -1,7 +0,0 @@
|
||||
interface ResourceProps {
|
||||
resource: Resource;
|
||||
handleCheckClick: () => void;
|
||||
handleUncheckClick: () => void;
|
||||
}
|
||||
|
||||
type ResourceItemType = ResourceProps;
|
@ -1,3 +1,5 @@
|
||||
import { Resource } from "@/types/proto/api/v2/resource_service_pb";
|
||||
|
||||
export const getResourceUrl = (resource: Resource, withOrigin = true) => {
|
||||
if (resource.externalLink) {
|
||||
return resource.externalLink;
|
||||
|
Reference in New Issue
Block a user