Introduce setting to disable thumbnail loading and generation when images are stored in S3

pull/5185/head
Florian Dewald 1 week ago
parent d516f9aab2
commit cb84b7bc8a

@ -169,6 +169,9 @@ message WorkspaceSetting {
bool enable_blur_nsfw_content = 9;
// nsfw_tags is the list of tags that mark content as NSFW for blurring.
repeated string nsfw_tags = 10;
// use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3.
// When false, images stored in S3 will not have thumbnails generated.
bool use_thumbnails_for_s3_images = 11;
}
}

@ -428,7 +428,6 @@ type GetUserRequest struct {
// Supports both numeric IDs and username strings:
// - users/{id} (e.g., users/101)
// - users/{username} (e.g., users/steven)
//
// Format: users/{id_or_username}
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// Optional. The fields to return in the response.

@ -672,9 +672,12 @@ type WorkspaceSetting_MemoRelatedSetting struct {
// enable_blur_nsfw_content enables blurring of content marked as not safe for work (NSFW).
EnableBlurNsfwContent bool `protobuf:"varint,9,opt,name=enable_blur_nsfw_content,json=enableBlurNsfwContent,proto3" json:"enable_blur_nsfw_content,omitempty"`
// nsfw_tags is the list of tags that mark content as NSFW for blurring.
NsfwTags []string `protobuf:"bytes,10,rep,name=nsfw_tags,json=nsfwTags,proto3" json:"nsfw_tags,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
NsfwTags []string `protobuf:"bytes,10,rep,name=nsfw_tags,json=nsfwTags,proto3" json:"nsfw_tags,omitempty"`
// use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3.
// When false, images stored in S3 will not have thumbnails generated.
UseThumbnailsForS3Images bool `protobuf:"varint,11,opt,name=use_thumbnails_for_s3_images,json=useThumbnailsForS3Images,proto3" json:"use_thumbnails_for_s3_images,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WorkspaceSetting_MemoRelatedSetting) Reset() {
@ -770,6 +773,13 @@ func (x *WorkspaceSetting_MemoRelatedSetting) GetNsfwTags() []string {
return nil
}
func (x *WorkspaceSetting_MemoRelatedSetting) GetUseThumbnailsForS3Images() bool {
if x != nil {
return x.UseThumbnailsForS3Images
}
return false
}
// Custom profile configuration for workspace branding.
type WorkspaceSetting_GeneralSetting_CustomProfile struct {
state protoimpl.MessageState `protogen:"open.v1"`
@ -935,7 +945,7 @@ const file_api_v1_workspace_service_proto_rawDesc = "" +
"\aversion\x18\x02 \x01(\tR\aversion\x12\x12\n" +
"\x04mode\x18\x03 \x01(\tR\x04mode\x12!\n" +
"\finstance_url\x18\x06 \x01(\tR\vinstanceUrl\"\x1c\n" +
"\x1aGetWorkspaceProfileRequest\"\x97\x11\n" +
"\x1aGetWorkspaceProfileRequest\"\xd7\x11\n" +
"\x10WorkspaceSetting\x12\x17\n" +
"\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12X\n" +
"\x0fgeneral_setting\x18\x02 \x01(\v2-.memos.api.v1.WorkspaceSetting.GeneralSettingH\x00R\x0egeneralSetting\x12X\n" +
@ -972,7 +982,7 @@ const file_api_v1_workspace_service_proto_rawDesc = "" +
"\x18STORAGE_TYPE_UNSPECIFIED\x10\x00\x12\f\n" +
"\bDATABASE\x10\x01\x12\t\n" +
"\x05LOCAL\x10\x02\x12\x06\n" +
"\x02S3\x10\x03\x1a\xd8\x03\n" +
"\x02S3\x10\x03\x1a\x98\x04\n" +
"\x12MemoRelatedSetting\x12<\n" +
"\x1adisallow_public_visibility\x18\x01 \x01(\bR\x18disallowPublicVisibility\x127\n" +
"\x18display_with_update_time\x18\x02 \x01(\bR\x15displayWithUpdateTime\x120\n" +
@ -983,7 +993,8 @@ const file_api_v1_workspace_service_proto_rawDesc = "" +
"\x1adisable_markdown_shortcuts\x18\b \x01(\bR\x18disableMarkdownShortcuts\x127\n" +
"\x18enable_blur_nsfw_content\x18\t \x01(\bR\x15enableBlurNsfwContent\x12\x1b\n" +
"\tnsfw_tags\x18\n" +
" \x03(\tR\bnsfwTags\"F\n" +
" \x03(\tR\bnsfwTags\x12>\n" +
"\x1cuse_thumbnails_for_s3_images\x18\v \x01(\bR\x18useThumbnailsForS3Images\"F\n" +
"\x03Key\x12\x13\n" +
"\x0fKEY_UNSPECIFIED\x10\x00\x12\v\n" +
"\aGENERAL\x10\x01\x12\v\n" +

@ -78,35 +78,23 @@ paths:
parameters:
- name: pageSize
in: query
description: |-
Optional. The maximum number of attachments to return.
The service may return fewer than this value.
If unspecified, at most 50 attachments will be returned.
The maximum value is 1000; values above 1000 will be coerced to 1000.
description: "Optional. The maximum number of attachments to return.\r\n The service may return fewer than this value.\r\n If unspecified, at most 50 attachments will be returned.\r\n The maximum value is 1000; values above 1000 will be coerced to 1000."
schema:
type: integer
format: int32
- name: pageToken
in: query
description: |-
Optional. A page token, received from a previous `ListAttachments` call.
Provide this to retrieve the subsequent page.
description: "Optional. A page token, received from a previous `ListAttachments` call.\r\n Provide this to retrieve the subsequent page."
schema:
type: string
- name: filter
in: query
description: |-
Optional. Filter to apply to the list results.
Example: "type=image/png" or "filename:*.jpg"
Supported operators: =, !=, <, <=, >, >=, :
Supported fields: filename, type, size, create_time, memo
description: "Optional. Filter to apply to the list results.\r\n Example: \"type=image/png\" or \"filename:*.jpg\"\r\n Supported operators: =, !=, <, <=, >, >=, :\r\n Supported fields: filename, type, size, create_time, memo"
schema:
type: string
- name: orderBy
in: query
description: |-
Optional. The order to sort results by.
Example: "create_time desc" or "filename asc"
description: "Optional. The order to sort results by.\r\n Example: \"create_time desc\" or \"filename asc\""
schema:
type: string
responses:
@ -130,9 +118,7 @@ paths:
parameters:
- name: attachmentId
in: query
description: |-
Optional. The attachment ID to use for this attachment.
If empty, a unique ID will be generated.
description: "Optional. The attachment ID to use for this attachment.\r\n If empty, a unique ID will be generated."
schema:
type: string
requestBody:
@ -516,26 +502,18 @@ paths:
parameters:
- name: pageSize
in: query
description: |-
Optional. The maximum number of memos to return.
The service may return fewer than this value.
If unspecified, at most 50 memos will be returned.
The maximum value is 1000; values above 1000 will be coerced to 1000.
description: "Optional. The maximum number of memos to return.\r\n The service may return fewer than this value.\r\n If unspecified, at most 50 memos will be returned.\r\n The maximum value is 1000; values above 1000 will be coerced to 1000."
schema:
type: integer
format: int32
- name: pageToken
in: query
description: |-
Optional. A page token, received from a previous `ListMemos` call.
Provide this to retrieve the subsequent page.
description: "Optional. A page token, received from a previous `ListMemos` call.\r\n Provide this to retrieve the subsequent page."
schema:
type: string
- name: state
in: query
description: |-
Optional. The state of the memos to list.
Default to `NORMAL`. Set to `ARCHIVED` to list archived memos.
description: "Optional. The state of the memos to list.\r\n Default to `NORMAL`. Set to `ARCHIVED` to list archived memos."
schema:
enum:
- STATE_UNSPECIFIED
@ -545,20 +523,12 @@ paths:
format: enum
- name: orderBy
in: query
description: |-
Optional. The order to sort results by.
Default to "display_time desc".
Supports comma-separated list of fields following AIP-132.
Example: "pinned desc, display_time desc" or "create_time asc"
Supported fields: pinned, display_time, create_time, update_time, name
description: "Optional. The order to sort results by.\r\n Default to \"display_time desc\".\r\n Supports comma-separated list of fields following AIP-132.\r\n Example: \"pinned desc, display_time desc\" or \"create_time asc\"\r\n Supported fields: pinned, display_time, create_time, update_time, name"
schema:
type: string
- name: filter
in: query
description: |-
Optional. Filter to apply to the list results.
Filter is a CEL expression to filter memos.
Refer to `Shortcut.filter`.
description: "Optional. Filter to apply to the list results.\r\n Filter is a CEL expression to filter memos.\r\n Refer to `Shortcut.filter`."
schema:
type: string
- name: showDeleted
@ -587,9 +557,7 @@ paths:
parameters:
- name: memoId
in: query
description: |-
Optional. The memo ID to use for this memo.
If empty, a unique ID will be generated.
description: "Optional. The memo ID to use for this memo.\r\n If empty, a unique ID will be generated."
schema:
type: string
- name: validateOnly
@ -636,9 +604,7 @@ paths:
type: string
- name: readMask
in: query
description: |-
Optional. The fields to return in the response.
If not specified, all fields are returned.
description: "Optional. The fields to return in the response.\r\n If not specified, all fields are returned."
schema:
type: string
format: field-mask
@ -2173,9 +2139,7 @@ components:
properties:
name:
type: string
description: |-
The name of the attachment.
Format: attachments/{attachment}
description: "The name of the attachment.\r\n Format: attachments/{attachment}"
createTime:
readOnly: true
type: string
@ -2201,9 +2165,7 @@ components:
description: Output only. The size of the attachment in bytes.
memo:
type: string
description: |-
Optional. The related memo. Refer to `Memo.name`.
Format: memos/{memo}
description: "Optional. The related memo. Refer to `Memo.name`.\r\n Format: memos/{memo}"
CreateSessionRequest:
type: object
properties:
@ -2277,9 +2239,7 @@ components:
properties:
parent:
type: string
description: |-
Required. The parent, who owns the tags.
Format: memos/{memo}. Use "memos/-" to delete all tags.
description: "Required. The parent, who owns the tags.\r\n Format: memos/{memo}. Use \"memos/-\" to delete all tags."
tag:
type: string
description: Required. The tag name to delete.
@ -2440,9 +2400,7 @@ components:
description: The list of attachments.
nextPageToken:
type: string
description: |-
A token that can be sent as `page_token` to retrieve the next page.
If this field is omitted, there are no subsequent pages.
description: "A token that can be sent as `page_token` to retrieve the next page.\r\n If this field is omitted, there are no subsequent pages."
totalSize:
type: integer
description: The total count of attachments (may be approximate).
@ -2542,9 +2500,7 @@ components:
description: The list of memos.
nextPageToken:
type: string
description: |-
A token that can be sent as `page_token` to retrieve the next page.
If this field is omitted, there are no subsequent pages.
description: "A token that can be sent as `page_token` to retrieve the next page.\r\n If this field is omitted, there are no subsequent pages."
totalSize:
type: integer
description: The total count of memos (may be approximate).
@ -2646,9 +2602,7 @@ components:
properties:
name:
type: string
description: |-
The resource name of the memo.
Format: memos/{memo}, memo is the user defined id or uuid.
description: "The resource name of the memo.\r\n Format: memos/{memo}, memo is the user defined id or uuid."
state:
enum:
- STATE_UNSPECIFIED
@ -2660,9 +2614,7 @@ components:
creator:
readOnly: true
type: string
description: |-
The name of the creator.
Format: users/{user}
description: "The name of the creator.\r\n Format: users/{user}"
createTime:
readOnly: true
type: string
@ -2722,9 +2674,7 @@ components:
parent:
readOnly: true
type: string
description: |-
Output only. The name of the parent memo.
Format: memos/{memo}
description: "Output only. The name of the parent memo.\r\n Format: memos/{memo}"
snippet:
readOnly: true
type: string
@ -2762,9 +2712,7 @@ components:
properties:
name:
type: string
description: |-
The resource name of the memo.
Format: memos/{memo}
description: "The resource name of the memo.\r\n Format: memos/{memo}"
snippet:
readOnly: true
type: string
@ -2810,21 +2758,14 @@ components:
name:
readOnly: true
type: string
description: |-
The resource name of the reaction.
Format: reactions/{reaction}
description: "The resource name of the reaction.\r\n Format: reactions/{reaction}"
creator:
readOnly: true
type: string
description: |-
The resource name of the creator.
Format: users/{user}
description: "The resource name of the creator.\r\n Format: users/{user}"
contentId:
type: string
description: |-
The resource name of the content.
For memo reactions, this should be the memo's resource name.
Format: memos/{memo}
description: "The resource name of the content.\r\n For memo reactions, this should be the memo's resource name.\r\n Format: memos/{memo}"
reactionType:
type: string
description: "Required. The type of reaction (e.g., \"\U0001F44D\", \"❤️\", \"\U0001F604\")."
@ -2842,9 +2783,7 @@ components:
properties:
parent:
type: string
description: |-
Required. The parent, who owns the tags.
Format: memos/{memo}. Use "memos/-" to rename all tags.
description: "Required. The parent, who owns the tags.\r\n Format: memos/{memo}. Use \"memos/-\" to rename all tags."
oldTag:
type: string
description: Required. The old tag name to rename.
@ -2859,9 +2798,7 @@ components:
properties:
name:
type: string
description: |-
Required. The resource name of the memo.
Format: memos/{memo}
description: "Required. The resource name of the memo.\r\n Format: memos/{memo}"
attachments:
type: array
items:
@ -2875,9 +2812,7 @@ components:
properties:
name:
type: string
description: |-
Required. The resource name of the memo.
Format: memos/{memo}
description: "Required. The resource name of the memo.\r\n Format: memos/{memo}"
relations:
type: array
items:
@ -2930,9 +2865,7 @@ components:
type: string
usePathStyle:
type: boolean
description: |-
S3 configuration for cloud storage backend.
Reference: https://developers.cloudflare.com/r2/examples/aws/aws-sdk-go/
description: "S3 configuration for cloud storage backend.\r\n Reference: https://developers.cloudflare.com/r2/examples/aws/aws-sdk-go/"
UpsertMemoReactionRequest:
required:
- name
@ -2941,9 +2874,7 @@ components:
properties:
name:
type: string
description: |-
Required. The resource name of the memo.
Format: memos/{memo}
description: "Required. The resource name of the memo.\r\n Format: memos/{memo}"
reaction:
allOf:
- $ref: '#/components/schemas/Reaction'
@ -3219,9 +3150,7 @@ components:
properties:
owner:
type: string
description: |-
The name of instance owner.
Format: users/{user}
description: "The name of instance owner.\r\n Format: users/{user}"
version:
type: string
description: Version is the current version of instance.
@ -3237,9 +3166,7 @@ components:
properties:
name:
type: string
description: |-
The name of the workspace setting.
Format: workspace/settings/{setting}
description: "The name of the workspace setting.\r\n Format: workspace/settings/{setting}"
generalSetting:
$ref: '#/components/schemas/WorkspaceSetting_GeneralSetting'
storageSetting:
@ -3252,9 +3179,7 @@ components:
properties:
theme:
type: string
description: |-
theme is the name of the selected theme.
This references a CSS file in the web/public/themes/ directory.
description: "theme is the name of the selected theme.\r\n This references a CSS file in the web/public/themes/ directory."
disallowUserRegistration:
type: boolean
description: disallow_user_registration disallows user registration.
@ -3273,10 +3198,7 @@ components:
description: custom_profile is the custom profile.
weekStartDayOffset:
type: integer
description: |-
week_start_day_offset is the week start day offset from Sunday.
0: Sunday, 1: Monday, 2: Tuesday, 3: Wednesday, 4: Thursday, 5: Friday, 6: Saturday
Default is Sunday.
description: "week_start_day_offset is the week start day offset from Sunday.\r\n 0: Sunday, 1: Monday, 2: Tuesday, 3: Wednesday, 4: Thursday, 5: Friday, 6: Saturday\r\n Default is Sunday."
format: int32
disallowChangeUsername:
type: boolean
@ -3320,6 +3242,9 @@ components:
items:
type: string
description: nsfw_tags is the list of tags that mark content as NSFW for blurring.
useThumbnailsForS3Images:
type: boolean
description: "use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3.\r\n When false, images stored in S3 will not have thumbnails generated."
description: Memo-related workspace settings and policies.
WorkspaceSetting_StorageSetting:
type: object
@ -3335,9 +3260,7 @@ components:
format: enum
filepathTemplate:
type: string
description: |-
The template of file path.
e.g. assets/{timestamp}_{filename}
description: "The template of file path.\r\n e.g. assets/{timestamp}_{filename}"
uploadSizeLimitMb:
type: string
description: The max upload size in megabytes.

@ -676,9 +676,12 @@ type WorkspaceMemoRelatedSetting struct {
// enable_blur_nsfw_content enables blurring of content marked as not safe for work (NSFW).
EnableBlurNsfwContent bool `protobuf:"varint,9,opt,name=enable_blur_nsfw_content,json=enableBlurNsfwContent,proto3" json:"enable_blur_nsfw_content,omitempty"`
// nsfw_tags is the list of tags that mark content as NSFW for blurring.
NsfwTags []string `protobuf:"bytes,10,rep,name=nsfw_tags,json=nsfwTags,proto3" json:"nsfw_tags,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
NsfwTags []string `protobuf:"bytes,10,rep,name=nsfw_tags,json=nsfwTags,proto3" json:"nsfw_tags,omitempty"`
// use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3.
// When false, images stored in S3 will not have thumbnails generated.
UseThumbnailsForS3Images bool `protobuf:"varint,11,opt,name=use_thumbnails_for_s3_images,json=useThumbnailsForS3Images,proto3" json:"use_thumbnails_for_s3_images,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WorkspaceMemoRelatedSetting) Reset() {
@ -774,6 +777,13 @@ func (x *WorkspaceMemoRelatedSetting) GetNsfwTags() []string {
return nil
}
func (x *WorkspaceMemoRelatedSetting) GetUseThumbnailsForS3Images() bool {
if x != nil {
return x.UseThumbnailsForS3Images
}
return false
}
var File_store_workspace_setting_proto protoreflect.FileDescriptor
const file_store_workspace_setting_proto_rawDesc = "" +
@ -821,7 +831,7 @@ const file_store_workspace_setting_proto_rawDesc = "" +
"\bendpoint\x18\x03 \x01(\tR\bendpoint\x12\x16\n" +
"\x06region\x18\x04 \x01(\tR\x06region\x12\x16\n" +
"\x06bucket\x18\x05 \x01(\tR\x06bucket\x12$\n" +
"\x0euse_path_style\x18\x06 \x01(\bR\fusePathStyle\"\xe1\x03\n" +
"\x0euse_path_style\x18\x06 \x01(\bR\fusePathStyle\"\xa1\x04\n" +
"\x1bWorkspaceMemoRelatedSetting\x12<\n" +
"\x1adisallow_public_visibility\x18\x01 \x01(\bR\x18disallowPublicVisibility\x127\n" +
"\x18display_with_update_time\x18\x02 \x01(\bR\x15displayWithUpdateTime\x120\n" +
@ -832,7 +842,8 @@ const file_store_workspace_setting_proto_rawDesc = "" +
"\x1adisable_markdown_shortcuts\x18\b \x01(\bR\x18disableMarkdownShortcuts\x127\n" +
"\x18enable_blur_nsfw_content\x18\t \x01(\bR\x15enableBlurNsfwContent\x12\x1b\n" +
"\tnsfw_tags\x18\n" +
" \x03(\tR\bnsfwTags*s\n" +
" \x03(\tR\bnsfwTags\x12>\n" +
"\x1cuse_thumbnails_for_s3_images\x18\v \x01(\bR\x18useThumbnailsForS3Images*s\n" +
"\x13WorkspaceSettingKey\x12%\n" +
"!WORKSPACE_SETTING_KEY_UNSPECIFIED\x10\x00\x12\t\n" +
"\x05BASIC\x10\x01\x12\v\n" +

@ -114,4 +114,7 @@ message WorkspaceMemoRelatedSetting {
bool enable_blur_nsfw_content = 9;
// nsfw_tags is the list of tags that mark content as NSFW for blurring.
repeated string nsfw_tags = 10;
// use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3.
// When false, images stored in S3 will not have thumbnails generated.
bool use_thumbnails_for_s3_images = 11;
}

@ -232,16 +232,29 @@ func (s *APIV1Service) GetAttachmentBinary(ctx context.Context, request *v1pb.Ge
}
if request.Thumbnail && util.HasPrefixes(attachment.Type, SupportedThumbnailMimeTypes...) {
thumbnailBlob, err := s.getOrGenerateThumbnail(attachment)
if err != nil {
// thumbnail failures are logged as warnings and not cosidered critical failures as
// a attachment image can be used in its place.
slog.Warn("failed to get attachment thumbnail image", slog.Any("error", err))
} else {
return &httpbody.HttpBody{
ContentType: attachment.Type,
Data: thumbnailBlob,
}, nil
// Check if we should generate thumbnails for S3 images
shouldGenerateThumbnail := true
if attachment.StorageType == storepb.AttachmentStorageType_S3 {
memoRelatedSetting, err := s.Store.GetWorkspaceMemoRelatedSetting(ctx)
if err != nil {
slog.Warn("failed to get workspace memo related setting", slog.Any("error", err))
} else if !memoRelatedSetting.UseThumbnailsForS3Images {
shouldGenerateThumbnail = false
}
}
if shouldGenerateThumbnail {
thumbnailBlob, err := s.getOrGenerateThumbnail(attachment)
if err != nil {
// thumbnail failures are logged as warnings and not cosidered critical failures as
// a attachment image can be used in its place.
slog.Warn("failed to get attachment thumbnail image", slog.Any("error", err))
} else {
return &httpbody.HttpBody{
ContentType: attachment.Type,
Data: thumbnailBlob,
}, nil
}
}
}

@ -264,6 +264,7 @@ func convertWorkspaceMemoRelatedSettingFromStore(setting *storepb.WorkspaceMemoR
DisableMarkdownShortcuts: setting.DisableMarkdownShortcuts,
EnableBlurNsfwContent: setting.EnableBlurNsfwContent,
NsfwTags: setting.NsfwTags,
UseThumbnailsForS3Images: setting.UseThumbnailsForS3Images,
}
}
@ -281,6 +282,7 @@ func convertWorkspaceMemoRelatedSettingToStore(setting *v1pb.WorkspaceSetting_Me
DisableMarkdownShortcuts: setting.DisableMarkdownShortcuts,
EnableBlurNsfwContent: setting.EnableBlurNsfwContent,
NsfwTags: setting.NsfwTags,
UseThumbnailsForS3Images: setting.UseThumbnailsForS3Images,
}
}

@ -175,6 +175,13 @@ const MemoRelatedSettings = observer(() => {
</div>
</div>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.memo-related-settings.use-thumbnails-for-s3-images")}</span>
<Switch
checked={memoRelatedSetting.useThumbnailsForS3Images}
onCheckedChange={(checked) => updatePartialSetting({ useThumbnailsForS3Images: checked })}
/>
</div>
<div className="mt-2 w-full flex justify-end">
<Button disabled={isEqual(memoRelatedSetting, originalSetting)} onClick={updateSetting}>
{t("common.save")}

@ -316,7 +316,8 @@
"enable-memo-comments": "Enable memo comments",
"enable-memo-location": "Enable memo location",
"reactions": "Reactions",
"title": "Memo related settings"
"title": "Memo related settings",
"use-thumbnails-for-s3-images": "Generate and serve thumbnails for images stored in S3"
},
"my-account": "My Account",
"preference": "Preferences",

@ -230,6 +230,11 @@ export interface WorkspaceSetting_MemoRelatedSetting {
enableBlurNsfwContent: boolean;
/** nsfw_tags is the list of tags that mark content as NSFW for blurring. */
nsfwTags: string[];
/**
* use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3.
* When false, images stored in S3 will not have thumbnails generated.
*/
useThumbnailsForS3Images: boolean;
}
/** Request message for GetWorkspaceSetting method. */
@ -905,6 +910,7 @@ function createBaseWorkspaceSetting_MemoRelatedSetting(): WorkspaceSetting_MemoR
disableMarkdownShortcuts: false,
enableBlurNsfwContent: false,
nsfwTags: [],
useThumbnailsForS3Images: false,
};
}
@ -937,6 +943,9 @@ export const WorkspaceSetting_MemoRelatedSetting: MessageFns<WorkspaceSetting_Me
for (const v of message.nsfwTags) {
writer.uint32(82).string(v!);
}
if (message.useThumbnailsForS3Images !== false) {
writer.uint32(88).bool(message.useThumbnailsForS3Images);
}
return writer;
},
@ -1019,6 +1028,14 @@ export const WorkspaceSetting_MemoRelatedSetting: MessageFns<WorkspaceSetting_Me
message.nsfwTags.push(reader.string());
continue;
}
case 11: {
if (tag !== 88) {
break;
}
message.useThumbnailsForS3Images = reader.bool();
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
@ -1042,6 +1059,7 @@ export const WorkspaceSetting_MemoRelatedSetting: MessageFns<WorkspaceSetting_Me
message.disableMarkdownShortcuts = object.disableMarkdownShortcuts ?? false;
message.enableBlurNsfwContent = object.enableBlurNsfwContent ?? false;
message.nsfwTags = object.nsfwTags?.map((e) => e) || [];
message.useThumbnailsForS3Images = object.useThumbnailsForS3Images ?? false;
return message;
},
};

@ -1,4 +1,5 @@
import { Attachment } from "@/types/proto/api/v1/attachment_service";
import workspaceStore from "@/store/workspace";
export const getAttachmentUrl = (attachment: Attachment) => {
if (attachment.externalLink) {
@ -9,6 +10,15 @@ export const getAttachmentUrl = (attachment: Attachment) => {
};
export const getAttachmentThumbnailUrl = (attachment: Attachment) => {
// Check if thumbnails are disabled for S3 images
const isS3Image = !!attachment.externalLink;
const useThumbnailsForS3Images = workspaceStore.state.memoRelatedSetting.useThumbnailsForS3Images;
// Don't request thumbnails for S3 images if the setting is disabled
if (isS3Image && !useThumbnailsForS3Images) {
return getAttachmentUrl(attachment);
}
return `${window.location.origin}/file/${attachment.name}/${attachment.filename}?thumbnail=true`;
};

Loading…
Cancel
Save