mirror of https://github.com/usememos/memos.git
refactor: simplify theme/locale to user preferences and improve initialization
Remove theme and locale from instance settings to eliminate duplication and simplify the codebase. These are user-specific preferences and should only exist in user settings, not instance-wide settings. Backend changes: - Remove theme from InstanceGeneralSetting proto - Remove locale from InstanceCustomProfile proto - Update instance service converters to remove theme/locale handling - Simplify RSS feed to use static locale Frontend changes: - Remove theme/locale from instanceStore state - Create unified initialization flow with clear fallback priority: * Theme: user setting → localStorage → system preference * Locale: user setting → browser language - Add applyUserPreferences() to centralize theme/locale application - Simplify App.tsx by removing redundant state synchronization - Update all components to use new helper functions: * getThemeWithFallback() for theme resolution * getLocaleWithFallback() for locale resolution - Remove theme/locale selectors from instance profile dialog Theme utilities refactor: - Organize code into clear sections with JSDoc comments - Extract localStorage operations into getStoredTheme/setStoredTheme helpers - Split DOM manipulation into focused functions - Improve type safety with Theme and ResolvedTheme types - Reduce code duplication and improve maintainability 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
8154a411a9
commit
81da20c905
|
|
@ -83,9 +83,6 @@ message InstanceSetting {
|
||||||
|
|
||||||
// General instance settings configuration.
|
// General instance settings configuration.
|
||||||
message GeneralSetting {
|
message GeneralSetting {
|
||||||
// theme is the name of the selected theme.
|
|
||||||
// This references a CSS file in the web/public/themes/ directory.
|
|
||||||
string theme = 1;
|
|
||||||
// disallow_user_registration disallows user registration.
|
// disallow_user_registration disallows user registration.
|
||||||
bool disallow_user_registration = 2;
|
bool disallow_user_registration = 2;
|
||||||
// disallow_password_auth disallows password authentication.
|
// disallow_password_auth disallows password authentication.
|
||||||
|
|
@ -111,7 +108,6 @@ message InstanceSetting {
|
||||||
string title = 1;
|
string title = 1;
|
||||||
string description = 2;
|
string description = 2;
|
||||||
string logo_url = 3;
|
string logo_url = 3;
|
||||||
string locale = 4;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.5.1
|
// - protoc-gen-go-grpc v1.6.0
|
||||||
// - protoc (unknown)
|
// - protoc (unknown)
|
||||||
// source: api/v1/activity_service.proto
|
// source: api/v1/activity_service.proto
|
||||||
|
|
||||||
|
|
@ -80,10 +80,10 @@ type ActivityServiceServer interface {
|
||||||
type UnimplementedActivityServiceServer struct{}
|
type UnimplementedActivityServiceServer struct{}
|
||||||
|
|
||||||
func (UnimplementedActivityServiceServer) ListActivities(context.Context, *ListActivitiesRequest) (*ListActivitiesResponse, error) {
|
func (UnimplementedActivityServiceServer) ListActivities(context.Context, *ListActivitiesRequest) (*ListActivitiesResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListActivities not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ListActivities not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedActivityServiceServer) GetActivity(context.Context, *GetActivityRequest) (*Activity, error) {
|
func (UnimplementedActivityServiceServer) GetActivity(context.Context, *GetActivityRequest) (*Activity, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetActivity not implemented")
|
return nil, status.Error(codes.Unimplemented, "method GetActivity not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedActivityServiceServer) mustEmbedUnimplementedActivityServiceServer() {}
|
func (UnimplementedActivityServiceServer) mustEmbedUnimplementedActivityServiceServer() {}
|
||||||
func (UnimplementedActivityServiceServer) testEmbeddedByValue() {}
|
func (UnimplementedActivityServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
@ -96,7 +96,7 @@ type UnsafeActivityServiceServer interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterActivityServiceServer(s grpc.ServiceRegistrar, srv ActivityServiceServer) {
|
func RegisterActivityServiceServer(s grpc.ServiceRegistrar, srv ActivityServiceServer) {
|
||||||
// If the following call pancis, it indicates UnimplementedActivityServiceServer was
|
// If the following call panics, it indicates UnimplementedActivityServiceServer was
|
||||||
// embedded by pointer and is nil. This will cause panics if an
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
// unimplemented method is ever invoked, so we test this at initialization
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
// time to prevent it from happening at runtime later due to I/O.
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.5.1
|
// - protoc-gen-go-grpc v1.6.0
|
||||||
// - protoc (unknown)
|
// - protoc (unknown)
|
||||||
// source: api/v1/attachment_service.proto
|
// source: api/v1/attachment_service.proto
|
||||||
|
|
||||||
|
|
@ -142,22 +142,22 @@ type AttachmentServiceServer interface {
|
||||||
type UnimplementedAttachmentServiceServer struct{}
|
type UnimplementedAttachmentServiceServer struct{}
|
||||||
|
|
||||||
func (UnimplementedAttachmentServiceServer) CreateAttachment(context.Context, *CreateAttachmentRequest) (*Attachment, error) {
|
func (UnimplementedAttachmentServiceServer) CreateAttachment(context.Context, *CreateAttachmentRequest) (*Attachment, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method CreateAttachment not implemented")
|
return nil, status.Error(codes.Unimplemented, "method CreateAttachment not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedAttachmentServiceServer) ListAttachments(context.Context, *ListAttachmentsRequest) (*ListAttachmentsResponse, error) {
|
func (UnimplementedAttachmentServiceServer) ListAttachments(context.Context, *ListAttachmentsRequest) (*ListAttachmentsResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListAttachments not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ListAttachments not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedAttachmentServiceServer) GetAttachment(context.Context, *GetAttachmentRequest) (*Attachment, error) {
|
func (UnimplementedAttachmentServiceServer) GetAttachment(context.Context, *GetAttachmentRequest) (*Attachment, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetAttachment not implemented")
|
return nil, status.Error(codes.Unimplemented, "method GetAttachment not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedAttachmentServiceServer) GetAttachmentBinary(context.Context, *GetAttachmentBinaryRequest) (*httpbody.HttpBody, error) {
|
func (UnimplementedAttachmentServiceServer) GetAttachmentBinary(context.Context, *GetAttachmentBinaryRequest) (*httpbody.HttpBody, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetAttachmentBinary not implemented")
|
return nil, status.Error(codes.Unimplemented, "method GetAttachmentBinary not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedAttachmentServiceServer) UpdateAttachment(context.Context, *UpdateAttachmentRequest) (*Attachment, error) {
|
func (UnimplementedAttachmentServiceServer) UpdateAttachment(context.Context, *UpdateAttachmentRequest) (*Attachment, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateAttachment not implemented")
|
return nil, status.Error(codes.Unimplemented, "method UpdateAttachment not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedAttachmentServiceServer) DeleteAttachment(context.Context, *DeleteAttachmentRequest) (*emptypb.Empty, error) {
|
func (UnimplementedAttachmentServiceServer) DeleteAttachment(context.Context, *DeleteAttachmentRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteAttachment not implemented")
|
return nil, status.Error(codes.Unimplemented, "method DeleteAttachment not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedAttachmentServiceServer) mustEmbedUnimplementedAttachmentServiceServer() {}
|
func (UnimplementedAttachmentServiceServer) mustEmbedUnimplementedAttachmentServiceServer() {}
|
||||||
func (UnimplementedAttachmentServiceServer) testEmbeddedByValue() {}
|
func (UnimplementedAttachmentServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
@ -170,7 +170,7 @@ type UnsafeAttachmentServiceServer interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterAttachmentServiceServer(s grpc.ServiceRegistrar, srv AttachmentServiceServer) {
|
func RegisterAttachmentServiceServer(s grpc.ServiceRegistrar, srv AttachmentServiceServer) {
|
||||||
// If the following call pancis, it indicates UnimplementedAttachmentServiceServer was
|
// If the following call panics, it indicates UnimplementedAttachmentServiceServer was
|
||||||
// embedded by pointer and is nil. This will cause panics if an
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
// unimplemented method is ever invoked, so we test this at initialization
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
// time to prevent it from happening at runtime later due to I/O.
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.5.1
|
// - protoc-gen-go-grpc v1.6.0
|
||||||
// - protoc (unknown)
|
// - protoc (unknown)
|
||||||
// source: api/v1/auth_service.proto
|
// source: api/v1/auth_service.proto
|
||||||
|
|
||||||
|
|
@ -102,13 +102,13 @@ type AuthServiceServer interface {
|
||||||
type UnimplementedAuthServiceServer struct{}
|
type UnimplementedAuthServiceServer struct{}
|
||||||
|
|
||||||
func (UnimplementedAuthServiceServer) GetCurrentSession(context.Context, *GetCurrentSessionRequest) (*GetCurrentSessionResponse, error) {
|
func (UnimplementedAuthServiceServer) GetCurrentSession(context.Context, *GetCurrentSessionRequest) (*GetCurrentSessionResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetCurrentSession not implemented")
|
return nil, status.Error(codes.Unimplemented, "method GetCurrentSession not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedAuthServiceServer) CreateSession(context.Context, *CreateSessionRequest) (*CreateSessionResponse, error) {
|
func (UnimplementedAuthServiceServer) CreateSession(context.Context, *CreateSessionRequest) (*CreateSessionResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method CreateSession not implemented")
|
return nil, status.Error(codes.Unimplemented, "method CreateSession not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedAuthServiceServer) DeleteSession(context.Context, *DeleteSessionRequest) (*emptypb.Empty, error) {
|
func (UnimplementedAuthServiceServer) DeleteSession(context.Context, *DeleteSessionRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteSession not implemented")
|
return nil, status.Error(codes.Unimplemented, "method DeleteSession not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedAuthServiceServer) mustEmbedUnimplementedAuthServiceServer() {}
|
func (UnimplementedAuthServiceServer) mustEmbedUnimplementedAuthServiceServer() {}
|
||||||
func (UnimplementedAuthServiceServer) testEmbeddedByValue() {}
|
func (UnimplementedAuthServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
@ -121,7 +121,7 @@ type UnsafeAuthServiceServer interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterAuthServiceServer(s grpc.ServiceRegistrar, srv AuthServiceServer) {
|
func RegisterAuthServiceServer(s grpc.ServiceRegistrar, srv AuthServiceServer) {
|
||||||
// If the following call pancis, it indicates UnimplementedAuthServiceServer was
|
// If the following call panics, it indicates UnimplementedAuthServiceServer was
|
||||||
// embedded by pointer and is nil. This will cause panics if an
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
// unimplemented method is ever invoked, so we test this at initialization
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
// time to prevent it from happening at runtime later due to I/O.
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.5.1
|
// - protoc-gen-go-grpc v1.6.0
|
||||||
// - protoc (unknown)
|
// - protoc (unknown)
|
||||||
// source: api/v1/idp_service.proto
|
// source: api/v1/idp_service.proto
|
||||||
|
|
||||||
|
|
@ -126,19 +126,19 @@ type IdentityProviderServiceServer interface {
|
||||||
type UnimplementedIdentityProviderServiceServer struct{}
|
type UnimplementedIdentityProviderServiceServer struct{}
|
||||||
|
|
||||||
func (UnimplementedIdentityProviderServiceServer) ListIdentityProviders(context.Context, *ListIdentityProvidersRequest) (*ListIdentityProvidersResponse, error) {
|
func (UnimplementedIdentityProviderServiceServer) ListIdentityProviders(context.Context, *ListIdentityProvidersRequest) (*ListIdentityProvidersResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListIdentityProviders not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ListIdentityProviders not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedIdentityProviderServiceServer) GetIdentityProvider(context.Context, *GetIdentityProviderRequest) (*IdentityProvider, error) {
|
func (UnimplementedIdentityProviderServiceServer) GetIdentityProvider(context.Context, *GetIdentityProviderRequest) (*IdentityProvider, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetIdentityProvider not implemented")
|
return nil, status.Error(codes.Unimplemented, "method GetIdentityProvider not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedIdentityProviderServiceServer) CreateIdentityProvider(context.Context, *CreateIdentityProviderRequest) (*IdentityProvider, error) {
|
func (UnimplementedIdentityProviderServiceServer) CreateIdentityProvider(context.Context, *CreateIdentityProviderRequest) (*IdentityProvider, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method CreateIdentityProvider not implemented")
|
return nil, status.Error(codes.Unimplemented, "method CreateIdentityProvider not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedIdentityProviderServiceServer) UpdateIdentityProvider(context.Context, *UpdateIdentityProviderRequest) (*IdentityProvider, error) {
|
func (UnimplementedIdentityProviderServiceServer) UpdateIdentityProvider(context.Context, *UpdateIdentityProviderRequest) (*IdentityProvider, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateIdentityProvider not implemented")
|
return nil, status.Error(codes.Unimplemented, "method UpdateIdentityProvider not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedIdentityProviderServiceServer) DeleteIdentityProvider(context.Context, *DeleteIdentityProviderRequest) (*emptypb.Empty, error) {
|
func (UnimplementedIdentityProviderServiceServer) DeleteIdentityProvider(context.Context, *DeleteIdentityProviderRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteIdentityProvider not implemented")
|
return nil, status.Error(codes.Unimplemented, "method DeleteIdentityProvider not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedIdentityProviderServiceServer) mustEmbedUnimplementedIdentityProviderServiceServer() {
|
func (UnimplementedIdentityProviderServiceServer) mustEmbedUnimplementedIdentityProviderServiceServer() {
|
||||||
}
|
}
|
||||||
|
|
@ -152,7 +152,7 @@ type UnsafeIdentityProviderServiceServer interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterIdentityProviderServiceServer(s grpc.ServiceRegistrar, srv IdentityProviderServiceServer) {
|
func RegisterIdentityProviderServiceServer(s grpc.ServiceRegistrar, srv IdentityProviderServiceServer) {
|
||||||
// If the following call pancis, it indicates UnimplementedIdentityProviderServiceServer was
|
// If the following call panics, it indicates UnimplementedIdentityProviderServiceServer was
|
||||||
// embedded by pointer and is nil. This will cause panics if an
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
// unimplemented method is ever invoked, so we test this at initialization
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
// time to prevent it from happening at runtime later due to I/O.
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
|
|
||||||
|
|
@ -460,9 +460,6 @@ func (x *UpdateInstanceSettingRequest) GetUpdateMask() *fieldmaskpb.FieldMask {
|
||||||
// General instance settings configuration.
|
// General instance settings configuration.
|
||||||
type InstanceSetting_GeneralSetting struct {
|
type InstanceSetting_GeneralSetting struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
// theme is the name of the selected theme.
|
|
||||||
// This references a CSS file in the web/public/themes/ directory.
|
|
||||||
Theme string `protobuf:"bytes,1,opt,name=theme,proto3" json:"theme,omitempty"`
|
|
||||||
// disallow_user_registration disallows user registration.
|
// disallow_user_registration disallows user registration.
|
||||||
DisallowUserRegistration bool `protobuf:"varint,2,opt,name=disallow_user_registration,json=disallowUserRegistration,proto3" json:"disallow_user_registration,omitempty"`
|
DisallowUserRegistration bool `protobuf:"varint,2,opt,name=disallow_user_registration,json=disallowUserRegistration,proto3" json:"disallow_user_registration,omitempty"`
|
||||||
// disallow_password_auth disallows password authentication.
|
// disallow_password_auth disallows password authentication.
|
||||||
|
|
@ -515,13 +512,6 @@ func (*InstanceSetting_GeneralSetting) Descriptor() ([]byte, []int) {
|
||||||
return file_api_v1_instance_service_proto_rawDescGZIP(), []int{2, 0}
|
return file_api_v1_instance_service_proto_rawDescGZIP(), []int{2, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *InstanceSetting_GeneralSetting) GetTheme() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Theme
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *InstanceSetting_GeneralSetting) GetDisallowUserRegistration() bool {
|
func (x *InstanceSetting_GeneralSetting) GetDisallowUserRegistration() bool {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.DisallowUserRegistration
|
return x.DisallowUserRegistration
|
||||||
|
|
@ -758,7 +748,6 @@ type InstanceSetting_GeneralSetting_CustomProfile struct {
|
||||||
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
|
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
|
||||||
Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
|
Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
|
||||||
LogoUrl string `protobuf:"bytes,3,opt,name=logo_url,json=logoUrl,proto3" json:"logo_url,omitempty"`
|
LogoUrl string `protobuf:"bytes,3,opt,name=logo_url,json=logoUrl,proto3" json:"logo_url,omitempty"`
|
||||||
Locale string `protobuf:"bytes,4,opt,name=locale,proto3" json:"locale,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
@ -814,13 +803,6 @@ func (x *InstanceSetting_GeneralSetting_CustomProfile) GetLogoUrl() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *InstanceSetting_GeneralSetting_CustomProfile) GetLocale() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Locale
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// S3 configuration for cloud storage backend.
|
// S3 configuration for cloud storage backend.
|
||||||
// Reference: https://developers.cloudflare.com/r2/examples/aws/aws-sdk-go/
|
// Reference: https://developers.cloudflare.com/r2/examples/aws/aws-sdk-go/
|
||||||
type InstanceSetting_StorageSetting_S3Config struct {
|
type InstanceSetting_StorageSetting_S3Config struct {
|
||||||
|
|
@ -917,14 +899,13 @@ const file_api_v1_instance_service_proto_rawDesc = "" +
|
||||||
"\aversion\x18\x02 \x01(\tR\aversion\x12\x12\n" +
|
"\aversion\x18\x02 \x01(\tR\aversion\x12\x12\n" +
|
||||||
"\x04mode\x18\x03 \x01(\tR\x04mode\x12!\n" +
|
"\x04mode\x18\x03 \x01(\tR\x04mode\x12!\n" +
|
||||||
"\finstance_url\x18\x06 \x01(\tR\vinstanceUrl\"\x1b\n" +
|
"\finstance_url\x18\x06 \x01(\tR\vinstanceUrl\"\x1b\n" +
|
||||||
"\x19GetInstanceProfileRequest\"\x9d\x10\n" +
|
"\x19GetInstanceProfileRequest\"\xef\x0f\n" +
|
||||||
"\x0fInstanceSetting\x12\x17\n" +
|
"\x0fInstanceSetting\x12\x17\n" +
|
||||||
"\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12W\n" +
|
"\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12W\n" +
|
||||||
"\x0fgeneral_setting\x18\x02 \x01(\v2,.memos.api.v1.InstanceSetting.GeneralSettingH\x00R\x0egeneralSetting\x12W\n" +
|
"\x0fgeneral_setting\x18\x02 \x01(\v2,.memos.api.v1.InstanceSetting.GeneralSettingH\x00R\x0egeneralSetting\x12W\n" +
|
||||||
"\x0fstorage_setting\x18\x03 \x01(\v2,.memos.api.v1.InstanceSetting.StorageSettingH\x00R\x0estorageSetting\x12d\n" +
|
"\x0fstorage_setting\x18\x03 \x01(\v2,.memos.api.v1.InstanceSetting.StorageSettingH\x00R\x0estorageSetting\x12d\n" +
|
||||||
"\x14memo_related_setting\x18\x04 \x01(\v20.memos.api.v1.InstanceSetting.MemoRelatedSettingH\x00R\x12memoRelatedSetting\x1a\xf8\x04\n" +
|
"\x14memo_related_setting\x18\x04 \x01(\v20.memos.api.v1.InstanceSetting.MemoRelatedSettingH\x00R\x12memoRelatedSetting\x1a\xca\x04\n" +
|
||||||
"\x0eGeneralSetting\x12\x14\n" +
|
"\x0eGeneralSetting\x12<\n" +
|
||||||
"\x05theme\x18\x01 \x01(\tR\x05theme\x12<\n" +
|
|
||||||
"\x1adisallow_user_registration\x18\x02 \x01(\bR\x18disallowUserRegistration\x124\n" +
|
"\x1adisallow_user_registration\x18\x02 \x01(\bR\x18disallowUserRegistration\x124\n" +
|
||||||
"\x16disallow_password_auth\x18\x03 \x01(\bR\x14disallowPasswordAuth\x12+\n" +
|
"\x16disallow_password_auth\x18\x03 \x01(\bR\x14disallowPasswordAuth\x12+\n" +
|
||||||
"\x11additional_script\x18\x04 \x01(\tR\x10additionalScript\x12)\n" +
|
"\x11additional_script\x18\x04 \x01(\tR\x10additionalScript\x12)\n" +
|
||||||
|
|
@ -932,12 +913,11 @@ const file_api_v1_instance_service_proto_rawDesc = "" +
|
||||||
"\x0ecustom_profile\x18\x06 \x01(\v2:.memos.api.v1.InstanceSetting.GeneralSetting.CustomProfileR\rcustomProfile\x121\n" +
|
"\x0ecustom_profile\x18\x06 \x01(\v2:.memos.api.v1.InstanceSetting.GeneralSetting.CustomProfileR\rcustomProfile\x121\n" +
|
||||||
"\x15week_start_day_offset\x18\a \x01(\x05R\x12weekStartDayOffset\x128\n" +
|
"\x15week_start_day_offset\x18\a \x01(\x05R\x12weekStartDayOffset\x128\n" +
|
||||||
"\x18disallow_change_username\x18\b \x01(\bR\x16disallowChangeUsername\x128\n" +
|
"\x18disallow_change_username\x18\b \x01(\bR\x16disallowChangeUsername\x128\n" +
|
||||||
"\x18disallow_change_nickname\x18\t \x01(\bR\x16disallowChangeNickname\x1az\n" +
|
"\x18disallow_change_nickname\x18\t \x01(\bR\x16disallowChangeNickname\x1ab\n" +
|
||||||
"\rCustomProfile\x12\x14\n" +
|
"\rCustomProfile\x12\x14\n" +
|
||||||
"\x05title\x18\x01 \x01(\tR\x05title\x12 \n" +
|
"\x05title\x18\x01 \x01(\tR\x05title\x12 \n" +
|
||||||
"\vdescription\x18\x02 \x01(\tR\vdescription\x12\x19\n" +
|
"\vdescription\x18\x02 \x01(\tR\vdescription\x12\x19\n" +
|
||||||
"\blogo_url\x18\x03 \x01(\tR\alogoUrl\x12\x16\n" +
|
"\blogo_url\x18\x03 \x01(\tR\alogoUrl\x1a\xbc\x04\n" +
|
||||||
"\x06locale\x18\x04 \x01(\tR\x06locale\x1a\xbc\x04\n" +
|
|
||||||
"\x0eStorageSetting\x12[\n" +
|
"\x0eStorageSetting\x12[\n" +
|
||||||
"\fstorage_type\x18\x01 \x01(\x0e28.memos.api.v1.InstanceSetting.StorageSetting.StorageTypeR\vstorageType\x12+\n" +
|
"\fstorage_type\x18\x01 \x01(\x0e28.memos.api.v1.InstanceSetting.StorageSetting.StorageTypeR\vstorageType\x12+\n" +
|
||||||
"\x11filepath_template\x18\x02 \x01(\tR\x10filepathTemplate\x12/\n" +
|
"\x11filepath_template\x18\x02 \x01(\tR\x10filepathTemplate\x12/\n" +
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.5.1
|
// - protoc-gen-go-grpc v1.6.0
|
||||||
// - protoc (unknown)
|
// - protoc (unknown)
|
||||||
// source: api/v1/instance_service.proto
|
// source: api/v1/instance_service.proto
|
||||||
|
|
||||||
|
|
@ -95,13 +95,13 @@ type InstanceServiceServer interface {
|
||||||
type UnimplementedInstanceServiceServer struct{}
|
type UnimplementedInstanceServiceServer struct{}
|
||||||
|
|
||||||
func (UnimplementedInstanceServiceServer) GetInstanceProfile(context.Context, *GetInstanceProfileRequest) (*InstanceProfile, error) {
|
func (UnimplementedInstanceServiceServer) GetInstanceProfile(context.Context, *GetInstanceProfileRequest) (*InstanceProfile, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetInstanceProfile not implemented")
|
return nil, status.Error(codes.Unimplemented, "method GetInstanceProfile not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedInstanceServiceServer) GetInstanceSetting(context.Context, *GetInstanceSettingRequest) (*InstanceSetting, error) {
|
func (UnimplementedInstanceServiceServer) GetInstanceSetting(context.Context, *GetInstanceSettingRequest) (*InstanceSetting, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetInstanceSetting not implemented")
|
return nil, status.Error(codes.Unimplemented, "method GetInstanceSetting not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedInstanceServiceServer) UpdateInstanceSetting(context.Context, *UpdateInstanceSettingRequest) (*InstanceSetting, error) {
|
func (UnimplementedInstanceServiceServer) UpdateInstanceSetting(context.Context, *UpdateInstanceSettingRequest) (*InstanceSetting, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateInstanceSetting not implemented")
|
return nil, status.Error(codes.Unimplemented, "method UpdateInstanceSetting not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedInstanceServiceServer) mustEmbedUnimplementedInstanceServiceServer() {}
|
func (UnimplementedInstanceServiceServer) mustEmbedUnimplementedInstanceServiceServer() {}
|
||||||
func (UnimplementedInstanceServiceServer) testEmbeddedByValue() {}
|
func (UnimplementedInstanceServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
@ -114,7 +114,7 @@ type UnsafeInstanceServiceServer interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterInstanceServiceServer(s grpc.ServiceRegistrar, srv InstanceServiceServer) {
|
func RegisterInstanceServiceServer(s grpc.ServiceRegistrar, srv InstanceServiceServer) {
|
||||||
// If the following call pancis, it indicates UnimplementedInstanceServiceServer was
|
// If the following call panics, it indicates UnimplementedInstanceServiceServer was
|
||||||
// embedded by pointer and is nil. This will cause panics if an
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
// unimplemented method is ever invoked, so we test this at initialization
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
// time to prevent it from happening at runtime later due to I/O.
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.5.1
|
// - protoc-gen-go-grpc v1.6.0
|
||||||
// - protoc (unknown)
|
// - protoc (unknown)
|
||||||
// source: api/v1/memo_service.proto
|
// source: api/v1/memo_service.proto
|
||||||
|
|
||||||
|
|
@ -261,46 +261,46 @@ type MemoServiceServer interface {
|
||||||
type UnimplementedMemoServiceServer struct{}
|
type UnimplementedMemoServiceServer struct{}
|
||||||
|
|
||||||
func (UnimplementedMemoServiceServer) CreateMemo(context.Context, *CreateMemoRequest) (*Memo, error) {
|
func (UnimplementedMemoServiceServer) CreateMemo(context.Context, *CreateMemoRequest) (*Memo, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method CreateMemo not implemented")
|
return nil, status.Error(codes.Unimplemented, "method CreateMemo not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedMemoServiceServer) ListMemos(context.Context, *ListMemosRequest) (*ListMemosResponse, error) {
|
func (UnimplementedMemoServiceServer) ListMemos(context.Context, *ListMemosRequest) (*ListMemosResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListMemos not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ListMemos not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedMemoServiceServer) GetMemo(context.Context, *GetMemoRequest) (*Memo, error) {
|
func (UnimplementedMemoServiceServer) GetMemo(context.Context, *GetMemoRequest) (*Memo, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetMemo not implemented")
|
return nil, status.Error(codes.Unimplemented, "method GetMemo not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedMemoServiceServer) UpdateMemo(context.Context, *UpdateMemoRequest) (*Memo, error) {
|
func (UnimplementedMemoServiceServer) UpdateMemo(context.Context, *UpdateMemoRequest) (*Memo, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateMemo not implemented")
|
return nil, status.Error(codes.Unimplemented, "method UpdateMemo not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedMemoServiceServer) DeleteMemo(context.Context, *DeleteMemoRequest) (*emptypb.Empty, error) {
|
func (UnimplementedMemoServiceServer) DeleteMemo(context.Context, *DeleteMemoRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteMemo not implemented")
|
return nil, status.Error(codes.Unimplemented, "method DeleteMemo not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedMemoServiceServer) SetMemoAttachments(context.Context, *SetMemoAttachmentsRequest) (*emptypb.Empty, error) {
|
func (UnimplementedMemoServiceServer) SetMemoAttachments(context.Context, *SetMemoAttachmentsRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method SetMemoAttachments not implemented")
|
return nil, status.Error(codes.Unimplemented, "method SetMemoAttachments not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedMemoServiceServer) ListMemoAttachments(context.Context, *ListMemoAttachmentsRequest) (*ListMemoAttachmentsResponse, error) {
|
func (UnimplementedMemoServiceServer) ListMemoAttachments(context.Context, *ListMemoAttachmentsRequest) (*ListMemoAttachmentsResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListMemoAttachments not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ListMemoAttachments not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedMemoServiceServer) SetMemoRelations(context.Context, *SetMemoRelationsRequest) (*emptypb.Empty, error) {
|
func (UnimplementedMemoServiceServer) SetMemoRelations(context.Context, *SetMemoRelationsRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method SetMemoRelations not implemented")
|
return nil, status.Error(codes.Unimplemented, "method SetMemoRelations not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedMemoServiceServer) ListMemoRelations(context.Context, *ListMemoRelationsRequest) (*ListMemoRelationsResponse, error) {
|
func (UnimplementedMemoServiceServer) ListMemoRelations(context.Context, *ListMemoRelationsRequest) (*ListMemoRelationsResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListMemoRelations not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ListMemoRelations not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedMemoServiceServer) CreateMemoComment(context.Context, *CreateMemoCommentRequest) (*Memo, error) {
|
func (UnimplementedMemoServiceServer) CreateMemoComment(context.Context, *CreateMemoCommentRequest) (*Memo, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method CreateMemoComment not implemented")
|
return nil, status.Error(codes.Unimplemented, "method CreateMemoComment not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedMemoServiceServer) ListMemoComments(context.Context, *ListMemoCommentsRequest) (*ListMemoCommentsResponse, error) {
|
func (UnimplementedMemoServiceServer) ListMemoComments(context.Context, *ListMemoCommentsRequest) (*ListMemoCommentsResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListMemoComments not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ListMemoComments not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedMemoServiceServer) ListMemoReactions(context.Context, *ListMemoReactionsRequest) (*ListMemoReactionsResponse, error) {
|
func (UnimplementedMemoServiceServer) ListMemoReactions(context.Context, *ListMemoReactionsRequest) (*ListMemoReactionsResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListMemoReactions not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ListMemoReactions not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedMemoServiceServer) UpsertMemoReaction(context.Context, *UpsertMemoReactionRequest) (*Reaction, error) {
|
func (UnimplementedMemoServiceServer) UpsertMemoReaction(context.Context, *UpsertMemoReactionRequest) (*Reaction, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method UpsertMemoReaction not implemented")
|
return nil, status.Error(codes.Unimplemented, "method UpsertMemoReaction not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedMemoServiceServer) DeleteMemoReaction(context.Context, *DeleteMemoReactionRequest) (*emptypb.Empty, error) {
|
func (UnimplementedMemoServiceServer) DeleteMemoReaction(context.Context, *DeleteMemoReactionRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteMemoReaction not implemented")
|
return nil, status.Error(codes.Unimplemented, "method DeleteMemoReaction not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedMemoServiceServer) mustEmbedUnimplementedMemoServiceServer() {}
|
func (UnimplementedMemoServiceServer) mustEmbedUnimplementedMemoServiceServer() {}
|
||||||
func (UnimplementedMemoServiceServer) testEmbeddedByValue() {}
|
func (UnimplementedMemoServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
@ -313,7 +313,7 @@ type UnsafeMemoServiceServer interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterMemoServiceServer(s grpc.ServiceRegistrar, srv MemoServiceServer) {
|
func RegisterMemoServiceServer(s grpc.ServiceRegistrar, srv MemoServiceServer) {
|
||||||
// If the following call pancis, it indicates UnimplementedMemoServiceServer was
|
// If the following call panics, it indicates UnimplementedMemoServiceServer was
|
||||||
// embedded by pointer and is nil. This will cause panics if an
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
// unimplemented method is ever invoked, so we test this at initialization
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
// time to prevent it from happening at runtime later due to I/O.
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.5.1
|
// - protoc-gen-go-grpc v1.6.0
|
||||||
// - protoc (unknown)
|
// - protoc (unknown)
|
||||||
// source: api/v1/shortcut_service.proto
|
// source: api/v1/shortcut_service.proto
|
||||||
|
|
||||||
|
|
@ -126,19 +126,19 @@ type ShortcutServiceServer interface {
|
||||||
type UnimplementedShortcutServiceServer struct{}
|
type UnimplementedShortcutServiceServer struct{}
|
||||||
|
|
||||||
func (UnimplementedShortcutServiceServer) ListShortcuts(context.Context, *ListShortcutsRequest) (*ListShortcutsResponse, error) {
|
func (UnimplementedShortcutServiceServer) ListShortcuts(context.Context, *ListShortcutsRequest) (*ListShortcutsResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListShortcuts not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ListShortcuts not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedShortcutServiceServer) GetShortcut(context.Context, *GetShortcutRequest) (*Shortcut, error) {
|
func (UnimplementedShortcutServiceServer) GetShortcut(context.Context, *GetShortcutRequest) (*Shortcut, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetShortcut not implemented")
|
return nil, status.Error(codes.Unimplemented, "method GetShortcut not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedShortcutServiceServer) CreateShortcut(context.Context, *CreateShortcutRequest) (*Shortcut, error) {
|
func (UnimplementedShortcutServiceServer) CreateShortcut(context.Context, *CreateShortcutRequest) (*Shortcut, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method CreateShortcut not implemented")
|
return nil, status.Error(codes.Unimplemented, "method CreateShortcut not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedShortcutServiceServer) UpdateShortcut(context.Context, *UpdateShortcutRequest) (*Shortcut, error) {
|
func (UnimplementedShortcutServiceServer) UpdateShortcut(context.Context, *UpdateShortcutRequest) (*Shortcut, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateShortcut not implemented")
|
return nil, status.Error(codes.Unimplemented, "method UpdateShortcut not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedShortcutServiceServer) DeleteShortcut(context.Context, *DeleteShortcutRequest) (*emptypb.Empty, error) {
|
func (UnimplementedShortcutServiceServer) DeleteShortcut(context.Context, *DeleteShortcutRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteShortcut not implemented")
|
return nil, status.Error(codes.Unimplemented, "method DeleteShortcut not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedShortcutServiceServer) mustEmbedUnimplementedShortcutServiceServer() {}
|
func (UnimplementedShortcutServiceServer) mustEmbedUnimplementedShortcutServiceServer() {}
|
||||||
func (UnimplementedShortcutServiceServer) testEmbeddedByValue() {}
|
func (UnimplementedShortcutServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
@ -151,7 +151,7 @@ type UnsafeShortcutServiceServer interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterShortcutServiceServer(s grpc.ServiceRegistrar, srv ShortcutServiceServer) {
|
func RegisterShortcutServiceServer(s grpc.ServiceRegistrar, srv ShortcutServiceServer) {
|
||||||
// If the following call pancis, it indicates UnimplementedShortcutServiceServer was
|
// If the following call panics, it indicates UnimplementedShortcutServiceServer was
|
||||||
// embedded by pointer and is nil. This will cause panics if an
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
// unimplemented method is ever invoked, so we test this at initialization
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
// time to prevent it from happening at runtime later due to I/O.
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.5.1
|
// - protoc-gen-go-grpc v1.6.0
|
||||||
// - protoc (unknown)
|
// - protoc (unknown)
|
||||||
// source: api/v1/user_service.proto
|
// source: api/v1/user_service.proto
|
||||||
|
|
||||||
|
|
@ -403,73 +403,73 @@ type UserServiceServer interface {
|
||||||
type UnimplementedUserServiceServer struct{}
|
type UnimplementedUserServiceServer struct{}
|
||||||
|
|
||||||
func (UnimplementedUserServiceServer) ListUsers(context.Context, *ListUsersRequest) (*ListUsersResponse, error) {
|
func (UnimplementedUserServiceServer) ListUsers(context.Context, *ListUsersRequest) (*ListUsersResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListUsers not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ListUsers not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) GetUser(context.Context, *GetUserRequest) (*User, error) {
|
func (UnimplementedUserServiceServer) GetUser(context.Context, *GetUserRequest) (*User, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetUser not implemented")
|
return nil, status.Error(codes.Unimplemented, "method GetUser not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) CreateUser(context.Context, *CreateUserRequest) (*User, error) {
|
func (UnimplementedUserServiceServer) CreateUser(context.Context, *CreateUserRequest) (*User, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method CreateUser not implemented")
|
return nil, status.Error(codes.Unimplemented, "method CreateUser not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) UpdateUser(context.Context, *UpdateUserRequest) (*User, error) {
|
func (UnimplementedUserServiceServer) UpdateUser(context.Context, *UpdateUserRequest) (*User, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateUser not implemented")
|
return nil, status.Error(codes.Unimplemented, "method UpdateUser not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) DeleteUser(context.Context, *DeleteUserRequest) (*emptypb.Empty, error) {
|
func (UnimplementedUserServiceServer) DeleteUser(context.Context, *DeleteUserRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteUser not implemented")
|
return nil, status.Error(codes.Unimplemented, "method DeleteUser not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) GetUserAvatar(context.Context, *GetUserAvatarRequest) (*httpbody.HttpBody, error) {
|
func (UnimplementedUserServiceServer) GetUserAvatar(context.Context, *GetUserAvatarRequest) (*httpbody.HttpBody, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetUserAvatar not implemented")
|
return nil, status.Error(codes.Unimplemented, "method GetUserAvatar not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) ListAllUserStats(context.Context, *ListAllUserStatsRequest) (*ListAllUserStatsResponse, error) {
|
func (UnimplementedUserServiceServer) ListAllUserStats(context.Context, *ListAllUserStatsRequest) (*ListAllUserStatsResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListAllUserStats not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ListAllUserStats not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) GetUserStats(context.Context, *GetUserStatsRequest) (*UserStats, error) {
|
func (UnimplementedUserServiceServer) GetUserStats(context.Context, *GetUserStatsRequest) (*UserStats, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetUserStats not implemented")
|
return nil, status.Error(codes.Unimplemented, "method GetUserStats not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) GetUserSetting(context.Context, *GetUserSettingRequest) (*UserSetting, error) {
|
func (UnimplementedUserServiceServer) GetUserSetting(context.Context, *GetUserSettingRequest) (*UserSetting, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetUserSetting not implemented")
|
return nil, status.Error(codes.Unimplemented, "method GetUserSetting not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) UpdateUserSetting(context.Context, *UpdateUserSettingRequest) (*UserSetting, error) {
|
func (UnimplementedUserServiceServer) UpdateUserSetting(context.Context, *UpdateUserSettingRequest) (*UserSetting, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateUserSetting not implemented")
|
return nil, status.Error(codes.Unimplemented, "method UpdateUserSetting not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) ListUserSettings(context.Context, *ListUserSettingsRequest) (*ListUserSettingsResponse, error) {
|
func (UnimplementedUserServiceServer) ListUserSettings(context.Context, *ListUserSettingsRequest) (*ListUserSettingsResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListUserSettings not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ListUserSettings not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) ListUserAccessTokens(context.Context, *ListUserAccessTokensRequest) (*ListUserAccessTokensResponse, error) {
|
func (UnimplementedUserServiceServer) ListUserAccessTokens(context.Context, *ListUserAccessTokensRequest) (*ListUserAccessTokensResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListUserAccessTokens not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ListUserAccessTokens not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) CreateUserAccessToken(context.Context, *CreateUserAccessTokenRequest) (*UserAccessToken, error) {
|
func (UnimplementedUserServiceServer) CreateUserAccessToken(context.Context, *CreateUserAccessTokenRequest) (*UserAccessToken, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method CreateUserAccessToken not implemented")
|
return nil, status.Error(codes.Unimplemented, "method CreateUserAccessToken not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) DeleteUserAccessToken(context.Context, *DeleteUserAccessTokenRequest) (*emptypb.Empty, error) {
|
func (UnimplementedUserServiceServer) DeleteUserAccessToken(context.Context, *DeleteUserAccessTokenRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteUserAccessToken not implemented")
|
return nil, status.Error(codes.Unimplemented, "method DeleteUserAccessToken not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) ListUserSessions(context.Context, *ListUserSessionsRequest) (*ListUserSessionsResponse, error) {
|
func (UnimplementedUserServiceServer) ListUserSessions(context.Context, *ListUserSessionsRequest) (*ListUserSessionsResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListUserSessions not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ListUserSessions not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) RevokeUserSession(context.Context, *RevokeUserSessionRequest) (*emptypb.Empty, error) {
|
func (UnimplementedUserServiceServer) RevokeUserSession(context.Context, *RevokeUserSessionRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method RevokeUserSession not implemented")
|
return nil, status.Error(codes.Unimplemented, "method RevokeUserSession not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) ListUserWebhooks(context.Context, *ListUserWebhooksRequest) (*ListUserWebhooksResponse, error) {
|
func (UnimplementedUserServiceServer) ListUserWebhooks(context.Context, *ListUserWebhooksRequest) (*ListUserWebhooksResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListUserWebhooks not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ListUserWebhooks not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) CreateUserWebhook(context.Context, *CreateUserWebhookRequest) (*UserWebhook, error) {
|
func (UnimplementedUserServiceServer) CreateUserWebhook(context.Context, *CreateUserWebhookRequest) (*UserWebhook, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method CreateUserWebhook not implemented")
|
return nil, status.Error(codes.Unimplemented, "method CreateUserWebhook not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) UpdateUserWebhook(context.Context, *UpdateUserWebhookRequest) (*UserWebhook, error) {
|
func (UnimplementedUserServiceServer) UpdateUserWebhook(context.Context, *UpdateUserWebhookRequest) (*UserWebhook, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateUserWebhook not implemented")
|
return nil, status.Error(codes.Unimplemented, "method UpdateUserWebhook not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) DeleteUserWebhook(context.Context, *DeleteUserWebhookRequest) (*emptypb.Empty, error) {
|
func (UnimplementedUserServiceServer) DeleteUserWebhook(context.Context, *DeleteUserWebhookRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteUserWebhook not implemented")
|
return nil, status.Error(codes.Unimplemented, "method DeleteUserWebhook not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) ListUserNotifications(context.Context, *ListUserNotificationsRequest) (*ListUserNotificationsResponse, error) {
|
func (UnimplementedUserServiceServer) ListUserNotifications(context.Context, *ListUserNotificationsRequest) (*ListUserNotificationsResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListUserNotifications not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ListUserNotifications not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) UpdateUserNotification(context.Context, *UpdateUserNotificationRequest) (*UserNotification, error) {
|
func (UnimplementedUserServiceServer) UpdateUserNotification(context.Context, *UpdateUserNotificationRequest) (*UserNotification, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateUserNotification not implemented")
|
return nil, status.Error(codes.Unimplemented, "method UpdateUserNotification not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) DeleteUserNotification(context.Context, *DeleteUserNotificationRequest) (*emptypb.Empty, error) {
|
func (UnimplementedUserServiceServer) DeleteUserNotification(context.Context, *DeleteUserNotificationRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteUserNotification not implemented")
|
return nil, status.Error(codes.Unimplemented, "method DeleteUserNotification not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUserServiceServer) mustEmbedUnimplementedUserServiceServer() {}
|
func (UnimplementedUserServiceServer) mustEmbedUnimplementedUserServiceServer() {}
|
||||||
func (UnimplementedUserServiceServer) testEmbeddedByValue() {}
|
func (UnimplementedUserServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
@ -482,7 +482,7 @@ type UnsafeUserServiceServer interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterUserServiceServer(s grpc.ServiceRegistrar, srv UserServiceServer) {
|
func RegisterUserServiceServer(s grpc.ServiceRegistrar, srv UserServiceServer) {
|
||||||
// If the following call pancis, it indicates UnimplementedUserServiceServer was
|
// If the following call panics, it indicates UnimplementedUserServiceServer was
|
||||||
// embedded by pointer and is nil. This will cause panics if an
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
// unimplemented method is ever invoked, so we test this at initialization
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
// time to prevent it from happening at runtime later due to I/O.
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
|
|
||||||
|
|
@ -2198,8 +2198,6 @@ components:
|
||||||
type: string
|
type: string
|
||||||
logoUrl:
|
logoUrl:
|
||||||
type: string
|
type: string
|
||||||
locale:
|
|
||||||
type: string
|
|
||||||
description: Custom profile configuration for instance branding.
|
description: Custom profile configuration for instance branding.
|
||||||
GetCurrentSessionResponse:
|
GetCurrentSessionResponse:
|
||||||
type: object
|
type: object
|
||||||
|
|
@ -2290,11 +2288,6 @@ components:
|
||||||
InstanceSetting_GeneralSetting:
|
InstanceSetting_GeneralSetting:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
theme:
|
|
||||||
type: string
|
|
||||||
description: |-
|
|
||||||
theme is the name of the selected theme.
|
|
||||||
This references a CSS file in the web/public/themes/ directory.
|
|
||||||
disallowUserRegistration:
|
disallowUserRegistration:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: disallow_user_registration disallows user registration.
|
description: disallow_user_registration disallows user registration.
|
||||||
|
|
|
||||||
|
|
@ -313,9 +313,6 @@ func (x *InstanceBasicSetting) GetSchemaVersion() string {
|
||||||
|
|
||||||
type InstanceGeneralSetting struct {
|
type InstanceGeneralSetting struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
// theme is the name of the selected theme.
|
|
||||||
// This references a CSS file in the web/public/themes/ directory.
|
|
||||||
Theme string `protobuf:"bytes,1,opt,name=theme,proto3" json:"theme,omitempty"`
|
|
||||||
// disallow_user_registration disallows user registration.
|
// disallow_user_registration disallows user registration.
|
||||||
DisallowUserRegistration bool `protobuf:"varint,2,opt,name=disallow_user_registration,json=disallowUserRegistration,proto3" json:"disallow_user_registration,omitempty"`
|
DisallowUserRegistration bool `protobuf:"varint,2,opt,name=disallow_user_registration,json=disallowUserRegistration,proto3" json:"disallow_user_registration,omitempty"`
|
||||||
// disallow_password_auth disallows password authentication.
|
// disallow_password_auth disallows password authentication.
|
||||||
|
|
@ -368,13 +365,6 @@ func (*InstanceGeneralSetting) Descriptor() ([]byte, []int) {
|
||||||
return file_store_instance_setting_proto_rawDescGZIP(), []int{2}
|
return file_store_instance_setting_proto_rawDescGZIP(), []int{2}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *InstanceGeneralSetting) GetTheme() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Theme
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *InstanceGeneralSetting) GetDisallowUserRegistration() bool {
|
func (x *InstanceGeneralSetting) GetDisallowUserRegistration() bool {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.DisallowUserRegistration
|
return x.DisallowUserRegistration
|
||||||
|
|
@ -436,7 +426,6 @@ type InstanceCustomProfile struct {
|
||||||
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
|
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
|
||||||
Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
|
Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
|
||||||
LogoUrl string `protobuf:"bytes,3,opt,name=logo_url,json=logoUrl,proto3" json:"logo_url,omitempty"`
|
LogoUrl string `protobuf:"bytes,3,opt,name=logo_url,json=logoUrl,proto3" json:"logo_url,omitempty"`
|
||||||
Locale string `protobuf:"bytes,4,opt,name=locale,proto3" json:"locale,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
@ -492,13 +481,6 @@ func (x *InstanceCustomProfile) GetLogoUrl() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *InstanceCustomProfile) GetLocale() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Locale
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type InstanceStorageSetting struct {
|
type InstanceStorageSetting struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
// storage_type is the storage type.
|
// storage_type is the storage type.
|
||||||
|
|
@ -771,9 +753,8 @@ const file_store_instance_setting_proto_rawDesc = "" +
|
||||||
"\x14InstanceBasicSetting\x12\x1d\n" +
|
"\x14InstanceBasicSetting\x12\x1d\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"secret_key\x18\x01 \x01(\tR\tsecretKey\x12%\n" +
|
"secret_key\x18\x01 \x01(\tR\tsecretKey\x12%\n" +
|
||||||
"\x0eschema_version\x18\x02 \x01(\tR\rschemaVersion\"\xec\x03\n" +
|
"\x0eschema_version\x18\x02 \x01(\tR\rschemaVersion\"\xd6\x03\n" +
|
||||||
"\x16InstanceGeneralSetting\x12\x14\n" +
|
"\x16InstanceGeneralSetting\x12<\n" +
|
||||||
"\x05theme\x18\x01 \x01(\tR\x05theme\x12<\n" +
|
|
||||||
"\x1adisallow_user_registration\x18\x02 \x01(\bR\x18disallowUserRegistration\x124\n" +
|
"\x1adisallow_user_registration\x18\x02 \x01(\bR\x18disallowUserRegistration\x124\n" +
|
||||||
"\x16disallow_password_auth\x18\x03 \x01(\bR\x14disallowPasswordAuth\x12+\n" +
|
"\x16disallow_password_auth\x18\x03 \x01(\bR\x14disallowPasswordAuth\x12+\n" +
|
||||||
"\x11additional_script\x18\x04 \x01(\tR\x10additionalScript\x12)\n" +
|
"\x11additional_script\x18\x04 \x01(\tR\x10additionalScript\x12)\n" +
|
||||||
|
|
@ -781,12 +762,11 @@ const file_store_instance_setting_proto_rawDesc = "" +
|
||||||
"\x0ecustom_profile\x18\x06 \x01(\v2\".memos.store.InstanceCustomProfileR\rcustomProfile\x121\n" +
|
"\x0ecustom_profile\x18\x06 \x01(\v2\".memos.store.InstanceCustomProfileR\rcustomProfile\x121\n" +
|
||||||
"\x15week_start_day_offset\x18\a \x01(\x05R\x12weekStartDayOffset\x128\n" +
|
"\x15week_start_day_offset\x18\a \x01(\x05R\x12weekStartDayOffset\x128\n" +
|
||||||
"\x18disallow_change_username\x18\b \x01(\bR\x16disallowChangeUsername\x128\n" +
|
"\x18disallow_change_username\x18\b \x01(\bR\x16disallowChangeUsername\x128\n" +
|
||||||
"\x18disallow_change_nickname\x18\t \x01(\bR\x16disallowChangeNickname\"\x82\x01\n" +
|
"\x18disallow_change_nickname\x18\t \x01(\bR\x16disallowChangeNickname\"j\n" +
|
||||||
"\x15InstanceCustomProfile\x12\x14\n" +
|
"\x15InstanceCustomProfile\x12\x14\n" +
|
||||||
"\x05title\x18\x01 \x01(\tR\x05title\x12 \n" +
|
"\x05title\x18\x01 \x01(\tR\x05title\x12 \n" +
|
||||||
"\vdescription\x18\x02 \x01(\tR\vdescription\x12\x19\n" +
|
"\vdescription\x18\x02 \x01(\tR\vdescription\x12\x19\n" +
|
||||||
"\blogo_url\x18\x03 \x01(\tR\alogoUrl\x12\x16\n" +
|
"\blogo_url\x18\x03 \x01(\tR\alogoUrl\"\xd3\x02\n" +
|
||||||
"\x06locale\x18\x04 \x01(\tR\x06locale\"\xd3\x02\n" +
|
|
||||||
"\x16InstanceStorageSetting\x12R\n" +
|
"\x16InstanceStorageSetting\x12R\n" +
|
||||||
"\fstorage_type\x18\x01 \x01(\x0e2/.memos.store.InstanceStorageSetting.StorageTypeR\vstorageType\x12+\n" +
|
"\fstorage_type\x18\x01 \x01(\x0e2/.memos.store.InstanceStorageSetting.StorageTypeR\vstorageType\x12+\n" +
|
||||||
"\x11filepath_template\x18\x02 \x01(\tR\x10filepathTemplate\x12/\n" +
|
"\x11filepath_template\x18\x02 \x01(\tR\x10filepathTemplate\x12/\n" +
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,6 @@ message InstanceBasicSetting {
|
||||||
}
|
}
|
||||||
|
|
||||||
message InstanceGeneralSetting {
|
message InstanceGeneralSetting {
|
||||||
// theme is the name of the selected theme.
|
|
||||||
// This references a CSS file in the web/public/themes/ directory.
|
|
||||||
string theme = 1;
|
|
||||||
// disallow_user_registration disallows user registration.
|
// disallow_user_registration disallows user registration.
|
||||||
bool disallow_user_registration = 2;
|
bool disallow_user_registration = 2;
|
||||||
// disallow_password_auth disallows password authentication.
|
// disallow_password_auth disallows password authentication.
|
||||||
|
|
@ -61,7 +58,6 @@ message InstanceCustomProfile {
|
||||||
string title = 1;
|
string title = 1;
|
||||||
string description = 2;
|
string description = 2;
|
||||||
string logo_url = 3;
|
string logo_url = 3;
|
||||||
string locale = 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message InstanceStorageSetting {
|
message InstanceStorageSetting {
|
||||||
|
|
|
||||||
|
|
@ -154,14 +154,8 @@ func convertInstanceGeneralSettingFromStore(setting *storepb.InstanceGeneralSett
|
||||||
if setting == nil {
|
if setting == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Backfill theme if empty
|
|
||||||
theme := setting.Theme
|
|
||||||
if theme == "" {
|
|
||||||
theme = "default"
|
|
||||||
}
|
|
||||||
|
|
||||||
generalSetting := &v1pb.InstanceSetting_GeneralSetting{
|
generalSetting := &v1pb.InstanceSetting_GeneralSetting{
|
||||||
Theme: theme,
|
|
||||||
DisallowUserRegistration: setting.DisallowUserRegistration,
|
DisallowUserRegistration: setting.DisallowUserRegistration,
|
||||||
DisallowPasswordAuth: setting.DisallowPasswordAuth,
|
DisallowPasswordAuth: setting.DisallowPasswordAuth,
|
||||||
AdditionalScript: setting.AdditionalScript,
|
AdditionalScript: setting.AdditionalScript,
|
||||||
|
|
@ -175,7 +169,6 @@ func convertInstanceGeneralSettingFromStore(setting *storepb.InstanceGeneralSett
|
||||||
Title: setting.CustomProfile.Title,
|
Title: setting.CustomProfile.Title,
|
||||||
Description: setting.CustomProfile.Description,
|
Description: setting.CustomProfile.Description,
|
||||||
LogoUrl: setting.CustomProfile.LogoUrl,
|
LogoUrl: setting.CustomProfile.LogoUrl,
|
||||||
Locale: setting.CustomProfile.Locale,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return generalSetting
|
return generalSetting
|
||||||
|
|
@ -186,7 +179,6 @@ func convertInstanceGeneralSettingToStore(setting *v1pb.InstanceSetting_GeneralS
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
generalSetting := &storepb.InstanceGeneralSetting{
|
generalSetting := &storepb.InstanceGeneralSetting{
|
||||||
Theme: setting.Theme,
|
|
||||||
DisallowUserRegistration: setting.DisallowUserRegistration,
|
DisallowUserRegistration: setting.DisallowUserRegistration,
|
||||||
DisallowPasswordAuth: setting.DisallowPasswordAuth,
|
DisallowPasswordAuth: setting.DisallowPasswordAuth,
|
||||||
AdditionalScript: setting.AdditionalScript,
|
AdditionalScript: setting.AdditionalScript,
|
||||||
|
|
@ -200,7 +192,6 @@ func convertInstanceGeneralSettingToStore(setting *v1pb.InstanceSetting_GeneralS
|
||||||
Title: setting.CustomProfile.Title,
|
Title: setting.CustomProfile.Title,
|
||||||
Description: setting.CustomProfile.Description,
|
Description: setting.CustomProfile.Description,
|
||||||
LogoUrl: setting.CustomProfile.LogoUrl,
|
LogoUrl: setting.CustomProfile.LogoUrl,
|
||||||
Locale: setting.CustomProfile.Locale,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return generalSetting
|
return generalSetting
|
||||||
|
|
|
||||||
|
|
@ -415,15 +415,9 @@ func getRSSHeading(ctx context.Context, stores *store.Store) (RSSHeading, error)
|
||||||
}
|
}
|
||||||
customProfile := settings.CustomProfile
|
customProfile := settings.CustomProfile
|
||||||
|
|
||||||
// Use locale as language if available, default to en-us
|
|
||||||
language := "en-us"
|
|
||||||
if customProfile.Locale != "" {
|
|
||||||
language = customProfile.Locale
|
|
||||||
}
|
|
||||||
|
|
||||||
return RSSHeading{
|
return RSSHeading{
|
||||||
Title: customProfile.Title,
|
Title: customProfile.Title,
|
||||||
Description: customProfile.Description,
|
Description: customProfile.Description,
|
||||||
Language: language,
|
Language: "en-us",
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { Outlet } from "react-router-dom";
|
||||||
import useNavigateTo from "./hooks/useNavigateTo";
|
import useNavigateTo from "./hooks/useNavigateTo";
|
||||||
import { instanceStore, userStore } from "./store";
|
import { instanceStore, userStore } from "./store";
|
||||||
import { cleanupExpiredOAuthState } from "./utils/oauth";
|
import { cleanupExpiredOAuthState } from "./utils/oauth";
|
||||||
import { loadTheme, setupSystemThemeListener } from "./utils/theme";
|
import { getThemeWithFallback, loadTheme, setupSystemThemeListener } from "./utils/theme";
|
||||||
|
|
||||||
const App = observer(() => {
|
const App = observer(() => {
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
|
|
@ -54,55 +54,44 @@ const App = observer(() => {
|
||||||
link.href = instanceGeneralSetting.customProfile.logoUrl || "/logo.webp";
|
link.href = instanceGeneralSetting.customProfile.logoUrl || "/logo.webp";
|
||||||
}, [instanceGeneralSetting.customProfile]);
|
}, [instanceGeneralSetting.customProfile]);
|
||||||
|
|
||||||
|
// Update HTML lang and dir attributes based on current locale
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const currentLocale = instanceStore.state.locale;
|
const currentLocale = i18n.language;
|
||||||
// This will trigger re-rendering of the whole app.
|
|
||||||
i18n.changeLanguage(currentLocale);
|
|
||||||
document.documentElement.setAttribute("lang", currentLocale);
|
document.documentElement.setAttribute("lang", currentLocale);
|
||||||
if (["ar", "fa"].includes(currentLocale)) {
|
if (["ar", "fa"].includes(currentLocale)) {
|
||||||
document.documentElement.setAttribute("dir", "rtl");
|
document.documentElement.setAttribute("dir", "rtl");
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.setAttribute("dir", "ltr");
|
document.documentElement.setAttribute("dir", "ltr");
|
||||||
}
|
}
|
||||||
}, [instanceStore.state.locale]);
|
}, [i18n.language]);
|
||||||
|
|
||||||
|
// Apply theme when user setting changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!userGeneralSetting) {
|
if (!userGeneralSetting) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const theme = getThemeWithFallback(userGeneralSetting.theme);
|
||||||
instanceStore.state.setPartial({
|
loadTheme(theme);
|
||||||
locale: userGeneralSetting.locale || instanceStore.state.locale,
|
}, [userGeneralSetting?.theme]);
|
||||||
theme: userGeneralSetting.theme || instanceStore.state.theme,
|
|
||||||
});
|
|
||||||
}, [userGeneralSetting?.locale, userGeneralSetting?.theme]);
|
|
||||||
|
|
||||||
// Load theme when instance theme changes or user setting changes
|
|
||||||
useEffect(() => {
|
|
||||||
const currentTheme = userGeneralSetting?.theme || instanceStore.state.theme;
|
|
||||||
if (currentTheme) {
|
|
||||||
loadTheme(currentTheme);
|
|
||||||
}
|
|
||||||
}, [userGeneralSetting?.theme, instanceStore.state.theme]);
|
|
||||||
|
|
||||||
// Listen for system theme changes when using "system" theme
|
// Listen for system theme changes when using "system" theme
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const currentTheme = userGeneralSetting?.theme || instanceStore.state.theme;
|
const theme = getThemeWithFallback(userGeneralSetting?.theme);
|
||||||
|
|
||||||
// Only set up listener if theme is "system"
|
// Only set up listener if theme is "system"
|
||||||
if (currentTheme !== "system") {
|
if (theme !== "system") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up listener for OS theme preference changes
|
// Set up listener for OS theme preference changes
|
||||||
const cleanup = setupSystemThemeListener(() => {
|
const cleanup = setupSystemThemeListener(() => {
|
||||||
// Reload theme when system preference changes
|
// Reload theme when system preference changes
|
||||||
loadTheme(currentTheme);
|
loadTheme(theme);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Cleanup listener on unmount or when theme changes
|
// Cleanup listener on unmount or when theme changes
|
||||||
return cleanup;
|
return cleanup;
|
||||||
}, [userGeneralSetting?.theme, instanceStore.state.theme]);
|
}, [userGeneralSetting?.theme]);
|
||||||
|
|
||||||
return <Outlet />;
|
return <Outlet />;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import i18n from "@/i18n";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { instanceStore } from "@/store";
|
import { getInitialTheme, loadTheme } from "@/utils/theme";
|
||||||
import LocaleSelect from "./LocaleSelect";
|
import LocaleSelect from "./LocaleSelect";
|
||||||
import ThemeSelect from "./ThemeSelect";
|
import ThemeSelect from "./ThemeSelect";
|
||||||
|
|
||||||
|
|
@ -9,10 +11,22 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthFooter = observer(({ className }: Props) => {
|
const AuthFooter = observer(({ className }: Props) => {
|
||||||
|
const { i18n: i18nInstance } = useTranslation();
|
||||||
|
const currentLocale = i18nInstance.language as Locale;
|
||||||
|
const currentTheme = getInitialTheme();
|
||||||
|
|
||||||
|
const handleLocaleChange = (locale: Locale) => {
|
||||||
|
i18n.changeLanguage(locale);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleThemeChange = (theme: string) => {
|
||||||
|
loadTheme(theme);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("mt-4 flex flex-row items-center justify-center w-full gap-2", className)}>
|
<div className={cn("mt-4 flex flex-row items-center justify-center w-full gap-2", className)}>
|
||||||
<LocaleSelect value={instanceStore.state.locale} onChange={(locale) => instanceStore.state.setPartial({ locale })} />
|
<LocaleSelect value={currentLocale} onChange={handleLocaleChange} />
|
||||||
<ThemeSelect value={instanceStore.state.theme} onValueChange={(theme) => instanceStore.state.setPartial({ theme })} />
|
<ThemeSelect value={currentTheme} onValueChange={handleThemeChange} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ import { CheckIcon, CopyIcon } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { instanceStore } from "@/store";
|
import { userStore } from "@/store";
|
||||||
|
import { getThemeWithFallback, resolveTheme } from "@/utils/theme";
|
||||||
import { MermaidBlock } from "./MermaidBlock";
|
import { MermaidBlock } from "./MermaidBlock";
|
||||||
|
|
||||||
interface PreProps {
|
interface PreProps {
|
||||||
|
|
@ -93,8 +94,9 @@ export const CodeBlock = observer(({ children, className, ...props }: PreProps)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const appTheme = instanceStore.state.theme;
|
const theme = getThemeWithFallback(userStore.state.userGeneralSetting?.theme);
|
||||||
const isDarkTheme = appTheme.includes("dark");
|
const resolvedTheme = resolveTheme(theme);
|
||||||
|
const isDarkTheme = resolvedTheme.includes("dark");
|
||||||
|
|
||||||
// Dynamically load highlight.js theme based on app theme
|
// Dynamically load highlight.js theme based on app theme
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -121,7 +123,7 @@ export const CodeBlock = observer(({ children, className, ...props }: PreProps)
|
||||||
};
|
};
|
||||||
|
|
||||||
dynamicImportStyle();
|
dynamicImportStyle();
|
||||||
}, [appTheme, isDarkTheme]);
|
}, [resolvedTheme, isDarkTheme]);
|
||||||
|
|
||||||
// Highlight code using highlight.js
|
// Highlight code using highlight.js
|
||||||
const highlightedCode = useMemo(() => {
|
const highlightedCode = useMemo(() => {
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import mermaid from "mermaid";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { instanceStore, userStore } from "@/store";
|
import { userStore } from "@/store";
|
||||||
import { resolveTheme, setupSystemThemeListener } from "@/utils/theme";
|
import { getThemeWithFallback, resolveTheme, setupSystemThemeListener } from "@/utils/theme";
|
||||||
|
|
||||||
interface MermaidBlockProps {
|
interface MermaidBlockProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
|
@ -25,7 +25,8 @@ export const MermaidBlock = observer(({ children, className }: MermaidBlockProps
|
||||||
const codeContent = String(codeElement?.props?.children || "").replace(/\n$/, "");
|
const codeContent = String(codeElement?.props?.children || "").replace(/\n$/, "");
|
||||||
|
|
||||||
// Get theme preference (reactive via MobX observer)
|
// Get theme preference (reactive via MobX observer)
|
||||||
const themePreference = userStore.state.userGeneralSetting?.theme || instanceStore.state.theme;
|
// Falls back to localStorage or system preference if no user setting
|
||||||
|
const themePreference = getThemeWithFallback(userStore.state.userGeneralSetting?.theme);
|
||||||
|
|
||||||
// Resolve theme to actual value (handles "system" theme + system theme changes)
|
// Resolve theme to actual value (handles "system" theme + system theme changes)
|
||||||
const currentTheme = useMemo(() => resolveTheme(themePreference), [themePreference, systemThemeChange]);
|
const currentTheme = useMemo(() => resolveTheme(themePreference), [themePreference, systemThemeChange]);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import { instanceSettingNamePrefix } from "@/store/common";
|
||||||
import { IdentityProvider } from "@/types/proto/api/v1/idp_service";
|
import { IdentityProvider } from "@/types/proto/api/v1/idp_service";
|
||||||
import { InstanceSetting_GeneralSetting, InstanceSetting_Key } from "@/types/proto/api/v1/instance_service";
|
import { InstanceSetting_GeneralSetting, InstanceSetting_Key } from "@/types/proto/api/v1/instance_service";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import ThemeSelect from "../ThemeSelect";
|
|
||||||
import UpdateCustomizedProfileDialog from "../UpdateCustomizedProfileDialog";
|
import UpdateCustomizedProfileDialog from "../UpdateCustomizedProfileDialog";
|
||||||
import SettingGroup from "./SettingGroup";
|
import SettingGroup from "./SettingGroup";
|
||||||
import SettingRow from "./SettingRow";
|
import SettingRow from "./SettingRow";
|
||||||
|
|
@ -79,14 +78,6 @@ const InstanceSection = observer(() => {
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
|
|
||||||
<SettingGroup title={t("setting.system-section.title")} showSeparator>
|
<SettingGroup title={t("setting.system-section.title")} showSeparator>
|
||||||
<SettingRow label="Theme">
|
|
||||||
<ThemeSelect
|
|
||||||
value={instanceGeneralSetting.theme || "default"}
|
|
||||||
onValueChange={(value: string) => updatePartialSetting({ theme: value })}
|
|
||||||
className="min-w-fit"
|
|
||||||
/>
|
|
||||||
</SettingRow>
|
|
||||||
|
|
||||||
<SettingRow label={t("setting.system-section.additional-style")} vertical>
|
<SettingRow label={t("setting.system-section.additional-style")} vertical>
|
||||||
<Textarea
|
<Textarea
|
||||||
className="font-mono w-full"
|
className="font-mono w-full"
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { instanceStore, userStore } from "@/store";
|
import i18n from "@/i18n";
|
||||||
|
import { userStore } from "@/store";
|
||||||
import { Visibility } from "@/types/proto/api/v1/memo_service";
|
import { Visibility } from "@/types/proto/api/v1/memo_service";
|
||||||
import { UserSetting_GeneralSetting } from "@/types/proto/api/v1/user_service";
|
import { UserSetting_GeneralSetting } from "@/types/proto/api/v1/user_service";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import { convertVisibilityFromString, convertVisibilityToString } from "@/utils/memo";
|
import { convertVisibilityFromString, convertVisibilityToString } from "@/utils/memo";
|
||||||
|
import { loadTheme } from "@/utils/theme";
|
||||||
import LocaleSelect from "../LocaleSelect";
|
import LocaleSelect from "../LocaleSelect";
|
||||||
import ThemeSelect from "../ThemeSelect";
|
import ThemeSelect from "../ThemeSelect";
|
||||||
import VisibilityIcon from "../VisibilityIcon";
|
import VisibilityIcon from "../VisibilityIcon";
|
||||||
|
|
@ -18,8 +20,8 @@ const PreferencesSection = observer(() => {
|
||||||
const generalSetting = userStore.state.userGeneralSetting;
|
const generalSetting = userStore.state.userGeneralSetting;
|
||||||
|
|
||||||
const handleLocaleSelectChange = async (locale: Locale) => {
|
const handleLocaleSelectChange = async (locale: Locale) => {
|
||||||
// Update instance store immediately for instant UI feedback
|
// Apply locale immediately for instant UI feedback
|
||||||
instanceStore.state.setPartial({ locale });
|
i18n.changeLanguage(locale);
|
||||||
// Persist to user settings
|
// Persist to user settings
|
||||||
await userStore.updateUserGeneralSetting({ locale }, ["locale"]);
|
await userStore.updateUserGeneralSetting({ locale }, ["locale"]);
|
||||||
};
|
};
|
||||||
|
|
@ -29,8 +31,8 @@ const PreferencesSection = observer(() => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleThemeChange = async (theme: string) => {
|
const handleThemeChange = async (theme: string) => {
|
||||||
// Update instance store immediately for instant UI feedback
|
// Apply theme immediately for instant UI feedback
|
||||||
instanceStore.state.setPartial({ theme });
|
loadTheme(theme);
|
||||||
// Persist to user settings
|
// Persist to user settings
|
||||||
await userStore.updateUserGeneralSetting({ theme }, ["theme"]);
|
await userStore.updateUserGeneralSetting({ theme }, ["theme"]);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { Monitor, Moon, MoonStar, Palette, Sun, Wallpaper } from "lucide-react";
|
import { Monitor, Moon, MoonStar, Palette, Sun, Wallpaper } from "lucide-react";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { instanceStore } from "@/store";
|
|
||||||
import { THEME_OPTIONS } from "@/utils/theme";
|
import { THEME_OPTIONS } from "@/utils/theme";
|
||||||
|
|
||||||
interface ThemeSelectProps {
|
interface ThemeSelectProps {
|
||||||
|
|
@ -19,13 +18,11 @@ const THEME_ICONS: Record<string, JSX.Element> = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const ThemeSelect = ({ value, onValueChange, className }: ThemeSelectProps = {}) => {
|
const ThemeSelect = ({ value, onValueChange, className }: ThemeSelectProps = {}) => {
|
||||||
const currentTheme = value || instanceStore.state.theme || "system";
|
const currentTheme = value || "system";
|
||||||
|
|
||||||
const handleThemeChange = (newTheme: Theme) => {
|
const handleThemeChange = (newTheme: string) => {
|
||||||
if (onValueChange) {
|
if (onValueChange) {
|
||||||
onValueChange(newTheme);
|
onValueChange(newTheme);
|
||||||
} else {
|
|
||||||
instanceStore.setTheme(newTheme);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,6 @@ import { instanceStore } from "@/store";
|
||||||
import { instanceSettingNamePrefix } from "@/store/common";
|
import { instanceSettingNamePrefix } from "@/store/common";
|
||||||
import { InstanceSetting_GeneralSetting_CustomProfile, InstanceSetting_Key } from "@/types/proto/api/v1/instance_service";
|
import { InstanceSetting_GeneralSetting_CustomProfile, InstanceSetting_Key } from "@/types/proto/api/v1/instance_service";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import LocaleSelect from "./LocaleSelect";
|
|
||||||
import ThemeSelect from "./ThemeSelect";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
|
@ -52,18 +50,11 @@ function UpdateCustomizedProfileDialog({ open, onOpenChange, onSuccess }: Props)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLocaleSelectChange = (locale: Locale) => {
|
|
||||||
setPartialState({
|
|
||||||
locale: locale,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRestoreButtonClick = () => {
|
const handleRestoreButtonClick = () => {
|
||||||
setPartialState({
|
setPartialState({
|
||||||
title: "Memos",
|
title: "Memos",
|
||||||
logoUrl: "/logo.webp",
|
logoUrl: "/logo.webp",
|
||||||
description: "",
|
description: "",
|
||||||
locale: "en",
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -126,16 +117,6 @@ function UpdateCustomizedProfileDialog({ open, onOpenChange, onSuccess }: Props)
|
||||||
placeholder="Enter description"
|
placeholder="Enter description"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label>{t("setting.system-section.customize-server.locale")}</Label>
|
|
||||||
<LocaleSelect value={customProfile.locale} onChange={handleLocaleSelectChange} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label>Theme</Label>
|
|
||||||
<ThemeSelect />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter className="flex-col sm:flex-row sm:justify-between gap-2">
|
<DialogFooter className="flex-col sm:flex-row sm:justify-between gap-2">
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ import { observer } from "mobx-react-lite";
|
||||||
import { authServiceClient } from "@/grpcweb";
|
import { authServiceClient } from "@/grpcweb";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||||
import { locales } from "@/i18n";
|
import i18n, { locales } from "@/i18n";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Routes } from "@/router";
|
import { Routes } from "@/router";
|
||||||
import { instanceStore, userStore } from "@/store";
|
import { userStore } from "@/store";
|
||||||
import { getLocaleDisplayName, useTranslate } from "@/utils/i18n";
|
import { getLocaleDisplayName, useTranslate } from "@/utils/i18n";
|
||||||
import { THEME_OPTIONS } from "@/utils/theme";
|
import { loadTheme, THEME_OPTIONS } from "@/utils/theme";
|
||||||
import UserAvatar from "./UserAvatar";
|
import UserAvatar from "./UserAvatar";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
|
@ -34,13 +34,16 @@ const UserMenu = observer((props: Props) => {
|
||||||
const currentTheme = generalSetting?.theme || "default";
|
const currentTheme = generalSetting?.theme || "default";
|
||||||
|
|
||||||
const handleLocaleChange = async (locale: Locale) => {
|
const handleLocaleChange = async (locale: Locale) => {
|
||||||
// Update instance store immediately for instant UI feedback
|
// Apply locale immediately for instant UI feedback
|
||||||
instanceStore.state.setPartial({ locale });
|
i18n.changeLanguage(locale);
|
||||||
// Persist to user settings
|
// Persist to user settings
|
||||||
await userStore.updateUserGeneralSetting({ locale }, ["locale"]);
|
await userStore.updateUserGeneralSetting({ locale }, ["locale"]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleThemeChange = async (theme: string) => {
|
const handleThemeChange = async (theme: string) => {
|
||||||
|
// Apply theme immediately for instant UI feedback
|
||||||
|
loadTheme(theme);
|
||||||
|
// Persist to user settings
|
||||||
await userStore.updateUserGeneralSetting({ theme }, ["theme"]);
|
await userStore.updateUserGeneralSetting({ theme }, ["theme"]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,12 @@ import router from "./router";
|
||||||
// Configure MobX before importing any stores
|
// Configure MobX before importing any stores
|
||||||
import "./store/config";
|
import "./store/config";
|
||||||
import { initialInstanceStore } from "./store/instance";
|
import { initialInstanceStore } from "./store/instance";
|
||||||
import { initialUserStore } from "./store/user";
|
import userStore, { initialUserStore } from "./store/user";
|
||||||
import { applyThemeEarly } from "./utils/theme";
|
import { applyThemeEarly } from "./utils/theme";
|
||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
|
|
||||||
// Apply theme early to prevent flash of wrong theme
|
// Apply theme early to prevent flash of wrong theme
|
||||||
|
// This uses localStorage as the source before user settings are loaded
|
||||||
applyThemeEarly();
|
applyThemeEarly();
|
||||||
|
|
||||||
const Main = observer(() => (
|
const Main = observer(() => (
|
||||||
|
|
@ -24,9 +25,14 @@ const Main = observer(() => (
|
||||||
));
|
));
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
// Initialize stores
|
||||||
await initialInstanceStore();
|
await initialInstanceStore();
|
||||||
await initialUserStore();
|
await initialUserStore();
|
||||||
|
|
||||||
|
// Apply user preferences (theme & locale) after user settings are loaded
|
||||||
|
// This will override the early theme with user's actual preference
|
||||||
|
userStore.applyUserPreferences();
|
||||||
|
|
||||||
const container = document.getElementById("root");
|
const container = document.getElementById("root");
|
||||||
const root = createRoot(container as HTMLElement);
|
const root = createRoot(container as HTMLElement);
|
||||||
root.render(<Main />);
|
root.render(<Main />);
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,6 @@ export {
|
||||||
memoNamePrefix,
|
memoNamePrefix,
|
||||||
userNamePrefix,
|
userNamePrefix,
|
||||||
} from "./common";
|
} from "./common";
|
||||||
// Re-export instance types
|
|
||||||
export type { Theme } from "./instance";
|
|
||||||
export { isValidTheme } from "./instance";
|
|
||||||
// Re-export filter types
|
// Re-export filter types
|
||||||
export type { FilterFactor, MemoFilter } from "./memoFilter";
|
export type { FilterFactor, MemoFilter } from "./memoFilter";
|
||||||
export { getMemoFilterKey, parseFilterQuery, stringifyFilters } from "./memoFilter";
|
export { getMemoFilterKey, parseFilterQuery, stringifyFilters } from "./memoFilter";
|
||||||
|
|
|
||||||
|
|
@ -9,21 +9,11 @@ import {
|
||||||
InstanceSetting_Key,
|
InstanceSetting_Key,
|
||||||
InstanceSetting_MemoRelatedSetting,
|
InstanceSetting_MemoRelatedSetting,
|
||||||
} from "@/types/proto/api/v1/instance_service";
|
} from "@/types/proto/api/v1/instance_service";
|
||||||
import { isValidateLocale } from "@/utils/i18n";
|
|
||||||
import { createServerStore, StandardState } from "./base-store";
|
import { createServerStore, StandardState } from "./base-store";
|
||||||
import { instanceSettingNamePrefix } from "./common";
|
import { instanceSettingNamePrefix } from "./common";
|
||||||
import { createRequestKey } from "./store-utils";
|
import { createRequestKey } from "./store-utils";
|
||||||
|
|
||||||
const VALID_THEMES = ["system", "default", "default-dark", "midnight", "paper", "whitewall"] as const;
|
|
||||||
export type Theme = (typeof VALID_THEMES)[number];
|
|
||||||
|
|
||||||
export function isValidTheme(theme: string): theme is Theme {
|
|
||||||
return VALID_THEMES.includes(theme as Theme);
|
|
||||||
}
|
|
||||||
|
|
||||||
class InstanceState extends StandardState {
|
class InstanceState extends StandardState {
|
||||||
locale: string = "en";
|
|
||||||
theme: Theme | string = "system";
|
|
||||||
profile: InstanceProfile = InstanceProfile.fromPartial({});
|
profile: InstanceProfile = InstanceProfile.fromPartial({});
|
||||||
settings: InstanceSetting[] = [];
|
settings: InstanceSetting[] = [];
|
||||||
|
|
||||||
|
|
@ -42,29 +32,6 @@ class InstanceState extends StandardState {
|
||||||
return setting?.memoRelatedSetting || InstanceSetting_MemoRelatedSetting.fromPartial({});
|
return setting?.memoRelatedSetting || InstanceSetting_MemoRelatedSetting.fromPartial({});
|
||||||
}).get();
|
}).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
setPartial(partial: Partial<InstanceState>): void {
|
|
||||||
const finalState = { ...this, ...partial };
|
|
||||||
|
|
||||||
// Validate locale
|
|
||||||
if (partial.locale !== undefined && !isValidateLocale(finalState.locale)) {
|
|
||||||
console.warn(`Invalid locale "${finalState.locale}", falling back to "en"`);
|
|
||||||
finalState.locale = "en";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate theme - accept string and validate
|
|
||||||
if (partial.theme !== undefined) {
|
|
||||||
const themeStr = String(finalState.theme);
|
|
||||||
if (!isValidTheme(themeStr)) {
|
|
||||||
console.warn(`Invalid theme "${themeStr}", falling back to "default"`);
|
|
||||||
finalState.theme = "default";
|
|
||||||
} else {
|
|
||||||
finalState.theme = themeStr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(this, finalState);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const instanceStore = (() => {
|
const instanceStore = (() => {
|
||||||
|
|
@ -114,33 +81,6 @@ const instanceStore = (() => {
|
||||||
return setting || InstanceSetting.fromPartial({});
|
return setting || InstanceSetting.fromPartial({});
|
||||||
};
|
};
|
||||||
|
|
||||||
const setTheme = async (theme: string): Promise<void> => {
|
|
||||||
// Validate theme
|
|
||||||
if (!isValidTheme(theme)) {
|
|
||||||
console.warn(`Invalid theme "${theme}", ignoring`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update local state immediately
|
|
||||||
state.setPartial({ theme });
|
|
||||||
|
|
||||||
// Persist to server
|
|
||||||
const generalSetting = state.generalSetting;
|
|
||||||
const updatedGeneralSetting = InstanceSetting_GeneralSetting.fromPartial({
|
|
||||||
...generalSetting,
|
|
||||||
customProfile: {
|
|
||||||
...generalSetting.customProfile,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await upsertInstanceSetting(
|
|
||||||
InstanceSetting.fromPartial({
|
|
||||||
name: `${instanceSettingNamePrefix}${InstanceSetting_Key.GENERAL}`,
|
|
||||||
generalSetting: updatedGeneralSetting,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchInstanceProfile = async (): Promise<InstanceProfile> => {
|
const fetchInstanceProfile = async (): Promise<InstanceProfile> => {
|
||||||
const requestKey = createRequestKey("fetchInstanceProfile");
|
const requestKey = createRequestKey("fetchInstanceProfile");
|
||||||
|
|
||||||
|
|
@ -161,7 +101,6 @@ const instanceStore = (() => {
|
||||||
fetchInstanceProfile,
|
fetchInstanceProfile,
|
||||||
upsertInstanceSetting,
|
upsertInstanceSetting,
|
||||||
getInstanceSettingByKey,
|
getInstanceSettingByKey,
|
||||||
setTheme,
|
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
@ -178,19 +117,9 @@ export const initialInstanceStore = async (): Promise<void> => {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Apply settings to state
|
// Apply settings to state
|
||||||
const instanceGeneralSetting = instanceStore.state.generalSetting;
|
Object.assign(instanceStore.state, { profile: instanceProfile });
|
||||||
instanceStore.state.setPartial({
|
|
||||||
locale: instanceGeneralSetting.customProfile?.locale || "en",
|
|
||||||
theme: instanceGeneralSetting.theme || "system",
|
|
||||||
profile: instanceProfile,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to initialize instance store:", error);
|
console.error("Failed to initialize instance store:", error);
|
||||||
// Set default fallback values
|
|
||||||
instanceStore.state.setPartial({
|
|
||||||
locale: "en",
|
|
||||||
theme: "system",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { uniqueId } from "lodash-es";
|
import { uniqueId } from "lodash-es";
|
||||||
import { computed, makeAutoObservable } from "mobx";
|
import { computed, makeAutoObservable } from "mobx";
|
||||||
import { authServiceClient, shortcutServiceClient, userServiceClient } from "@/grpcweb";
|
import { authServiceClient, shortcutServiceClient, userServiceClient } from "@/grpcweb";
|
||||||
|
import i18n from "@/i18n";
|
||||||
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
|
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
|
||||||
import {
|
import {
|
||||||
User,
|
User,
|
||||||
|
|
@ -13,8 +14,8 @@ import {
|
||||||
UserSetting_WebhooksSetting,
|
UserSetting_WebhooksSetting,
|
||||||
UserStats,
|
UserStats,
|
||||||
} from "@/types/proto/api/v1/user_service";
|
} from "@/types/proto/api/v1/user_service";
|
||||||
import { findNearestMatchedLanguage } from "@/utils/i18n";
|
import { getLocaleWithFallback } from "@/utils/i18n";
|
||||||
import instanceStore from "./instance";
|
import { getThemeWithFallback, loadTheme } from "@/utils/theme";
|
||||||
import { createRequestKey, RequestDeduplicator, StoreError } from "./store-utils";
|
import { createRequestKey, RequestDeduplicator, StoreError } from "./store-utils";
|
||||||
|
|
||||||
class LocalState {
|
class LocalState {
|
||||||
|
|
@ -283,6 +284,20 @@ const userStore = (() => {
|
||||||
state.statsStateId = id;
|
state.statsStateId = id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Applies user preferences (theme and locale) with proper fallbacks
|
||||||
|
// This should be called after user settings are loaded
|
||||||
|
const applyUserPreferences = () => {
|
||||||
|
const generalSetting = state.userGeneralSetting;
|
||||||
|
|
||||||
|
// Apply theme with fallback: user setting -> localStorage -> system
|
||||||
|
const theme = getThemeWithFallback(generalSetting?.theme);
|
||||||
|
loadTheme(theme);
|
||||||
|
|
||||||
|
// Apply locale with fallback: user setting -> browser language
|
||||||
|
const locale = getLocaleWithFallback(generalSetting?.locale);
|
||||||
|
i18n.changeLanguage(locale);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state,
|
state,
|
||||||
getOrFetchUserByName,
|
getOrFetchUserByName,
|
||||||
|
|
@ -299,6 +314,7 @@ const userStore = (() => {
|
||||||
deleteNotification,
|
deleteNotification,
|
||||||
fetchUserStats,
|
fetchUserStats,
|
||||||
setStatsStateId,
|
setStatsStateId,
|
||||||
|
applyUserPreferences,
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
@ -306,22 +322,18 @@ const userStore = (() => {
|
||||||
// 1. Fetch current authenticated user session
|
// 1. Fetch current authenticated user session
|
||||||
// 2. Set current user in store (required for subsequent calls)
|
// 2. Set current user in store (required for subsequent calls)
|
||||||
// 3. Fetch user settings (depends on currentUser being set)
|
// 3. Fetch user settings (depends on currentUser being set)
|
||||||
// 4. Apply user preferences to instance store
|
|
||||||
export const initialUserStore = async () => {
|
export const initialUserStore = async () => {
|
||||||
try {
|
try {
|
||||||
// Step 1: Authenticate and get current user
|
// Step 1: Authenticate and get current user
|
||||||
const { user: currentUser } = await authServiceClient.getCurrentSession({});
|
const { user: currentUser } = await authServiceClient.getCurrentSession({});
|
||||||
|
|
||||||
if (!currentUser) {
|
if (!currentUser) {
|
||||||
// No authenticated user - clear state and use default locale
|
// No authenticated user - clear state
|
||||||
userStore.state.setPartial({
|
userStore.state.setPartial({
|
||||||
currentUser: undefined,
|
currentUser: undefined,
|
||||||
userGeneralSetting: undefined,
|
userGeneralSetting: undefined,
|
||||||
userMapByName: {},
|
userMapByName: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const locale = findNearestMatchedLanguage(navigator.language);
|
|
||||||
instanceStore.state.setPartial({ locale });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -339,27 +351,8 @@ export const initialUserStore = async () => {
|
||||||
// CRITICAL: This must happen after currentUser is set in step 2
|
// CRITICAL: This must happen after currentUser is set in step 2
|
||||||
// The fetchUserSettings() and fetchUserStats() methods check state.currentUser internally
|
// The fetchUserSettings() and fetchUserStats() methods check state.currentUser internally
|
||||||
await Promise.all([userStore.fetchUserSettings(), userStore.fetchUserStats()]);
|
await Promise.all([userStore.fetchUserSettings(), userStore.fetchUserStats()]);
|
||||||
|
|
||||||
// Step 4: Apply user preferences to instance
|
|
||||||
// CRITICAL: This must happen after fetchUserSettings() completes
|
|
||||||
// We need userGeneralSetting to be populated before accessing it
|
|
||||||
const generalSetting = userStore.state.userGeneralSetting;
|
|
||||||
if (generalSetting) {
|
|
||||||
// Note: setPartial will validate theme automatically
|
|
||||||
instanceStore.state.setPartial({
|
|
||||||
locale: generalSetting.locale,
|
|
||||||
theme: generalSetting.theme || "default", // Validation handled by setPartial
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Fallback if settings weren't loaded
|
|
||||||
const locale = findNearestMatchedLanguage(navigator.language);
|
|
||||||
instanceStore.state.setPartial({ locale });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// On any error, fall back to browser language detection
|
|
||||||
console.error("Failed to initialize user store:", error);
|
console.error("Failed to initialize user store:", error);
|
||||||
const locale = findNearestMatchedLanguage(navigator.language);
|
|
||||||
instanceStore.state.setPartial({ locale });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,11 +92,6 @@ export function instanceSetting_KeyToNumber(object: InstanceSetting_Key): number
|
||||||
|
|
||||||
/** General instance settings configuration. */
|
/** General instance settings configuration. */
|
||||||
export interface InstanceSetting_GeneralSetting {
|
export interface InstanceSetting_GeneralSetting {
|
||||||
/**
|
|
||||||
* theme is the name of the selected theme.
|
|
||||||
* This references a CSS file in the web/public/themes/ directory.
|
|
||||||
*/
|
|
||||||
theme: string;
|
|
||||||
/** disallow_user_registration disallows user registration. */
|
/** disallow_user_registration disallows user registration. */
|
||||||
disallowUserRegistration: boolean;
|
disallowUserRegistration: boolean;
|
||||||
/** disallow_password_auth disallows password authentication. */
|
/** disallow_password_auth disallows password authentication. */
|
||||||
|
|
@ -126,7 +121,6 @@ export interface InstanceSetting_GeneralSetting_CustomProfile {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
logoUrl: string;
|
logoUrl: string;
|
||||||
locale: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Storage configuration settings for instance attachments. */
|
/** Storage configuration settings for instance attachments. */
|
||||||
|
|
@ -453,7 +447,6 @@ export const InstanceSetting: MessageFns<InstanceSetting> = {
|
||||||
|
|
||||||
function createBaseInstanceSetting_GeneralSetting(): InstanceSetting_GeneralSetting {
|
function createBaseInstanceSetting_GeneralSetting(): InstanceSetting_GeneralSetting {
|
||||||
return {
|
return {
|
||||||
theme: "",
|
|
||||||
disallowUserRegistration: false,
|
disallowUserRegistration: false,
|
||||||
disallowPasswordAuth: false,
|
disallowPasswordAuth: false,
|
||||||
additionalScript: "",
|
additionalScript: "",
|
||||||
|
|
@ -467,9 +460,6 @@ function createBaseInstanceSetting_GeneralSetting(): InstanceSetting_GeneralSett
|
||||||
|
|
||||||
export const InstanceSetting_GeneralSetting: MessageFns<InstanceSetting_GeneralSetting> = {
|
export const InstanceSetting_GeneralSetting: MessageFns<InstanceSetting_GeneralSetting> = {
|
||||||
encode(message: InstanceSetting_GeneralSetting, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
encode(message: InstanceSetting_GeneralSetting, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||||
if (message.theme !== "") {
|
|
||||||
writer.uint32(10).string(message.theme);
|
|
||||||
}
|
|
||||||
if (message.disallowUserRegistration !== false) {
|
if (message.disallowUserRegistration !== false) {
|
||||||
writer.uint32(16).bool(message.disallowUserRegistration);
|
writer.uint32(16).bool(message.disallowUserRegistration);
|
||||||
}
|
}
|
||||||
|
|
@ -504,14 +494,6 @@ export const InstanceSetting_GeneralSetting: MessageFns<InstanceSetting_GeneralS
|
||||||
while (reader.pos < end) {
|
while (reader.pos < end) {
|
||||||
const tag = reader.uint32();
|
const tag = reader.uint32();
|
||||||
switch (tag >>> 3) {
|
switch (tag >>> 3) {
|
||||||
case 1: {
|
|
||||||
if (tag !== 10) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
message.theme = reader.string();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
case 2: {
|
case 2: {
|
||||||
if (tag !== 16) {
|
if (tag !== 16) {
|
||||||
break;
|
break;
|
||||||
|
|
@ -590,7 +572,6 @@ export const InstanceSetting_GeneralSetting: MessageFns<InstanceSetting_GeneralS
|
||||||
},
|
},
|
||||||
fromPartial(object: DeepPartial<InstanceSetting_GeneralSetting>): InstanceSetting_GeneralSetting {
|
fromPartial(object: DeepPartial<InstanceSetting_GeneralSetting>): InstanceSetting_GeneralSetting {
|
||||||
const message = createBaseInstanceSetting_GeneralSetting();
|
const message = createBaseInstanceSetting_GeneralSetting();
|
||||||
message.theme = object.theme ?? "";
|
|
||||||
message.disallowUserRegistration = object.disallowUserRegistration ?? false;
|
message.disallowUserRegistration = object.disallowUserRegistration ?? false;
|
||||||
message.disallowPasswordAuth = object.disallowPasswordAuth ?? false;
|
message.disallowPasswordAuth = object.disallowPasswordAuth ?? false;
|
||||||
message.additionalScript = object.additionalScript ?? "";
|
message.additionalScript = object.additionalScript ?? "";
|
||||||
|
|
@ -606,7 +587,7 @@ export const InstanceSetting_GeneralSetting: MessageFns<InstanceSetting_GeneralS
|
||||||
};
|
};
|
||||||
|
|
||||||
function createBaseInstanceSetting_GeneralSetting_CustomProfile(): InstanceSetting_GeneralSetting_CustomProfile {
|
function createBaseInstanceSetting_GeneralSetting_CustomProfile(): InstanceSetting_GeneralSetting_CustomProfile {
|
||||||
return { title: "", description: "", logoUrl: "", locale: "" };
|
return { title: "", description: "", logoUrl: "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const InstanceSetting_GeneralSetting_CustomProfile: MessageFns<InstanceSetting_GeneralSetting_CustomProfile> = {
|
export const InstanceSetting_GeneralSetting_CustomProfile: MessageFns<InstanceSetting_GeneralSetting_CustomProfile> = {
|
||||||
|
|
@ -623,9 +604,6 @@ export const InstanceSetting_GeneralSetting_CustomProfile: MessageFns<InstanceSe
|
||||||
if (message.logoUrl !== "") {
|
if (message.logoUrl !== "") {
|
||||||
writer.uint32(26).string(message.logoUrl);
|
writer.uint32(26).string(message.logoUrl);
|
||||||
}
|
}
|
||||||
if (message.locale !== "") {
|
|
||||||
writer.uint32(34).string(message.locale);
|
|
||||||
}
|
|
||||||
return writer;
|
return writer;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -660,14 +638,6 @@ export const InstanceSetting_GeneralSetting_CustomProfile: MessageFns<InstanceSe
|
||||||
message.logoUrl = reader.string();
|
message.logoUrl = reader.string();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
case 4: {
|
|
||||||
if (tag !== 34) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
message.locale = reader.string();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if ((tag & 7) === 4 || tag === 0) {
|
if ((tag & 7) === 4 || tag === 0) {
|
||||||
break;
|
break;
|
||||||
|
|
@ -689,7 +659,6 @@ export const InstanceSetting_GeneralSetting_CustomProfile: MessageFns<InstanceSe
|
||||||
message.title = object.title ?? "";
|
message.title = object.title ?? "";
|
||||||
message.description = object.description ?? "";
|
message.description = object.description ?? "";
|
||||||
message.logoUrl = object.logoUrl ?? "";
|
message.logoUrl = object.logoUrl ?? "";
|
||||||
message.locale = object.locale ?? "";
|
|
||||||
return message;
|
return message;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,19 @@ export const isValidateLocale = (locale: string | undefined | null): boolean =>
|
||||||
return locales.includes(locale);
|
return locales.includes(locale);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Gets the locale to use with proper priority:
|
||||||
|
// 1. User setting (if logged in and has preference)
|
||||||
|
// 2. Browser language preference
|
||||||
|
export const getLocaleWithFallback = (userLocale?: string): Locale => {
|
||||||
|
// Priority 1: User setting (if logged in and valid)
|
||||||
|
if (userLocale && isValidateLocale(userLocale)) {
|
||||||
|
return userLocale as Locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 2: Browser language
|
||||||
|
return findNearestMatchedLanguage(navigator.language);
|
||||||
|
};
|
||||||
|
|
||||||
// Get the display name for a locale in its native language
|
// Get the display name for a locale in its native language
|
||||||
export const getLocaleDisplayName = (locale: string): string => {
|
export const getLocaleDisplayName = (locale: string): string => {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,24 @@ import midnightThemeContent from "../themes/midnight.css?raw";
|
||||||
import paperThemeContent from "../themes/paper.css?raw";
|
import paperThemeContent from "../themes/paper.css?raw";
|
||||||
import whitewallThemeContent from "../themes/whitewall.css?raw";
|
import whitewallThemeContent from "../themes/whitewall.css?raw";
|
||||||
|
|
||||||
const VALID_THEMES = ["system", "default", "default-dark", "midnight", "paper", "whitewall"] as const;
|
// ============================================================================
|
||||||
type ValidTheme = (typeof VALID_THEMES)[number];
|
// Types and Constants
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
const THEME_CONTENT: Record<ValidTheme, string | null> = {
|
const VALID_THEMES = ["system", "default", "default-dark", "midnight", "paper", "whitewall"] as const;
|
||||||
system: null, // System theme dynamically chooses between default and default-dark
|
|
||||||
|
export type Theme = (typeof VALID_THEMES)[number];
|
||||||
|
export type ResolvedTheme = Exclude<Theme, "system">;
|
||||||
|
|
||||||
|
export interface ThemeOption {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const STORAGE_KEY = "memos-theme";
|
||||||
|
const STYLE_ELEMENT_ID = "instance-theme";
|
||||||
|
|
||||||
|
const THEME_CONTENT: Record<ResolvedTheme, string | null> = {
|
||||||
default: null,
|
default: null,
|
||||||
"default-dark": defaultDarkThemeContent,
|
"default-dark": defaultDarkThemeContent,
|
||||||
midnight: midnightThemeContent,
|
midnight: midnightThemeContent,
|
||||||
|
|
@ -15,11 +28,6 @@ const THEME_CONTENT: Record<ValidTheme, string | null> = {
|
||||||
whitewall: whitewallThemeContent,
|
whitewall: whitewallThemeContent,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ThemeOption {
|
|
||||||
value: string;
|
|
||||||
label: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const THEME_OPTIONS: ThemeOption[] = [
|
export const THEME_OPTIONS: ThemeOption[] = [
|
||||||
{ value: "system", label: "Sync with system" },
|
{ value: "system", label: "Sync with system" },
|
||||||
{ value: "default", label: "Light" },
|
{ value: "default", label: "Light" },
|
||||||
|
|
@ -29,102 +37,201 @@ export const THEME_OPTIONS: ThemeOption[] = [
|
||||||
{ value: "whitewall", label: "Whitewall" },
|
{ value: "whitewall", label: "Whitewall" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const validateTheme = (theme: string): ValidTheme => {
|
// ============================================================================
|
||||||
return VALID_THEMES.includes(theme as ValidTheme) ? (theme as ValidTheme) : "default";
|
// Theme Validation and Detection
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates and normalizes a theme string to a valid theme.
|
||||||
|
* Falls back to "default" for invalid themes.
|
||||||
|
*/
|
||||||
|
const validateTheme = (theme: string): Theme => {
|
||||||
|
return VALID_THEMES.includes(theme as Theme) ? (theme as Theme) : "default";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSystemTheme = (): "default" | "default-dark" => {
|
/**
|
||||||
if (typeof window !== "undefined" && window.matchMedia) {
|
* Detects the system's preferred color scheme.
|
||||||
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "default-dark" : "default";
|
* @returns "default-dark" for dark mode, "default" for light mode
|
||||||
|
*/
|
||||||
|
export const getSystemTheme = (): ResolvedTheme => {
|
||||||
|
if (typeof window !== "undefined" && window.matchMedia?.("(prefers-color-scheme: dark)").matches) {
|
||||||
|
return "default-dark";
|
||||||
}
|
}
|
||||||
return "default";
|
return "default";
|
||||||
};
|
};
|
||||||
|
|
||||||
// Resolves "system" to actual theme based on OS preference
|
/**
|
||||||
export const resolveTheme = (theme: string): "default" | "default-dark" | "midnight" | "paper" | "whitewall" => {
|
* Resolves "system" theme to the actual theme based on OS preference.
|
||||||
if (theme === "system") {
|
* Other themes are returned as-is after validation.
|
||||||
return getSystemTheme();
|
*/
|
||||||
}
|
export const resolveTheme = (theme: string): ResolvedTheme => {
|
||||||
const validTheme = validateTheme(theme);
|
const validTheme = validateTheme(theme);
|
||||||
return validTheme === "system" ? getSystemTheme() : validTheme;
|
return validTheme === "system" ? getSystemTheme() : validTheme;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Gets the theme that should be applied on initial load
|
// ============================================================================
|
||||||
export const getInitialTheme = (): ValidTheme => {
|
// LocalStorage Helpers
|
||||||
// Try to get stored theme from localStorage
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely reads the theme from localStorage.
|
||||||
|
* @returns The stored theme, or null if not found or unavailable
|
||||||
|
*/
|
||||||
|
const getStoredTheme = (): Theme | null => {
|
||||||
try {
|
try {
|
||||||
const storedTheme = localStorage.getItem("memos-theme");
|
const stored = localStorage.getItem(STORAGE_KEY);
|
||||||
if (storedTheme && VALID_THEMES.includes(storedTheme as ValidTheme)) {
|
return stored && VALID_THEMES.includes(stored as Theme) ? (stored as Theme) : null;
|
||||||
return storedTheme as ValidTheme;
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
// localStorage might not be available
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely stores the theme to localStorage.
|
||||||
|
*/
|
||||||
|
const setStoredTheme = (theme: Theme): void => {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(STORAGE_KEY, theme);
|
||||||
|
} catch {
|
||||||
|
// localStorage might not be available (SSR, private browsing, etc.)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Theme Selection with Fallbacks
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the theme for initial page load (before user settings are available).
|
||||||
|
* Priority: localStorage -> system preference
|
||||||
|
*/
|
||||||
|
export const getInitialTheme = (): Theme => {
|
||||||
|
return getStoredTheme() ?? "system";
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the theme with full fallback chain.
|
||||||
|
* Priority:
|
||||||
|
* 1. User setting (if logged in and has preference)
|
||||||
|
* 2. localStorage (from previous session)
|
||||||
|
* 3. System preference
|
||||||
|
*/
|
||||||
|
export const getThemeWithFallback = (userTheme?: string): Theme => {
|
||||||
|
// Priority 1: User setting
|
||||||
|
if (userTheme && VALID_THEMES.includes(userTheme as Theme)) {
|
||||||
|
return userTheme as Theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Priority 2: localStorage
|
||||||
|
const stored = getStoredTheme();
|
||||||
|
if (stored) {
|
||||||
|
return stored;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 3: System preference
|
||||||
return "system";
|
return "system";
|
||||||
};
|
};
|
||||||
|
|
||||||
// Applies the theme early to prevent flash of wrong theme
|
// ============================================================================
|
||||||
|
// DOM Manipulation
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the existing theme style element from the DOM.
|
||||||
|
*/
|
||||||
|
const removeThemeStyle = (): void => {
|
||||||
|
document.getElementById(STYLE_ELEMENT_ID)?.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injects theme CSS into the document head.
|
||||||
|
* Skips injection for the default theme (uses base CSS).
|
||||||
|
*/
|
||||||
|
const injectThemeStyle = (theme: ResolvedTheme): void => {
|
||||||
|
removeThemeStyle();
|
||||||
|
|
||||||
|
if (theme === "default") {
|
||||||
|
return; // Use base CSS for default theme
|
||||||
|
}
|
||||||
|
|
||||||
|
const css = THEME_CONTENT[theme];
|
||||||
|
if (css) {
|
||||||
|
const style = document.createElement("style");
|
||||||
|
style.id = STYLE_ELEMENT_ID;
|
||||||
|
style.textContent = css;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the data-theme attribute on the document element.
|
||||||
|
* This allows CSS to react to the current theme.
|
||||||
|
*/
|
||||||
|
const setThemeAttribute = (theme: ResolvedTheme): void => {
|
||||||
|
document.documentElement.setAttribute("data-theme", theme);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Main Theme Loading
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads and applies a theme.
|
||||||
|
* This function:
|
||||||
|
* 1. Validates the theme
|
||||||
|
* 2. Resolves "system" to actual theme
|
||||||
|
* 3. Injects theme CSS
|
||||||
|
* 4. Sets data-theme attribute
|
||||||
|
* 5. Persists to localStorage
|
||||||
|
*/
|
||||||
|
export const loadTheme = (themeName: string): void => {
|
||||||
|
const validTheme = validateTheme(themeName);
|
||||||
|
const resolvedTheme = resolveTheme(validTheme);
|
||||||
|
|
||||||
|
injectThemeStyle(resolvedTheme);
|
||||||
|
setThemeAttribute(resolvedTheme);
|
||||||
|
setStoredTheme(validTheme); // Store original theme preference (not resolved)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies theme early during initial page load to prevent FOUC.
|
||||||
|
* Uses only localStorage and system preference (no user settings yet).
|
||||||
|
*/
|
||||||
export const applyThemeEarly = (): void => {
|
export const applyThemeEarly = (): void => {
|
||||||
const theme = getInitialTheme();
|
const theme = getInitialTheme();
|
||||||
loadTheme(theme);
|
loadTheme(theme);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loadTheme = (themeName: string): void => {
|
// ============================================================================
|
||||||
const validTheme = validateTheme(themeName);
|
// System Theme Listener
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
// Resolve "system" to actual theme based on OS preference
|
/**
|
||||||
const resolvedTheme = resolveTheme(validTheme);
|
* Sets up a listener for OS-level theme preference changes.
|
||||||
|
* Supports both modern (addEventListener) and legacy (addListener) APIs.
|
||||||
// Remove existing theme
|
*
|
||||||
document.getElementById("instance-theme")?.remove();
|
* @param onThemeChange - Callback invoked when system theme changes
|
||||||
|
* @returns Cleanup function to remove the listener
|
||||||
// Apply theme (skip for default)
|
*/
|
||||||
if (resolvedTheme !== "default") {
|
|
||||||
const css = THEME_CONTENT[resolvedTheme];
|
|
||||||
if (css) {
|
|
||||||
const style = document.createElement("style");
|
|
||||||
style.id = "instance-theme";
|
|
||||||
style.textContent = css;
|
|
||||||
document.head.appendChild(style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set data attribute with resolved theme
|
|
||||||
document.documentElement.setAttribute("data-theme", resolvedTheme);
|
|
||||||
|
|
||||||
// Store theme preference (original, not resolved) for future loads
|
|
||||||
try {
|
|
||||||
localStorage.setItem("memos-theme", validTheme);
|
|
||||||
} catch {
|
|
||||||
// localStorage might not be available
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sets up a listener for system theme preference changes
|
|
||||||
export const setupSystemThemeListener = (onThemeChange: () => void): (() => void) => {
|
export const setupSystemThemeListener = (onThemeChange: () => void): (() => void) => {
|
||||||
|
// Guard against SSR
|
||||||
if (typeof window === "undefined" || !window.matchMedia) {
|
if (typeof window === "undefined" || !window.matchMedia) {
|
||||||
return () => {}; // No-op cleanup
|
return () => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
|
||||||
// Handle theme change
|
// Modern API (preferred)
|
||||||
const handleChange = () => {
|
|
||||||
onThemeChange();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Modern API (addEventListener)
|
|
||||||
if (mediaQuery.addEventListener) {
|
if (mediaQuery.addEventListener) {
|
||||||
mediaQuery.addEventListener("change", handleChange);
|
mediaQuery.addEventListener("change", onThemeChange);
|
||||||
return () => mediaQuery.removeEventListener("change", handleChange);
|
return () => mediaQuery.removeEventListener("change", onThemeChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy API (addListener) - for older browsers
|
// Legacy API (Safari < 14)
|
||||||
if (mediaQuery.addListener) {
|
if (mediaQuery.addListener) {
|
||||||
mediaQuery.addListener(handleChange);
|
mediaQuery.addListener(onThemeChange);
|
||||||
return () => mediaQuery.removeListener(handleChange);
|
return () => mediaQuery.removeListener(onThemeChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {}; // No-op cleanup
|
return () => {};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue