diff --git a/src/client/components/media-caption.vue b/src/client/components/media-caption.vue index 690927d4c5..73eba23025 100644 --- a/src/client/components/media-caption.vue +++ b/src/client/components/media-caption.vue @@ -3,10 +3,13 @@ <div class="container"> <div class="fullwidth top-caption"> <div class="mk-dialog"> - <header v-if="title"><Mfm :text="title"/></header> + <header> + <Mfm v-if="title" class="title" :text="title"/> + <span class="text-count" :class="{ over: remainingLength < 0 }">{{ remainingLength }}</span> + </header> <textarea autofocus v-model="inputValue" :placeholder="input.placeholder" @keydown="onInputKeydown"></textarea> <div class="buttons" v-if="(showOkButton || showCancelButton)"> - <MkButton inline @click="ok" primary>{{ $ts.ok }}</MkButton> + <MkButton inline @click="ok" primary :disabled="remainingLength < 0">{{ $ts.ok }}</MkButton> <MkButton inline @click="cancel" >{{ $ts.cancel }}</MkButton> </div> </div> @@ -26,10 +29,12 @@ <script lang="ts"> import { defineComponent } from 'vue'; +import { length } from 'stringz'; import MkModal from '@client/components/ui/modal.vue'; import MkButton from '@client/components/ui/button.vue'; import bytes from '@client/filters/bytes'; import number from '@client/filters/number'; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits'; export default defineComponent({ components: { @@ -79,6 +84,13 @@ export default defineComponent({ document.removeEventListener('keydown', this.onKeydown); }, + computed: { + remainingLength(): number { + if (typeof this.inputValue != "string") return DB_MAX_IMAGE_COMMENT_LENGTH; + return DB_MAX_IMAGE_COMMENT_LENGTH - length(this.inputValue); + } + }, + methods: { bytes, number, @@ -156,8 +168,18 @@ export default defineComponent({ > header { margin: 0 0 8px 0; - font-weight: bold; - font-size: 20px; + position: relative; + + > .title { + font-weight: bold; + font-size: 20px; + } + + > .text-count { + opacity: 0.7; + position: absolute; + right: 0; + } } > .buttons { diff --git a/src/misc/hard-limits.ts b/src/misc/hard-limits.ts index 2a61cb321b..1039f7335a 100644 --- a/src/misc/hard-limits.ts +++ b/src/misc/hard-limits.ts @@ -6,3 +6,9 @@ * Surrogate pairs count as one */ export const DB_MAX_NOTE_TEXT_LENGTH = 8192; + +/** + * Maximum image description length that can be stored in DB. + * Surrogate pairs count as one + */ +export const DB_MAX_IMAGE_COMMENT_LENGTH = 512; diff --git a/src/misc/truncate.ts b/src/misc/truncate.ts new file mode 100644 index 0000000000..cb120331a1 --- /dev/null +++ b/src/misc/truncate.ts @@ -0,0 +1,11 @@ +import { substring } from 'stringz'; + +export function truncate(input: string, size: number): string; +export function truncate(input: string | undefined, size: number): string | undefined; +export function truncate(input: string | undefined, size: number): string | undefined { + if (!input) { + return input; + } else { + return substring(input, 0, size); + } +} diff --git a/src/remote/activitypub/models/image.ts b/src/remote/activitypub/models/image.ts index cd28d59a16..89259d30fb 100644 --- a/src/remote/activitypub/models/image.ts +++ b/src/remote/activitypub/models/image.ts @@ -5,6 +5,8 @@ import { fetchMeta } from '@/misc/fetch-meta'; import { apLogger } from '../logger'; import { DriveFile } from '@/models/entities/drive-file'; import { DriveFiles } from '@/models/index'; +import { truncate } from '@/misc/truncate'; +import { DM_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits'; const logger = apLogger; @@ -28,7 +30,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<Drive const instance = await fetchMeta(); const cache = instance.cacheRemoteFiles; - let file = await uploadFromUrl(image.url, actor, null, image.url, image.sensitive, false, !cache, image.name); + let file = await uploadFromUrl(image.url, actor, null, image.url, image.sensitive, false, !cache, truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH)); if (file.isLink) { // URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、 diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index 4823def7cb..84b2f0c51c 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -28,22 +28,13 @@ import { getConnection } from 'typeorm'; import { toArray } from '@/prelude/array'; import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata'; import { normalizeForSearch } from '@/misc/normalize-for-search'; +import { truncate } from '@/misc/truncate'; const logger = apLogger; const nameLength = 128; const summaryLength = 2048; -function truncate(input: string, size: number): string; -function truncate(input: string | undefined, size: number): string | undefined; -function truncate(input: string | undefined, size: number): string | undefined { - if (!input || input.length <= size) { - return input; - } else { - return input.substring(0, size); - } -} - /** * Validate and convert to actor object * @param x Fetched object diff --git a/src/server/api/endpoints/drive/files/update.ts b/src/server/api/endpoints/drive/files/update.ts index 1ef445625c..f277a9c3dc 100644 --- a/src/server/api/endpoints/drive/files/update.ts +++ b/src/server/api/endpoints/drive/files/update.ts @@ -4,6 +4,7 @@ import { publishDriveStream } from '@/services/stream'; import define from '../../../define'; import { ApiError } from '../../../error'; import { DriveFiles, DriveFolders } from '@/models/index'; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits'; export const meta = { tags: ['drive'], @@ -33,7 +34,7 @@ export const meta = { }, comment: { - validator: $.optional.nullable.str, + validator: $.optional.nullable.str.max(DB_MAX_IMAGE_COMMENT_LENGTH), default: undefined as any, } }, diff --git a/src/server/api/endpoints/drive/files/upload-from-url.ts b/src/server/api/endpoints/drive/files/upload-from-url.ts index f37f316efb..9f10a42d24 100644 --- a/src/server/api/endpoints/drive/files/upload-from-url.ts +++ b/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -5,6 +5,7 @@ import uploadFromUrl from '@/services/drive/upload-from-url'; import define from '../../../define'; import { DriveFiles } from '@/models/index'; import { publishMainStream } from '@/services/stream'; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits'; export const meta = { tags: ['drive'], @@ -35,7 +36,7 @@ export const meta = { }, comment: { - validator: $.optional.nullable.str, + validator: $.optional.nullable.str.max(DB_MAX_IMAGE_COMMENT_LENGTH), default: null, },