From 2c15bafdf30dbecc21f19852bdc32f913667dab5 Mon Sep 17 00:00:00 2001 From: Yiqun Zhang Date: Fri, 31 Jul 2026 12:09:56 +0800 Subject: [PATCH 1/4] :sparkles: discard oversized non-file paste items to prevent huge database rows (#4698) --- .../crosspaste/paste/PasteReleaseService.kt | 46 ++++++ .../process/DiscardOversizedNonFilePlugin.kt | 73 +++++++++ .../ui/settings/NetworkSettingsContentView.kt | 23 +++ .../crosspaste/DesktopPasteComponentModule.kt | 4 + .../com/crosspaste/config/DesktopAppConfig.kt | 12 ++ .../desktopMain/resources/i18n/de.properties | 5 + .../desktopMain/resources/i18n/en.properties | 5 + .../desktopMain/resources/i18n/es.properties | 5 + .../desktopMain/resources/i18n/fa.properties | 5 + .../desktopMain/resources/i18n/fr.properties | 5 + .../desktopMain/resources/i18n/ja.properties | 5 + .../desktopMain/resources/i18n/ko.properties | 5 + .../desktopMain/resources/i18n/pt.properties | 5 + .../desktopMain/resources/i18n/zh.properties | 5 + .../resources/i18n/zh_hant.properties | 5 + .../crosspaste/config/DesktopAppConfigTest.kt | 23 +++ .../com/crosspaste/config/TestAppConfig.kt | 2 + .../paste/PasteReleaseServicePushTest.kt | 2 + .../PasteReleaseServiceRemoteDiscardTest.kt | 141 ++++++++++++++++++ .../DiscardOversizedNonFilePluginTest.kt | 129 ++++++++++++++++ .../kotlin/com/crosspaste/cli/CliAdapters.kt | 2 + .../com/crosspaste/e2e/peer/E2eAppConfig.kt | 2 + .../kotlin/com/crosspaste/config/AppConfig.kt | 7 + 23 files changed, 516 insertions(+) create mode 100644 app/src/commonMain/kotlin/com/crosspaste/paste/plugin/process/DiscardOversizedNonFilePlugin.kt create mode 100644 app/src/desktopTest/kotlin/com/crosspaste/paste/PasteReleaseServiceRemoteDiscardTest.kt create mode 100644 app/src/desktopTest/kotlin/com/crosspaste/paste/plugin/process/DiscardOversizedNonFilePluginTest.kt diff --git a/app/src/commonMain/kotlin/com/crosspaste/paste/PasteReleaseService.kt b/app/src/commonMain/kotlin/com/crosspaste/paste/PasteReleaseService.kt index e4ec304ce..4f2bd56fe 100644 --- a/app/src/commonMain/kotlin/com/crosspaste/paste/PasteReleaseService.kt +++ b/app/src/commonMain/kotlin/com/crosspaste/paste/PasteReleaseService.kt @@ -3,11 +3,15 @@ package com.crosspaste.paste import com.crosspaste.Database import com.crosspaste.config.CommonConfigManager import com.crosspaste.db.paste.PasteDao +import com.crosspaste.db.sync.SyncRuntimeInfoDao +import com.crosspaste.notification.MessageType +import com.crosspaste.notification.NotificationManager import com.crosspaste.paste.item.PasteFiles import com.crosspaste.paste.item.PasteItem import com.crosspaste.paste.item.PasteItemProperties import com.crosspaste.paste.item.PasteItemReader import com.crosspaste.paste.item.bindItem +import com.crosspaste.paste.plugin.process.DiscardOversizedNonFilePlugin import com.crosspaste.paste.plugin.process.PasteProcessPlugin import com.crosspaste.path.UserDataPathProvider import com.crosspaste.presist.FilesIndex @@ -36,10 +40,12 @@ class PasteReleaseService( private val commonConfigManager: CommonConfigManager, private val currentPaste: CurrentPaste, private val database: Database, + private val notificationManager: NotificationManager, private val pasteDao: PasteDao, private val pasteItemReader: PasteItemReader, private val pasteProcessPlugins: List, private val searchContentService: SearchContentService, + private val syncRuntimeInfoDao: SyncRuntimeInfoDao, private val taskSubmitter: TaskSubmitter, private val userDataPathProvider: UserDataPathProvider, ) { @@ -194,11 +200,51 @@ class PasteReleaseService( return newPasteData } + /** + * Remote pastes bypass the plugin pipeline (they are persisted as-is), so the + * non-file size limit is enforced here. The whole paste is skipped instead of + * degrading flavors: the remote PasteData's hash/pasteType/searchContent are + * already assembled, and dropping individual items would break the + * hash-content consistency that markDeleteSameHash dedup relies on. The + * sender keeps the full data, so nothing is lost. + */ + private suspend fun discardOversizedRemoteNonFilePaste(pasteData: PasteData): Boolean { + val config = commonConfigManager.getCurrentConfig() + if (!config.enabledNonFilePasteSizeLimit) { + return false + } + val maxSize = fileUtils.bytesSize(config.maxNonFilePasteSize) + val oversized = + pasteData.getPasteAppearItems().any { + DiscardOversizedNonFilePlugin.isNonFilePasteType(it.getPasteType()) && it.size > maxSize + } + if (!oversized) { + return false + } + logger.info { + "Discard oversized remote non-file paste from ${pasteData.appInstanceId}: " + + "size=${pasteData.size}, limit=$maxSize" + } + val deviceName = + syncRuntimeInfoDao + .getSyncRuntimeInfo(pasteData.appInstanceId) + ?.getDeviceDisplayName() + ?: pasteData.appInstanceId + notificationManager.sendNotification( + title = { it.getText("remote_non_file_paste_discarded_too_large", deviceName) }, + messageType = MessageType.Warning, + ) + return true + } + suspend fun releaseRemotePasteData( pasteData: PasteData, tryWritePasteboard: (PasteData) -> Unit, ): Result { return runCatching { + if (discardOversizedRemoteNonFilePaste(pasteData)) { + return@runCatching + } val remotePasteDataId = pasteData.id val isFileType = pasteData.isFileType() val existIconFile: Boolean? = diff --git a/app/src/commonMain/kotlin/com/crosspaste/paste/plugin/process/DiscardOversizedNonFilePlugin.kt b/app/src/commonMain/kotlin/com/crosspaste/paste/plugin/process/DiscardOversizedNonFilePlugin.kt new file mode 100644 index 000000000..7d1ff60f1 --- /dev/null +++ b/app/src/commonMain/kotlin/com/crosspaste/paste/plugin/process/DiscardOversizedNonFilePlugin.kt @@ -0,0 +1,73 @@ +package com.crosspaste.paste.plugin.process + +import com.crosspaste.config.CommonConfigManager +import com.crosspaste.notification.MessageType +import com.crosspaste.notification.NotificationManager +import com.crosspaste.paste.PasteType +import com.crosspaste.paste.item.PasteCoordinate +import com.crosspaste.paste.item.PasteItem +import com.crosspaste.utils.getFileUtils +import io.github.oshai.kotlinlogging.KotlinLogging + +/** + * Discards non-file paste items larger than [com.crosspaste.config.AppConfig.maxNonFilePasteSize]. + * + * Only non-file carrier types (text/html/rtf/url/color) are subject to the limit; + * file and image pastes store their payload on disk and are governed by + * maxSyncFileSize instead. Oversized items are silently dropped (flavor + * degradation); a notification is pushed only when every item is discarded — + * the resulting empty list makes PasteReleaseService mark-delete the paste row. + */ +class DiscardOversizedNonFilePlugin( + private val configManager: CommonConfigManager, + private val notificationManager: NotificationManager, +) : PasteProcessPlugin { + + companion object { + private val NON_FILE_PASTE_TYPES = + setOf( + PasteType.TEXT_TYPE, + PasteType.HTML_TYPE, + PasteType.RTF_TYPE, + PasteType.URL_TYPE, + PasteType.COLOR_TYPE, + ) + + private val fileUtils = getFileUtils() + + fun isNonFilePasteType(pasteType: PasteType): Boolean = pasteType in NON_FILE_PASTE_TYPES + } + + private val logger = KotlinLogging.logger {} + + override fun process( + pasteCoordinate: PasteCoordinate, + pasteItems: List, + source: String?, + ): List { + val config = configManager.getCurrentConfig() + if (!config.enabledNonFilePasteSizeLimit) { + return pasteItems + } + val maxSize = fileUtils.bytesSize(config.maxNonFilePasteSize) + val retainedItems = + pasteItems.filter { pasteItem -> + val oversized = + isNonFilePasteType(pasteItem.getPasteType()) && pasteItem.size > maxSize + if (oversized) { + logger.info { + "Discard oversized ${pasteItem.getPasteType().name} paste item: " + + "size=${pasteItem.size}, limit=$maxSize, id=${pasteCoordinate.id}" + } + } + !oversized + } + if (retainedItems.isEmpty() && pasteItems.isNotEmpty()) { + notificationManager.sendNotification( + title = { it.getText("non_file_paste_discarded_too_large") }, + messageType = MessageType.Warning, + ) + } + return retainedItems + } +} diff --git a/app/src/commonMain/kotlin/com/crosspaste/ui/settings/NetworkSettingsContentView.kt b/app/src/commonMain/kotlin/com/crosspaste/ui/settings/NetworkSettingsContentView.kt index 6ba551c09..b7d19aead 100644 --- a/app/src/commonMain/kotlin/com/crosspaste/ui/settings/NetworkSettingsContentView.kt +++ b/app/src/commonMain/kotlin/com/crosspaste/ui/settings/NetworkSettingsContentView.kt @@ -27,10 +27,12 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp import com.composables.icons.materialsymbols.MaterialSymbols +import com.composables.icons.materialsymbols.rounded.Article import com.composables.icons.materialsymbols.rounded.Docs import com.composables.icons.materialsymbols.rounded.Power import com.composables.icons.materialsymbols.rounded.Shield import com.composables.icons.materialsymbols.rounded.Sync_alt +import com.composables.icons.materialsymbols.rounded.Title import com.composables.icons.materialsymbols.rounded.Visibility import com.crosspaste.config.CommonConfigManager import com.crosspaste.dto.sync.SyncInfo @@ -202,6 +204,27 @@ fun NetworkSettingsContentView(syncExtContent: @Composable () -> Unit = {}) { } }, ) + HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge)) + SettingListSwitchItem( + title = "non_file_paste_size_limit", + subtitle = "non_file_paste_size_limit_desc", + icon = IconData(MaterialSymbols.Rounded.Title, themeExt.amberIconColor), + checked = config.enabledNonFilePasteSizeLimit, + ) { newEnabledNonFilePasteSizeLimit -> + configManager.updateConfig("enabledNonFilePasteSizeLimit", newEnabledNonFilePasteSizeLimit) + } + HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge)) + SettingListItem( + title = "max_non_file_paste_size", + icon = IconData(MaterialSymbols.Rounded.Article, themeExt.cyanIconColor), + trailingContent = { + Counter(defaultValue = config.maxNonFilePasteSize, unit = "MB", rule = { + it > 0 + }) { currentMaxNonFilePasteSize -> + configManager.updateConfig("maxNonFilePasteSize", currentMaxNonFilePasteSize) + } + }, + ) syncExtContent() } } diff --git a/app/src/desktopMain/kotlin/com/crosspaste/DesktopPasteComponentModule.kt b/app/src/desktopMain/kotlin/com/crosspaste/DesktopPasteComponentModule.kt index 3228806b8..10339e586 100644 --- a/app/src/desktopMain/kotlin/com/crosspaste/DesktopPasteComponentModule.kt +++ b/app/src/desktopMain/kotlin/com/crosspaste/DesktopPasteComponentModule.kt @@ -32,6 +32,7 @@ import com.crosspaste.paste.getDesktopPasteboardService import com.crosspaste.paste.item.DefaultPasteItemReader import com.crosspaste.paste.item.PasteItemReader import com.crosspaste.paste.item.UpdatePasteItemHelper +import com.crosspaste.paste.plugin.process.DiscardOversizedNonFilePlugin import com.crosspaste.paste.plugin.process.DistinctPlugin import com.crosspaste.paste.plugin.process.FileToUrlPlugin import com.crosspaste.paste.plugin.process.FilesToImagesPlugin @@ -165,11 +166,13 @@ fun desktopPasteComponentModule(headless: Boolean): Module = commonConfigManager = get(), currentPaste = get(), database = get(), + notificationManager = get(), pasteDao = get(), pasteItemReader = get(), pasteProcessPlugins = listOf( RemoveInvalidPlugin, + DiscardOversizedNonFilePlugin(get(), get()), DistinctPlugin(get()), GenerateTextPlugin(get()), GenerateUrlPlugin, @@ -181,6 +184,7 @@ fun desktopPasteComponentModule(headless: Boolean): Module = SortPlugin, ), searchContentService = get(), + syncRuntimeInfoDao = get(), taskSubmitter = get(), userDataPathProvider = get(), ) diff --git a/app/src/desktopMain/kotlin/com/crosspaste/config/DesktopAppConfig.kt b/app/src/desktopMain/kotlin/com/crosspaste/config/DesktopAppConfig.kt index 3e01ecc00..28c26b89b 100644 --- a/app/src/desktopMain/kotlin/com/crosspaste/config/DesktopAppConfig.kt +++ b/app/src/desktopMain/kotlin/com/crosspaste/config/DesktopAppConfig.kt @@ -39,6 +39,9 @@ data class DesktopAppConfig( override val maxBackupFileSize: Long = 32, override val enabledSyncFileSizeLimit: Boolean = true, override val maxSyncFileSize: Long = 512, + override val enabledNonFilePasteSizeLimit: Boolean = true, + // MB + override val maxNonFilePasteSize: Long = 8, override val useDefaultStoragePath: Boolean = true, override val storagePath: String = "", override val enableSoundEffect: Boolean = true, @@ -141,6 +144,15 @@ data class DesktopAppConfig( enabledSyncFileSizeLimit }, maxSyncFileSize = if (key == "maxSyncFileSize") toLong(value) else maxSyncFileSize, + enabledNonFilePasteSizeLimit = + if (key == "enabledNonFilePasteSizeLimit") { + toBoolean( + value, + ) + } else { + enabledNonFilePasteSizeLimit + }, + maxNonFilePasteSize = if (key == "maxNonFilePasteSize") toLong(value) else maxNonFilePasteSize, useDefaultStoragePath = if (key == "useDefaultStoragePath") toBoolean(value) else useDefaultStoragePath, storagePath = if (key == "storagePath") toString(value) else storagePath, enableSoundEffect = if (key == "enableSoundEffect") toBoolean(value) else enableSoundEffect, diff --git a/app/src/desktopMain/resources/i18n/de.properties b/app/src/desktopMain/resources/i18n/de.properties index 4b1d52a5e..2e111da10 100644 --- a/app/src/desktopMain/resources/i18n/de.properties +++ b/app/src/desktopMain/resources/i18n/de.properties @@ -138,6 +138,7 @@ local_version_low_desc=Aktuelle Softwareversion zu niedrig, Upgrade empfohlen. manual_clear=Manuelle Bereinigung manual_proxy_configuration=Manuelle Proxy-Konfiguration max_back_up_file_size=Maximale Backup-Dateigröße +max_non_file_paste_size=Größenlimit für Nicht-Datei-Einträge max_sync_file_size=Maximale Sync-Dateigröße maximum_storage=Maximaler Speicher mcp_claude_config_hint=Führen Sie diesen Befehl im Terminal aus, um CrossPaste zu Claude Code hinzuzufügen: @@ -169,6 +170,9 @@ no_data_found=Keine Daten gefunden no_data_import=Kein Datenimport no_new_version_available=Keine neue Version verfügbar new_version_available=Eine neue Version ist verfügbar +non_file_paste_discarded_too_large=Text-, HTML-, Rich-Text- oder Link-Inhalt ist zu groß und wurde nicht gespeichert +non_file_paste_size_limit=Größe von Nicht-Datei-Einträgen begrenzen +non_file_paste_size_limit_desc=Text-, HTML-, Rich-Text- und Link-Einträge über dem Limit werden nicht gespeichert; Dateien und Bilder sind nicht betroffen (über das Sync-Dateigrößenlimit geregelt) update_available=Eine neue Version ist verfügbar update_available_dialog_desc=Eine neuere Version von CrossPaste ist bereit. Jetzt aktualisieren, um herunterzuladen und neu zu starten, oder später. update_now=Jetzt aktualisieren @@ -265,6 +269,7 @@ relative_seconds_ago=vor %s Sekunden relative_weeks_ago=vor %s Wochen relative_years_ago=vor %s Jahren remote=Remote +remote_non_file_paste_discarded_too_large=Text-, HTML-, Rich-Text- oder Link-Inhalt von %s ist zu groß und wurde nicht synchronisiert remote_version_low_desc=Softwareversion des verbundenen Geräts zu niedrig, Upgrade empfohlen. remove=Entfernen remove_blacklist=Von der Blacklist entfernen diff --git a/app/src/desktopMain/resources/i18n/en.properties b/app/src/desktopMain/resources/i18n/en.properties index bb4c3b32b..125c01c91 100644 --- a/app/src/desktopMain/resources/i18n/en.properties +++ b/app/src/desktopMain/resources/i18n/en.properties @@ -138,6 +138,7 @@ local_version_low_desc=Current software version is too low, upgrading is recomme manual_clear=Manual clear manual_proxy_configuration=Manual proxy configuration max_back_up_file_size=Max back up file size +max_non_file_paste_size=Non-file clipboard item size limit max_sync_file_size=Max sync file size maximum_storage=Maximum Storage mcp_claude_config_hint=Run this command in your terminal to add CrossPaste to Claude Code: @@ -169,6 +170,9 @@ no_data_found=No data found no_data_import=No data import no_new_version_available=No new version available new_version_available=A new version is available +non_file_paste_discarded_too_large=Text, HTML, rich text, or link content is too large and was not recorded +non_file_paste_size_limit=Limit non-file clipboard item size +non_file_paste_size_limit_desc=Text, HTML, rich text, and link items over the limit will not be recorded; files and images are not affected (managed by the sync file size limit) update_available=A new version is available update_available_dialog_desc=A newer version of CrossPaste is ready. Update now to download and restart, or do it later. update_now=Update now @@ -265,6 +269,7 @@ relative_seconds_ago=%s seconds ago relative_weeks_ago=%s weeks ago relative_years_ago=%s years ago remote=Remote +remote_non_file_paste_discarded_too_large=Text, HTML, rich text, or link content from %s is too large and was not synced remote_version_low_desc=Connected device software version is too low, upgrading is recommended. remove=Remove remove_blacklist=Remove from blacklist diff --git a/app/src/desktopMain/resources/i18n/es.properties b/app/src/desktopMain/resources/i18n/es.properties index 2ac347335..0a34843ab 100644 --- a/app/src/desktopMain/resources/i18n/es.properties +++ b/app/src/desktopMain/resources/i18n/es.properties @@ -138,6 +138,7 @@ local_version_low_desc=La versión actual de software es demasiado baja, se reco manual_clear=Limpieza manual manual_proxy_configuration=Configuración manual de proxy max_back_up_file_size=Tamaño máximo de archivo backup +max_non_file_paste_size=Límite de tamaño de elementos que no son archivos max_sync_file_size=Tamaño máximo de archivo de sincronización maximum_storage=Almacenamiento máximo mcp_claude_config_hint=Ejecute este comando en su terminal para añadir CrossPaste a Claude Code: @@ -169,6 +170,9 @@ no_data_found=No se encontraron datos no_data_import=No hay datos para importar no_new_version_available=No hay una nueva versión disponible new_version_available=Hay una nueva versión disponible +non_file_paste_discarded_too_large=El contenido de texto, HTML, texto enriquecido o enlace es demasiado grande y no se registró +non_file_paste_size_limit=Limitar tamaño de elementos que no son archivos +non_file_paste_size_limit_desc=Los elementos de texto, HTML, texto enriquecido y enlaces que superen el límite no se registrarán; los archivos e imágenes no se ven afectados (gestionados por el límite de tamaño de archivo de sincronización) update_available=Hay una nueva versión disponible update_available_dialog_desc=Hay una versión más reciente de CrossPaste lista. Actualiza ahora para descargar y reiniciar, o hazlo más tarde. update_now=Actualizar ahora @@ -265,6 +269,7 @@ relative_seconds_ago=hace %s segundos relative_weeks_ago=hace %s semanas relative_years_ago=hace %s años remote=Remoto +remote_non_file_paste_discarded_too_large=El contenido de texto, HTML, texto enriquecido o enlace de %s es demasiado grande y no se sincronizó remote_version_low_desc=La versión de software del dispositivo conectado es demasiado baja, se recomienda actualizar. remove=Eliminar remove_blacklist=Quitar de la lista negra diff --git a/app/src/desktopMain/resources/i18n/fa.properties b/app/src/desktopMain/resources/i18n/fa.properties index 508d67b1f..f85d4195a 100644 --- a/app/src/desktopMain/resources/i18n/fa.properties +++ b/app/src/desktopMain/resources/i18n/fa.properties @@ -138,6 +138,7 @@ local_version_low_desc=نسخه نرم‌افزار فعلی بیش از حد ق manual_clear=پاک‌سازی دستی manual_proxy_configuration=پیکربندی دستی پروکسی max_back_up_file_size=حداکثر اندازه فایل پشتیبان +max_non_file_paste_size=حداکثر اندازه موارد غیرفایلی کلیپ‌بورد max_sync_file_size=حداکثر اندازه فایل برای همگام‌سازی maximum_storage=حداکثر فضای ذخیره‌سازی mcp_claude_config_hint=این دستور را در ترمینال اجرا کنید تا CrossPaste به Claude Code اضافه شود: @@ -169,6 +170,9 @@ no_data_found=داده‌ای یافت نشد no_data_import=داده‌ای برای درون‌ریزی نیست no_new_version_available=نسخه جدیدی در دسترس نیست new_version_available=نسخه جدیدی در دسترس است +non_file_paste_discarded_too_large=محتوای متن / HTML / متن غنی / پیوند بسیار بزرگ است و ثبت نشد +non_file_paste_size_limit=محدود کردن اندازه موارد غیرفایلی کلیپ‌بورد +non_file_paste_size_limit_desc=موارد متن / HTML / متن غنی / پیوند بزرگ‌تر از حد مجاز ثبت نمی‌شوند؛ فایل‌ها و تصاویر مشمول این محدودیت نیستند (توسط محدودیت اندازه فایل همگام‌سازی مدیریت می‌شوند) update_available=نسخه جدیدی در دسترس است update_available_dialog_desc=نسخه جدیدتری از CrossPaste آماده است. اکنون به‌روزرسانی کنید تا دانلود و راه‌اندازی مجدد شود، یا بعداً انجام دهید. update_now=اکنون به‌روزرسانی کن @@ -265,6 +269,7 @@ relative_seconds_ago=%s ثانیه پیش relative_weeks_ago=%s هفته پیش relative_years_ago=%s سال پیش remote=ریموت +remote_non_file_paste_discarded_too_large=محتوای متن / HTML / متن غنی / پیوند از %s بسیار بزرگ است و همگام‌سازی نشد remote_version_low_desc=نسخه نرم‌افزار دستگاه متصل بیش از حد قدیمی است، ارتقا توصیه می‌شود. remove=حذف remove_blacklist=حذف از لیست سیاه diff --git a/app/src/desktopMain/resources/i18n/fr.properties b/app/src/desktopMain/resources/i18n/fr.properties index 0ddae2d2a..5528cfcfe 100644 --- a/app/src/desktopMain/resources/i18n/fr.properties +++ b/app/src/desktopMain/resources/i18n/fr.properties @@ -138,6 +138,7 @@ local_version_low_desc=Version actuelle du logiciel trop ancienne, mise à jour manual_clear=Nettoyage manuel manual_proxy_configuration=Configuration manuelle du proxy max_back_up_file_size=Taille maximale du fichier de sauvegarde +max_non_file_paste_size=Limite de taille des éléments non-fichiers max_sync_file_size=Taille maximale du fichier de synchronisation maximum_storage=Stockage maximum mcp_claude_config_hint=Exécutez cette commande dans votre terminal pour ajouter CrossPaste à Claude Code : @@ -169,6 +170,9 @@ no_data_found=Aucune donnée trouvée no_data_import=Aucune importation de données no_new_version_available=Aucune nouvelle version disponible new_version_available=Une nouvelle version est disponible +non_file_paste_discarded_too_large=Le contenu texte, HTML, texte enrichi ou lien est trop volumineux et n'a pas été enregistré +non_file_paste_size_limit=Limiter la taille des éléments non-fichiers +non_file_paste_size_limit_desc=Les éléments texte, HTML, texte enrichi et liens dépassant la limite ne seront pas enregistrés ; les fichiers et images ne sont pas concernés (gérés par la limite de taille de fichier de synchronisation) update_available=Une nouvelle version est disponible update_available_dialog_desc=Une version plus récente de CrossPaste est prête. Mettez à jour maintenant pour télécharger et redémarrer, ou plus tard. update_now=Mettre à jour maintenant @@ -265,6 +269,7 @@ relative_seconds_ago=il y a %s secondes relative_weeks_ago=il y a %s semaines relative_years_ago=il y a %s ans remote=Distant +remote_non_file_paste_discarded_too_large=Le contenu texte, HTML, texte enrichi ou lien provenant de %s est trop volumineux et n'a pas été synchronisé remote_version_low_desc=Version du logiciel de l'appareil connecté trop ancienne, mise à jour recommandée. remove=Supprimer remove_blacklist=Retirer de la liste noire diff --git a/app/src/desktopMain/resources/i18n/ja.properties b/app/src/desktopMain/resources/i18n/ja.properties index c57417b72..23474f72c 100644 --- a/app/src/desktopMain/resources/i18n/ja.properties +++ b/app/src/desktopMain/resources/i18n/ja.properties @@ -138,6 +138,7 @@ local_version_low_desc=現在のソフトウェアバージョンが古すぎま manual_clear=手動クリア manual_proxy_configuration=手動プロキシ設定 max_back_up_file_size=最大バックアップファイルサイズ +max_non_file_paste_size=非ファイル系クリップボードのサイズ上限 max_sync_file_size=最大同期ファイルサイズ maximum_storage=最大ストレージ mcp_claude_config_hint=ターミナルでこのコマンドを実行してCrossPasteをClaude Codeに追加してください: @@ -169,6 +170,9 @@ no_data_found=データが見つかりません no_data_import=データがインポートされていません no_new_version_available=新しいバージョンはありません new_version_available=新しいバージョンが利用可能です +non_file_paste_discarded_too_large=テキスト / HTML / リッチテキスト / リンクの内容が大きすぎるため記録されませんでした +non_file_paste_size_limit=非ファイル系クリップボードのサイズを制限 +non_file_paste_size_limit_desc=上限を超えるテキスト / HTML / リッチテキスト / リンクは記録されません。ファイルと画像はこの制限の対象外です(同期ファイルサイズ制限で管理) update_available=新しいバージョンが利用可能です update_available_dialog_desc=CrossPaste の新しいバージョンが利用可能です。今すぐ更新するとダウンロードして再起動します。後で行うこともできます。 update_now=今すぐ更新 @@ -265,6 +269,7 @@ relative_seconds_ago=%s 秒前 relative_weeks_ago=%s 週間前 relative_years_ago=%s 年前 remote=リモート +remote_non_file_paste_discarded_too_large=%s からのテキスト / HTML / リッチテキスト / リンクの内容が大きすぎるため同期されませんでした remote_version_low_desc=接続されたデバイスのソフトウェアバージョンが古すぎます。アップグレードをお勧めします。 remove=削除 remove_blacklist=ブラックリストから削除 diff --git a/app/src/desktopMain/resources/i18n/ko.properties b/app/src/desktopMain/resources/i18n/ko.properties index 58673d59e..184ff273a 100644 --- a/app/src/desktopMain/resources/i18n/ko.properties +++ b/app/src/desktopMain/resources/i18n/ko.properties @@ -138,6 +138,7 @@ local_version_low_desc=현재 소프트웨어 버전이 너무 낮습니다. 업 manual_clear=수동 지우기 manual_proxy_configuration=수동 프록시 구성 max_back_up_file_size=최대백업파일크기 +max_non_file_paste_size=비파일 클립보드 크기 상한 max_sync_file_size=최대동기화파일크기 maximum_storage=최대저장용량 mcp_claude_config_hint=터미널에서 이 명령을 실행하여 CrossPaste를 Claude Code에 추가하세요: @@ -169,6 +170,9 @@ no_data_found=데이터를찾을수없음 no_data_import=가져온데이터없음 no_new_version_available=새버전이없습니다 new_version_available=새 버전이 있습니다 +non_file_paste_discarded_too_large=텍스트 / HTML / 서식 있는 텍스트 / 링크 내용이 너무 커서 기록되지 않았습니다 +non_file_paste_size_limit=비파일 클립보드 크기 제한 +non_file_paste_size_limit_desc=상한을 초과하는 텍스트 / HTML / 서식 있는 텍스트 / 링크는 기록되지 않습니다. 파일과 이미지는 이 제한의 대상이 아닙니다(동기화 파일 크기 제한으로 관리) update_available=새 버전이 있습니다 update_available_dialog_desc=CrossPaste의 새 버전이 준비되었습니다. 지금 업데이트하면 다운로드 후 재시작합니다. 나중에 할 수도 있습니다. update_now=지금 업데이트 @@ -265,6 +269,7 @@ relative_seconds_ago=%s초 전 relative_weeks_ago=%s주 전 relative_years_ago=%s년 전 remote=원격 +remote_non_file_paste_discarded_too_large=%s에서 온 텍스트 / HTML / 서식 있는 텍스트 / 링크 내용이 너무 커서 동기화되지 않았습니다 remote_version_low_desc=연결된 장치의 소프트웨어 버전이 너무 낮습니다. 업그레이드를 권장합니다. remove=제거 remove_blacklist=블랙리스트에서 제거 diff --git a/app/src/desktopMain/resources/i18n/pt.properties b/app/src/desktopMain/resources/i18n/pt.properties index b70e844a3..457e49a1c 100644 --- a/app/src/desktopMain/resources/i18n/pt.properties +++ b/app/src/desktopMain/resources/i18n/pt.properties @@ -138,6 +138,7 @@ local_version_low_desc=A versão atual do software é muito antiga, recomenda-se manual_clear=Limpeza manual manual_proxy_configuration=Configuração manual de proxy max_back_up_file_size=Tamanho máximo do arquivo de backup +max_non_file_paste_size=Limite de tamanho de itens não-arquivo max_sync_file_size=Tam. máx. arquivo sinc. maximum_storage=Armazenamento máximo mcp_claude_config_hint=Execute este comando no seu terminal para adicionar o CrossPaste ao Claude Code: @@ -169,6 +170,9 @@ no_data_found=Nenhum dado encontrado no_data_import=Nenhum dado para importar no_new_version_available=Nenhuma nova versão disponível new_version_available=Uma nova versão está disponível +non_file_paste_discarded_too_large=Conteúdo de texto, HTML, texto rico ou link é grande demais e não foi registrado +non_file_paste_size_limit=Limitar tamanho de itens não-arquivo +non_file_paste_size_limit_desc=Itens de texto, HTML, texto rico e links acima do limite não serão registrados; arquivos e imagens não são afetados (controlados pelo limite de arquivo sinc.) update_available=Uma nova versão está disponível update_available_dialog_desc=Uma versão mais recente do CrossPaste está pronta. Atualize agora para baixar e reiniciar, ou faça isso mais tarde. update_now=Atualizar agora @@ -265,6 +269,7 @@ relative_seconds_ago=há %s segundos relative_weeks_ago=há %s semanas relative_years_ago=há %s anos remote=Remoto +remote_non_file_paste_discarded_too_large=Conteúdo de texto, HTML, texto rico ou link de %s é grande demais e não foi sincronizado remote_version_low_desc=A versão do software do dispositivo conectado é muito antiga, recomenda-se atualizar. remove=Remover remove_blacklist=Remover da lista negra diff --git a/app/src/desktopMain/resources/i18n/zh.properties b/app/src/desktopMain/resources/i18n/zh.properties index 57d4da936..81461f47c 100644 --- a/app/src/desktopMain/resources/i18n/zh.properties +++ b/app/src/desktopMain/resources/i18n/zh.properties @@ -138,6 +138,7 @@ local_version_low_desc=当前软件版本过低,建议升级版本。 manual_clear=手动清理 manual_proxy_configuration=手动代理配置 max_back_up_file_size=最大备份文件大小 +max_non_file_paste_size=非文件类粘贴板大小上限 max_sync_file_size=最大同步文件大小 maximum_storage=最大存储容量 mcp_claude_config_hint=在终端中运行此命令以将 CrossPaste 添加到 Claude Code: @@ -169,6 +170,9 @@ no_data_found=未命中任何数据 no_data_import=未导入任何数据 no_new_version_available=没有新版本可用 new_version_available=有新版本可用 +non_file_paste_discarded_too_large=文本 / HTML / 富文本 / 链接类内容过大,未记录 +non_file_paste_size_limit=限制非文件类粘贴板大小 +non_file_paste_size_limit_desc=超过上限的文本 / HTML / 富文本 / 链接类粘贴板将不被记录;文件与图片不受此限制(由同步文件大小限制管控) update_available=有新版本可用 update_available_dialog_desc=CrossPaste 有新版本可用。可立即更新(下载并重启),也可以稍后再更新。 update_now=立即更新 @@ -265,6 +269,7 @@ relative_seconds_ago=%s 秒前 relative_weeks_ago=%s 周前 relative_years_ago=%s 年前 remote=远程 +remote_non_file_paste_discarded_too_large=来自 %s 的文本 / HTML / 富文本 / 链接类内容过大,未同步 remote_version_low_desc=连接设备软件版本过低,建议升级版本。 remove=移除 remove_blacklist=从黑名单移出 diff --git a/app/src/desktopMain/resources/i18n/zh_hant.properties b/app/src/desktopMain/resources/i18n/zh_hant.properties index fa54f0723..2934c85f0 100644 --- a/app/src/desktopMain/resources/i18n/zh_hant.properties +++ b/app/src/desktopMain/resources/i18n/zh_hant.properties @@ -138,6 +138,7 @@ local_version_low_desc=當前軟體版本過低,建議升級版本。 manual_clear=手動清理 manual_proxy_configuration=手動代理配置 max_back_up_file_size=最大備份檔案大小 +max_non_file_paste_size=非檔案類貼上板大小上限 max_sync_file_size=最大同步檔案大小 maximum_storage=最大儲存容量 mcp_claude_config_hint=在終端機中執行此命令以將 CrossPaste 新增到 Claude Code: @@ -169,6 +170,9 @@ no_data_found=未命中任何資料 no_data_import=未匯入任何資料 no_new_version_available=沒有新版本可用 new_version_available=有新版本可用 +non_file_paste_discarded_too_large=文字 / HTML / 富文字 / 連結類內容過大,未記錄 +non_file_paste_size_limit=限制非檔案類貼上板大小 +non_file_paste_size_limit_desc=超過上限的文字 / HTML / 富文字 / 連結類貼上板將不被記錄;檔案與圖片不受此限制(由同步檔案大小限制管控) update_available=有新版本可用 update_available_dialog_desc=CrossPaste 有新版本可用。可立即更新(下載並重新啟動),也可以稍後再更新。 update_now=立即更新 @@ -265,6 +269,7 @@ relative_seconds_ago=%s 秒前 relative_weeks_ago=%s 週前 relative_years_ago=%s 年前 remote=遠端 +remote_non_file_paste_discarded_too_large=來自 %s 的文字 / HTML / 富文字 / 連結類內容過大,未同步 remote_version_low_desc=連接設備軟體版本過低,建議升級版本。 remove=移除 remove_blacklist=從黑名單移除 diff --git a/app/src/desktopTest/kotlin/com/crosspaste/config/DesktopAppConfigTest.kt b/app/src/desktopTest/kotlin/com/crosspaste/config/DesktopAppConfigTest.kt index 9c509b36a..74ff1626e 100644 --- a/app/src/desktopTest/kotlin/com/crosspaste/config/DesktopAppConfigTest.kt +++ b/app/src/desktopTest/kotlin/com/crosspaste/config/DesktopAppConfigTest.kt @@ -39,6 +39,8 @@ class DesktopAppConfigTest { assertEquals(32L, config.maxBackupFileSize) assertTrue(config.enabledSyncFileSizeLimit) assertEquals(512L, config.maxSyncFileSize) + assertTrue(config.enabledNonFilePasteSizeLimit) + assertEquals(8L, config.maxNonFilePasteSize) assertTrue(config.useDefaultStoragePath) assertEquals("", config.storagePath) assertTrue(config.enableSoundEffect) @@ -104,6 +106,27 @@ class DesktopAppConfigTest { assertEquals(332, config.searchWindowHeight) } + @Test + fun `copy updates non-file paste size limit configs`() { + val config: AppConfig = createDefaultConfig() + val disabled = config.copy("enabledNonFilePasteSizeLimit", false) + assertFalse(disabled.enabledNonFilePasteSizeLimit) + val resized = config.copy("maxNonFilePasteSize", 16L) + assertEquals(16L, resized.maxNonFilePasteSize) + } + + @Test + fun `legacy config without non-file paste size limit uses defaults`() { + val config = + getJsonUtils().JSON.decodeFromString( + DesktopAppConfig.serializer(), + """{"language":"zh"}""", + ) + + assertTrue(config.enabledNonFilePasteSizeLimit) + assertEquals(8L, config.maxNonFilePasteSize) + } + @Test fun `copy with long key updates maxStorage`() { val config: AppConfig = createDefaultConfig() diff --git a/app/src/desktopTest/kotlin/com/crosspaste/config/TestAppConfig.kt b/app/src/desktopTest/kotlin/com/crosspaste/config/TestAppConfig.kt index 95624237d..1145af199 100644 --- a/app/src/desktopTest/kotlin/com/crosspaste/config/TestAppConfig.kt +++ b/app/src/desktopTest/kotlin/com/crosspaste/config/TestAppConfig.kt @@ -23,6 +23,8 @@ data class TestAppConfig( override val maxBackupFileSize: Long = 32, override val enabledSyncFileSizeLimit: Boolean = true, override val maxSyncFileSize: Long = 512, + override val enabledNonFilePasteSizeLimit: Boolean = true, + override val maxNonFilePasteSize: Long = 8, override val useDefaultStoragePath: Boolean = true, override val storagePath: String = "", override val enableSoundEffect: Boolean = false, diff --git a/app/src/desktopTest/kotlin/com/crosspaste/paste/PasteReleaseServicePushTest.kt b/app/src/desktopTest/kotlin/com/crosspaste/paste/PasteReleaseServicePushTest.kt index 927452766..2e9e04dd0 100644 --- a/app/src/desktopTest/kotlin/com/crosspaste/paste/PasteReleaseServicePushTest.kt +++ b/app/src/desktopTest/kotlin/com/crosspaste/paste/PasteReleaseServicePushTest.kt @@ -39,10 +39,12 @@ class PasteReleaseServicePushTest { commonConfigManager = commonConfigManager, currentPaste = mockk(relaxed = true), database = mockk(relaxed = true), + notificationManager = mockk(relaxed = true), pasteDao = pasteDao, pasteItemReader = mockk(relaxed = true), pasteProcessPlugins = emptyList(), searchContentService = mockk(relaxed = true), + syncRuntimeInfoDao = mockk(relaxed = true), taskSubmitter = mockk(relaxed = true), userDataPathProvider = userDataPathProvider, ) diff --git a/app/src/desktopTest/kotlin/com/crosspaste/paste/PasteReleaseServiceRemoteDiscardTest.kt b/app/src/desktopTest/kotlin/com/crosspaste/paste/PasteReleaseServiceRemoteDiscardTest.kt new file mode 100644 index 000000000..80a846746 --- /dev/null +++ b/app/src/desktopTest/kotlin/com/crosspaste/paste/PasteReleaseServiceRemoteDiscardTest.kt @@ -0,0 +1,141 @@ +package com.crosspaste.paste + +import com.crosspaste.Database +import com.crosspaste.config.CommonConfigManager +import com.crosspaste.config.TestAppConfig +import com.crosspaste.db.paste.PasteDao +import com.crosspaste.db.sync.SyncRuntimeInfoDao +import com.crosspaste.notification.NotificationManager +import com.crosspaste.paste.item.CreatePasteItemHelper.createTextPasteItem +import com.crosspaste.paste.item.PasteItemReader +import com.crosspaste.paste.item.TextPasteItem +import com.crosspaste.path.UserDataPathProvider +import com.crosspaste.task.TaskSubmitter +import com.crosspaste.utils.getJsonUtils +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.runBlocking +import kotlin.test.Test +import kotlin.test.assertTrue + +class PasteReleaseServiceRemoteDiscardTest { + + @Suppress("unused") + private val jsonUtils = getJsonUtils() + + private val maxSizeBytes = 8L * 1024 * 1024 + + private fun configManager(enabled: Boolean): CommonConfigManager { + val configManager = mockk(relaxed = true) + every { configManager.getCurrentConfig() } returns + TestAppConfig(enabledNonFilePasteSizeLimit = enabled, maxNonFilePasteSize = 8) + return configManager + } + + private fun newService( + commonConfigManager: CommonConfigManager = configManager(enabled = true), + notificationManager: NotificationManager = mockk(relaxed = true), + syncRuntimeInfoDao: SyncRuntimeInfoDao = mockk(relaxed = true), + taskSubmitter: TaskSubmitter = mockk(relaxed = true), + ): PasteReleaseService = + PasteReleaseService( + commonConfigManager = commonConfigManager, + currentPaste = mockk(relaxed = true), + database = mockk(relaxed = true), + notificationManager = notificationManager, + pasteDao = mockk(relaxed = true), + pasteItemReader = mockk(relaxed = true), + pasteProcessPlugins = emptyList(), + searchContentService = mockk(relaxed = true), + syncRuntimeInfoDao = syncRuntimeInfoDao, + taskSubmitter = taskSubmitter, + userDataPathProvider = mockk(relaxed = true), + ) + + private fun remotePasteData(item: TextPasteItem): PasteData = + PasteData( + appInstanceId = "remote-device", + pasteAppearItem = item, + pasteCollection = PasteCollection(emptyList()), + pasteType = PasteType.TEXT_TYPE.type, + source = null, + size = item.size, + hash = item.hash, + remote = true, + ) + + private fun oversizedTextItem(): TextPasteItem = + TextPasteItem( + identifiers = listOf(), + hash = "oversized", + size = maxSizeBytes + 1, + text = "oversized", + ) + + @Test + fun `oversized remote non-file paste is skipped entirely with notification`() = + runBlocking { + val notificationManager = mockk(relaxed = true) + val syncRuntimeInfoDao = mockk(relaxed = true) + coEvery { syncRuntimeInfoDao.getSyncRuntimeInfo("remote-device") } returns null + val taskSubmitter = mockk(relaxed = true) + val service = + newService( + notificationManager = notificationManager, + syncRuntimeInfoDao = syncRuntimeInfoDao, + taskSubmitter = taskSubmitter, + ) + + var wrotePasteboard = false + val result = + service.releaseRemotePasteData(remotePasteData(oversizedTextItem())) { + wrotePasteboard = true + } + + assertTrue(result.isSuccess) + assertTrue(!wrotePasteboard) + coVerify(exactly = 0) { taskSubmitter.submit(any()) } + verify(exactly = 1) { notificationManager.sendNotification(any(), any(), any(), any()) } + } + + @Test + fun `remote paste within limit is released normally`() = + runBlocking { + val notificationManager = mockk(relaxed = true) + val taskSubmitter = mockk(relaxed = true) + val service = + newService( + notificationManager = notificationManager, + taskSubmitter = taskSubmitter, + ) + + val result = + service.releaseRemotePasteData(remotePasteData(createTextPasteItem(text = "small"))) {} + + assertTrue(result.isSuccess) + coVerify(exactly = 1) { taskSubmitter.submit(any()) } + verify(exactly = 0) { notificationManager.sendNotification(any(), any(), any(), any()) } + } + + @Test + fun `disabled limit releases oversized remote paste normally`() = + runBlocking { + val notificationManager = mockk(relaxed = true) + val taskSubmitter = mockk(relaxed = true) + val service = + newService( + commonConfigManager = configManager(enabled = false), + notificationManager = notificationManager, + taskSubmitter = taskSubmitter, + ) + + val result = service.releaseRemotePasteData(remotePasteData(oversizedTextItem())) {} + + assertTrue(result.isSuccess) + coVerify(exactly = 1) { taskSubmitter.submit(any()) } + verify(exactly = 0) { notificationManager.sendNotification(any(), any(), any(), any()) } + } +} diff --git a/app/src/desktopTest/kotlin/com/crosspaste/paste/plugin/process/DiscardOversizedNonFilePluginTest.kt b/app/src/desktopTest/kotlin/com/crosspaste/paste/plugin/process/DiscardOversizedNonFilePluginTest.kt new file mode 100644 index 000000000..844c83e06 --- /dev/null +++ b/app/src/desktopTest/kotlin/com/crosspaste/paste/plugin/process/DiscardOversizedNonFilePluginTest.kt @@ -0,0 +1,129 @@ +package com.crosspaste.paste.plugin.process + +import com.crosspaste.config.CommonConfigManager +import com.crosspaste.config.TestAppConfig +import com.crosspaste.notification.NotificationManager +import com.crosspaste.paste.item.CreatePasteItemHelper.createFilesPasteItem +import com.crosspaste.paste.item.CreatePasteItemHelper.createImagesPasteItem +import com.crosspaste.paste.item.CreatePasteItemHelper.createTextPasteItem +import com.crosspaste.paste.item.PasteCoordinate +import com.crosspaste.paste.item.TextPasteItem +import com.crosspaste.presist.SingleFileInfoTree +import com.crosspaste.utils.getJsonUtils +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class DiscardOversizedNonFilePluginTest { + + @Suppress("unused") + private val jsonUtils = getJsonUtils() + + private val coord = PasteCoordinate(id = 1L, appInstanceId = "test") + + private val maxSizeBytes = 8L * 1024 * 1024 + + private fun configManager(enabled: Boolean): CommonConfigManager { + val configManager = mockk(relaxed = true) + every { configManager.getCurrentConfig() } returns + TestAppConfig(enabledNonFilePasteSizeLimit = enabled, maxNonFilePasteSize = 8) + return configManager + } + + private fun oversizedTextItem(): TextPasteItem = + TextPasteItem( + identifiers = listOf(), + hash = "oversized", + size = maxSizeBytes + 1, + text = "oversized", + ) + + @Test + fun `all oversized items are discarded and notification is sent`() { + val notificationManager = mockk(relaxed = true) + val plugin = DiscardOversizedNonFilePlugin(configManager(enabled = true), notificationManager) + + val result = plugin.process(coord, listOf(oversizedTextItem()), null) + + assertTrue(result.isEmpty()) + verify(exactly = 1) { notificationManager.sendNotification(any(), any(), any(), any()) } + } + + @Test + fun `partial oversized items are degraded silently`() { + val notificationManager = mockk(relaxed = true) + val plugin = DiscardOversizedNonFilePlugin(configManager(enabled = true), notificationManager) + + val withinLimit = createTextPasteItem(text = "small") + val result = plugin.process(coord, listOf(withinLimit, oversizedTextItem()), null) + + assertEquals(listOf(withinLimit), result) + verify(exactly = 0) { notificationManager.sendNotification(any(), any(), any(), any()) } + } + + @Test + fun `disabled limit passes items through unchanged`() { + val notificationManager = mockk(relaxed = true) + val plugin = DiscardOversizedNonFilePlugin(configManager(enabled = false), notificationManager) + + val items = listOf(oversizedTextItem()) + val result = plugin.process(coord, items, null) + + assertEquals(items, result) + verify(exactly = 0) { notificationManager.sendNotification(any(), any(), any(), any()) } + } + + @Test + fun `file and image items are exempt regardless of size`() { + val notificationManager = mockk(relaxed = true) + val plugin = DiscardOversizedNonFilePlugin(configManager(enabled = true), notificationManager) + + val bigTree = SingleFileInfoTree(size = maxSizeBytes * 10, hash = "big") + val filesItem = + createFilesPasteItem( + relativePathList = listOf("big.bin"), + fileInfoTreeMap = mapOf("big.bin" to bigTree), + ) + val imagesItem = + createImagesPasteItem( + relativePathList = listOf("big.png"), + fileInfoTreeMap = mapOf("big.png" to bigTree), + ) + val result = plugin.process(coord, listOf(filesItem, imagesItem), null) + + assertEquals(listOf(filesItem, imagesItem), result) + verify(exactly = 0) { notificationManager.sendNotification(any(), any(), any(), any()) } + } + + @Test + fun `empty list returns empty without notification`() { + val notificationManager = mockk(relaxed = true) + val plugin = DiscardOversizedNonFilePlugin(configManager(enabled = true), notificationManager) + + val result = plugin.process(coord, emptyList(), null) + + assertTrue(result.isEmpty()) + verify(exactly = 0) { notificationManager.sendNotification(any(), any(), any(), any()) } + } + + @Test + fun `item exactly at limit is retained`() { + val notificationManager = mockk(relaxed = true) + val plugin = DiscardOversizedNonFilePlugin(configManager(enabled = true), notificationManager) + + val atLimit = + TextPasteItem( + identifiers = listOf(), + hash = "at-limit", + size = maxSizeBytes, + text = "at-limit", + ) + val result = plugin.process(coord, listOf(atLimit), null) + + assertEquals(listOf(atLimit), result) + verify(exactly = 0) { notificationManager.sendNotification(any(), any(), any(), any()) } + } +} diff --git a/cli/src/cliNativeMain/kotlin/com/crosspaste/cli/CliAdapters.kt b/cli/src/cliNativeMain/kotlin/com/crosspaste/cli/CliAdapters.kt index fbdea6843..cf54eeaa3 100644 --- a/cli/src/cliNativeMain/kotlin/com/crosspaste/cli/CliAdapters.kt +++ b/cli/src/cliNativeMain/kotlin/com/crosspaste/cli/CliAdapters.kt @@ -40,6 +40,8 @@ data class CliReadOnlyAppConfig( override val maxBackupFileSize: Long = 32, override val enabledSyncFileSizeLimit: Boolean = true, override val maxSyncFileSize: Long = 512, + override val enabledNonFilePasteSizeLimit: Boolean = true, + override val maxNonFilePasteSize: Long = 8, override val useDefaultStoragePath: Boolean = true, override val storagePath: String = "", override val enableSoundEffect: Boolean = true, diff --git a/e2e/src/desktopMain/kotlin/com/crosspaste/e2e/peer/E2eAppConfig.kt b/e2e/src/desktopMain/kotlin/com/crosspaste/e2e/peer/E2eAppConfig.kt index 280a6acd4..912fd69f3 100644 --- a/e2e/src/desktopMain/kotlin/com/crosspaste/e2e/peer/E2eAppConfig.kt +++ b/e2e/src/desktopMain/kotlin/com/crosspaste/e2e/peer/E2eAppConfig.kt @@ -31,6 +31,8 @@ data class E2eAppConfig( override val maxBackupFileSize: Long = 32, override val enabledSyncFileSizeLimit: Boolean = true, override val maxSyncFileSize: Long = 512, + override val enabledNonFilePasteSizeLimit: Boolean = true, + override val maxNonFilePasteSize: Long = 8, override val useDefaultStoragePath: Boolean = true, override val storagePath: String = "", override val enableSoundEffect: Boolean = false, diff --git a/shared/src/commonMain/kotlin/com/crosspaste/config/AppConfig.kt b/shared/src/commonMain/kotlin/com/crosspaste/config/AppConfig.kt index 9f5ca2105..9771d5e53 100644 --- a/shared/src/commonMain/kotlin/com/crosspaste/config/AppConfig.kt +++ b/shared/src/commonMain/kotlin/com/crosspaste/config/AppConfig.kt @@ -25,6 +25,13 @@ interface AppConfig { val maxBackupFileSize: Long val enabledSyncFileSizeLimit: Boolean val maxSyncFileSize: Long + + // Limit applies only to non-file paste items (text/html/rtf/url/color); + // file and image pastes are governed by maxSyncFileSize instead + val enabledNonFilePasteSizeLimit: Boolean + + // MB + val maxNonFilePasteSize: Long val useDefaultStoragePath: Boolean val storagePath: String val enableSoundEffect: Boolean From d30f3e29d321fa7c39a275072840d6f533fe547a Mon Sep 17 00:00:00 2001 From: Yiqun Zhang Date: Fri, 31 Jul 2026 12:42:20 +0800 Subject: [PATCH 2/4] :hammer: make the non-file paste size limit always-on and move it to pasteboard settings --- .../crosspaste/paste/PasteReleaseService.kt | 6 +---- .../process/DiscardOversizedNonFilePlugin.kt | 6 +---- .../ui/settings/NetworkSettingsContentView.kt | 23 ------------------- .../settings/PasteboardSettingsContentView.kt | 14 +++++++++++ .../com/crosspaste/config/DesktopAppConfig.kt | 9 -------- .../desktopMain/resources/i18n/de.properties | 3 +-- .../desktopMain/resources/i18n/en.properties | 3 +-- .../desktopMain/resources/i18n/es.properties | 3 +-- .../desktopMain/resources/i18n/fa.properties | 3 +-- .../desktopMain/resources/i18n/fr.properties | 3 +-- .../desktopMain/resources/i18n/ja.properties | 3 +-- .../desktopMain/resources/i18n/ko.properties | 3 +-- .../desktopMain/resources/i18n/pt.properties | 3 +-- .../desktopMain/resources/i18n/zh.properties | 3 +-- .../resources/i18n/zh_hant.properties | 3 +-- .../crosspaste/config/DesktopAppConfigTest.kt | 8 ++----- .../com/crosspaste/config/TestAppConfig.kt | 1 - .../PasteReleaseServiceRemoteDiscardTest.kt | 10 ++++---- .../DiscardOversizedNonFilePluginTest.kt | 21 +++++++++-------- .../kotlin/com/crosspaste/cli/CliAdapters.kt | 1 - .../com/crosspaste/e2e/peer/E2eAppConfig.kt | 1 - .../kotlin/com/crosspaste/config/AppConfig.kt | 7 ++---- 22 files changed, 46 insertions(+), 91 deletions(-) diff --git a/app/src/commonMain/kotlin/com/crosspaste/paste/PasteReleaseService.kt b/app/src/commonMain/kotlin/com/crosspaste/paste/PasteReleaseService.kt index 4f2bd56fe..6a3580ea9 100644 --- a/app/src/commonMain/kotlin/com/crosspaste/paste/PasteReleaseService.kt +++ b/app/src/commonMain/kotlin/com/crosspaste/paste/PasteReleaseService.kt @@ -209,11 +209,7 @@ class PasteReleaseService( * sender keeps the full data, so nothing is lost. */ private suspend fun discardOversizedRemoteNonFilePaste(pasteData: PasteData): Boolean { - val config = commonConfigManager.getCurrentConfig() - if (!config.enabledNonFilePasteSizeLimit) { - return false - } - val maxSize = fileUtils.bytesSize(config.maxNonFilePasteSize) + val maxSize = fileUtils.bytesSize(commonConfigManager.getCurrentConfig().maxNonFilePasteSize) val oversized = pasteData.getPasteAppearItems().any { DiscardOversizedNonFilePlugin.isNonFilePasteType(it.getPasteType()) && it.size > maxSize diff --git a/app/src/commonMain/kotlin/com/crosspaste/paste/plugin/process/DiscardOversizedNonFilePlugin.kt b/app/src/commonMain/kotlin/com/crosspaste/paste/plugin/process/DiscardOversizedNonFilePlugin.kt index 7d1ff60f1..b1eeab126 100644 --- a/app/src/commonMain/kotlin/com/crosspaste/paste/plugin/process/DiscardOversizedNonFilePlugin.kt +++ b/app/src/commonMain/kotlin/com/crosspaste/paste/plugin/process/DiscardOversizedNonFilePlugin.kt @@ -45,11 +45,7 @@ class DiscardOversizedNonFilePlugin( pasteItems: List, source: String?, ): List { - val config = configManager.getCurrentConfig() - if (!config.enabledNonFilePasteSizeLimit) { - return pasteItems - } - val maxSize = fileUtils.bytesSize(config.maxNonFilePasteSize) + val maxSize = fileUtils.bytesSize(configManager.getCurrentConfig().maxNonFilePasteSize) val retainedItems = pasteItems.filter { pasteItem -> val oversized = diff --git a/app/src/commonMain/kotlin/com/crosspaste/ui/settings/NetworkSettingsContentView.kt b/app/src/commonMain/kotlin/com/crosspaste/ui/settings/NetworkSettingsContentView.kt index b7d19aead..6ba551c09 100644 --- a/app/src/commonMain/kotlin/com/crosspaste/ui/settings/NetworkSettingsContentView.kt +++ b/app/src/commonMain/kotlin/com/crosspaste/ui/settings/NetworkSettingsContentView.kt @@ -27,12 +27,10 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp import com.composables.icons.materialsymbols.MaterialSymbols -import com.composables.icons.materialsymbols.rounded.Article import com.composables.icons.materialsymbols.rounded.Docs import com.composables.icons.materialsymbols.rounded.Power import com.composables.icons.materialsymbols.rounded.Shield import com.composables.icons.materialsymbols.rounded.Sync_alt -import com.composables.icons.materialsymbols.rounded.Title import com.composables.icons.materialsymbols.rounded.Visibility import com.crosspaste.config.CommonConfigManager import com.crosspaste.dto.sync.SyncInfo @@ -204,27 +202,6 @@ fun NetworkSettingsContentView(syncExtContent: @Composable () -> Unit = {}) { } }, ) - HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge)) - SettingListSwitchItem( - title = "non_file_paste_size_limit", - subtitle = "non_file_paste_size_limit_desc", - icon = IconData(MaterialSymbols.Rounded.Title, themeExt.amberIconColor), - checked = config.enabledNonFilePasteSizeLimit, - ) { newEnabledNonFilePasteSizeLimit -> - configManager.updateConfig("enabledNonFilePasteSizeLimit", newEnabledNonFilePasteSizeLimit) - } - HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge)) - SettingListItem( - title = "max_non_file_paste_size", - icon = IconData(MaterialSymbols.Rounded.Article, themeExt.cyanIconColor), - trailingContent = { - Counter(defaultValue = config.maxNonFilePasteSize, unit = "MB", rule = { - it > 0 - }) { currentMaxNonFilePasteSize -> - configManager.updateConfig("maxNonFilePasteSize", currentMaxNonFilePasteSize) - } - }, - ) syncExtContent() } } diff --git a/app/src/commonMain/kotlin/com/crosspaste/ui/settings/PasteboardSettingsContentView.kt b/app/src/commonMain/kotlin/com/crosspaste/ui/settings/PasteboardSettingsContentView.kt index 2a2272acb..ea1a90196 100644 --- a/app/src/commonMain/kotlin/com/crosspaste/ui/settings/PasteboardSettingsContentView.kt +++ b/app/src/commonMain/kotlin/com/crosspaste/ui/settings/PasteboardSettingsContentView.kt @@ -15,6 +15,7 @@ import com.composables.icons.materialsymbols.rounded.Content_paste import com.composables.icons.materialsymbols.rounded.Music_note import com.composables.icons.materialsymbols.rounded.Skip_next import com.composables.icons.materialsymbols.rounded.Stacks +import com.composables.icons.materialsymbols.rounded.Title import com.crosspaste.config.CommonConfigManager import com.crosspaste.paste.PasteboardService import com.crosspaste.ui.LocalThemeExtState @@ -90,6 +91,19 @@ fun PasteboardSettingsContentView(extContent: @Composable () -> Unit = {}) { } }, ) + HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge)) + SettingListItem( + title = "max_non_file_paste_size", + subtitle = "max_non_file_paste_size_desc", + icon = IconData(MaterialSymbols.Rounded.Title, themeExt.cyanIconColor), + trailingContent = { + Counter(defaultValue = config.maxNonFilePasteSize, unit = "MB", rule = { + it > 0 + }) { currentMaxNonFilePasteSize -> + configManager.updateConfig("maxNonFilePasteSize", currentMaxNonFilePasteSize) + } + }, + ) extContent() } } diff --git a/app/src/desktopMain/kotlin/com/crosspaste/config/DesktopAppConfig.kt b/app/src/desktopMain/kotlin/com/crosspaste/config/DesktopAppConfig.kt index 28c26b89b..86dfcaed2 100644 --- a/app/src/desktopMain/kotlin/com/crosspaste/config/DesktopAppConfig.kt +++ b/app/src/desktopMain/kotlin/com/crosspaste/config/DesktopAppConfig.kt @@ -39,7 +39,6 @@ data class DesktopAppConfig( override val maxBackupFileSize: Long = 32, override val enabledSyncFileSizeLimit: Boolean = true, override val maxSyncFileSize: Long = 512, - override val enabledNonFilePasteSizeLimit: Boolean = true, // MB override val maxNonFilePasteSize: Long = 8, override val useDefaultStoragePath: Boolean = true, @@ -144,14 +143,6 @@ data class DesktopAppConfig( enabledSyncFileSizeLimit }, maxSyncFileSize = if (key == "maxSyncFileSize") toLong(value) else maxSyncFileSize, - enabledNonFilePasteSizeLimit = - if (key == "enabledNonFilePasteSizeLimit") { - toBoolean( - value, - ) - } else { - enabledNonFilePasteSizeLimit - }, maxNonFilePasteSize = if (key == "maxNonFilePasteSize") toLong(value) else maxNonFilePasteSize, useDefaultStoragePath = if (key == "useDefaultStoragePath") toBoolean(value) else useDefaultStoragePath, storagePath = if (key == "storagePath") toString(value) else storagePath, diff --git a/app/src/desktopMain/resources/i18n/de.properties b/app/src/desktopMain/resources/i18n/de.properties index 2e111da10..57bdc9775 100644 --- a/app/src/desktopMain/resources/i18n/de.properties +++ b/app/src/desktopMain/resources/i18n/de.properties @@ -139,6 +139,7 @@ manual_clear=Manuelle Bereinigung manual_proxy_configuration=Manuelle Proxy-Konfiguration max_back_up_file_size=Maximale Backup-Dateigröße max_non_file_paste_size=Größenlimit für Nicht-Datei-Einträge +max_non_file_paste_size_desc=Text-, HTML-, Rich-Text- und Link-Einträge über dem Limit werden nicht gespeichert; Dateien und Bilder sind nicht betroffen (über das Sync-Dateigrößenlimit geregelt) max_sync_file_size=Maximale Sync-Dateigröße maximum_storage=Maximaler Speicher mcp_claude_config_hint=Führen Sie diesen Befehl im Terminal aus, um CrossPaste zu Claude Code hinzuzufügen: @@ -171,8 +172,6 @@ no_data_import=Kein Datenimport no_new_version_available=Keine neue Version verfügbar new_version_available=Eine neue Version ist verfügbar non_file_paste_discarded_too_large=Text-, HTML-, Rich-Text- oder Link-Inhalt ist zu groß und wurde nicht gespeichert -non_file_paste_size_limit=Größe von Nicht-Datei-Einträgen begrenzen -non_file_paste_size_limit_desc=Text-, HTML-, Rich-Text- und Link-Einträge über dem Limit werden nicht gespeichert; Dateien und Bilder sind nicht betroffen (über das Sync-Dateigrößenlimit geregelt) update_available=Eine neue Version ist verfügbar update_available_dialog_desc=Eine neuere Version von CrossPaste ist bereit. Jetzt aktualisieren, um herunterzuladen und neu zu starten, oder später. update_now=Jetzt aktualisieren diff --git a/app/src/desktopMain/resources/i18n/en.properties b/app/src/desktopMain/resources/i18n/en.properties index 125c01c91..9bc2c4900 100644 --- a/app/src/desktopMain/resources/i18n/en.properties +++ b/app/src/desktopMain/resources/i18n/en.properties @@ -139,6 +139,7 @@ manual_clear=Manual clear manual_proxy_configuration=Manual proxy configuration max_back_up_file_size=Max back up file size max_non_file_paste_size=Non-file clipboard item size limit +max_non_file_paste_size_desc=Text, HTML, rich text, and link items over the limit will not be recorded; files and images are not affected (managed by the sync file size limit) max_sync_file_size=Max sync file size maximum_storage=Maximum Storage mcp_claude_config_hint=Run this command in your terminal to add CrossPaste to Claude Code: @@ -171,8 +172,6 @@ no_data_import=No data import no_new_version_available=No new version available new_version_available=A new version is available non_file_paste_discarded_too_large=Text, HTML, rich text, or link content is too large and was not recorded -non_file_paste_size_limit=Limit non-file clipboard item size -non_file_paste_size_limit_desc=Text, HTML, rich text, and link items over the limit will not be recorded; files and images are not affected (managed by the sync file size limit) update_available=A new version is available update_available_dialog_desc=A newer version of CrossPaste is ready. Update now to download and restart, or do it later. update_now=Update now diff --git a/app/src/desktopMain/resources/i18n/es.properties b/app/src/desktopMain/resources/i18n/es.properties index 0a34843ab..8a53d462a 100644 --- a/app/src/desktopMain/resources/i18n/es.properties +++ b/app/src/desktopMain/resources/i18n/es.properties @@ -139,6 +139,7 @@ manual_clear=Limpieza manual manual_proxy_configuration=Configuración manual de proxy max_back_up_file_size=Tamaño máximo de archivo backup max_non_file_paste_size=Límite de tamaño de elementos que no son archivos +max_non_file_paste_size_desc=Los elementos de texto, HTML, texto enriquecido y enlaces que superen el límite no se registrarán; los archivos e imágenes no se ven afectados (gestionados por el límite de tamaño de archivo de sincronización) max_sync_file_size=Tamaño máximo de archivo de sincronización maximum_storage=Almacenamiento máximo mcp_claude_config_hint=Ejecute este comando en su terminal para añadir CrossPaste a Claude Code: @@ -171,8 +172,6 @@ no_data_import=No hay datos para importar no_new_version_available=No hay una nueva versión disponible new_version_available=Hay una nueva versión disponible non_file_paste_discarded_too_large=El contenido de texto, HTML, texto enriquecido o enlace es demasiado grande y no se registró -non_file_paste_size_limit=Limitar tamaño de elementos que no son archivos -non_file_paste_size_limit_desc=Los elementos de texto, HTML, texto enriquecido y enlaces que superen el límite no se registrarán; los archivos e imágenes no se ven afectados (gestionados por el límite de tamaño de archivo de sincronización) update_available=Hay una nueva versión disponible update_available_dialog_desc=Hay una versión más reciente de CrossPaste lista. Actualiza ahora para descargar y reiniciar, o hazlo más tarde. update_now=Actualizar ahora diff --git a/app/src/desktopMain/resources/i18n/fa.properties b/app/src/desktopMain/resources/i18n/fa.properties index f85d4195a..616461fac 100644 --- a/app/src/desktopMain/resources/i18n/fa.properties +++ b/app/src/desktopMain/resources/i18n/fa.properties @@ -139,6 +139,7 @@ manual_clear=پاک‌سازی دستی manual_proxy_configuration=پیکربندی دستی پروکسی max_back_up_file_size=حداکثر اندازه فایل پشتیبان max_non_file_paste_size=حداکثر اندازه موارد غیرفایلی کلیپ‌بورد +max_non_file_paste_size_desc=موارد متن / HTML / متن غنی / پیوند بزرگ‌تر از حد مجاز ثبت نمی‌شوند؛ فایل‌ها و تصاویر مشمول این محدودیت نیستند (توسط محدودیت اندازه فایل همگام‌سازی مدیریت می‌شوند) max_sync_file_size=حداکثر اندازه فایل برای همگام‌سازی maximum_storage=حداکثر فضای ذخیره‌سازی mcp_claude_config_hint=این دستور را در ترمینال اجرا کنید تا CrossPaste به Claude Code اضافه شود: @@ -171,8 +172,6 @@ no_data_import=داده‌ای برای درون‌ریزی نیست no_new_version_available=نسخه جدیدی در دسترس نیست new_version_available=نسخه جدیدی در دسترس است non_file_paste_discarded_too_large=محتوای متن / HTML / متن غنی / پیوند بسیار بزرگ است و ثبت نشد -non_file_paste_size_limit=محدود کردن اندازه موارد غیرفایلی کلیپ‌بورد -non_file_paste_size_limit_desc=موارد متن / HTML / متن غنی / پیوند بزرگ‌تر از حد مجاز ثبت نمی‌شوند؛ فایل‌ها و تصاویر مشمول این محدودیت نیستند (توسط محدودیت اندازه فایل همگام‌سازی مدیریت می‌شوند) update_available=نسخه جدیدی در دسترس است update_available_dialog_desc=نسخه جدیدتری از CrossPaste آماده است. اکنون به‌روزرسانی کنید تا دانلود و راه‌اندازی مجدد شود، یا بعداً انجام دهید. update_now=اکنون به‌روزرسانی کن diff --git a/app/src/desktopMain/resources/i18n/fr.properties b/app/src/desktopMain/resources/i18n/fr.properties index 5528cfcfe..bfd5ff136 100644 --- a/app/src/desktopMain/resources/i18n/fr.properties +++ b/app/src/desktopMain/resources/i18n/fr.properties @@ -139,6 +139,7 @@ manual_clear=Nettoyage manuel manual_proxy_configuration=Configuration manuelle du proxy max_back_up_file_size=Taille maximale du fichier de sauvegarde max_non_file_paste_size=Limite de taille des éléments non-fichiers +max_non_file_paste_size_desc=Les éléments texte, HTML, texte enrichi et liens dépassant la limite ne seront pas enregistrés ; les fichiers et images ne sont pas concernés (gérés par la limite de taille de fichier de synchronisation) max_sync_file_size=Taille maximale du fichier de synchronisation maximum_storage=Stockage maximum mcp_claude_config_hint=Exécutez cette commande dans votre terminal pour ajouter CrossPaste à Claude Code : @@ -171,8 +172,6 @@ no_data_import=Aucune importation de données no_new_version_available=Aucune nouvelle version disponible new_version_available=Une nouvelle version est disponible non_file_paste_discarded_too_large=Le contenu texte, HTML, texte enrichi ou lien est trop volumineux et n'a pas été enregistré -non_file_paste_size_limit=Limiter la taille des éléments non-fichiers -non_file_paste_size_limit_desc=Les éléments texte, HTML, texte enrichi et liens dépassant la limite ne seront pas enregistrés ; les fichiers et images ne sont pas concernés (gérés par la limite de taille de fichier de synchronisation) update_available=Une nouvelle version est disponible update_available_dialog_desc=Une version plus récente de CrossPaste est prête. Mettez à jour maintenant pour télécharger et redémarrer, ou plus tard. update_now=Mettre à jour maintenant diff --git a/app/src/desktopMain/resources/i18n/ja.properties b/app/src/desktopMain/resources/i18n/ja.properties index 23474f72c..fd252791a 100644 --- a/app/src/desktopMain/resources/i18n/ja.properties +++ b/app/src/desktopMain/resources/i18n/ja.properties @@ -139,6 +139,7 @@ manual_clear=手動クリア manual_proxy_configuration=手動プロキシ設定 max_back_up_file_size=最大バックアップファイルサイズ max_non_file_paste_size=非ファイル系クリップボードのサイズ上限 +max_non_file_paste_size_desc=上限を超えるテキスト / HTML / リッチテキスト / リンクは記録されません。ファイルと画像はこの制限の対象外です(同期ファイルサイズ制限で管理) max_sync_file_size=最大同期ファイルサイズ maximum_storage=最大ストレージ mcp_claude_config_hint=ターミナルでこのコマンドを実行してCrossPasteをClaude Codeに追加してください: @@ -171,8 +172,6 @@ no_data_import=データがインポートされていません no_new_version_available=新しいバージョンはありません new_version_available=新しいバージョンが利用可能です non_file_paste_discarded_too_large=テキスト / HTML / リッチテキスト / リンクの内容が大きすぎるため記録されませんでした -non_file_paste_size_limit=非ファイル系クリップボードのサイズを制限 -non_file_paste_size_limit_desc=上限を超えるテキスト / HTML / リッチテキスト / リンクは記録されません。ファイルと画像はこの制限の対象外です(同期ファイルサイズ制限で管理) update_available=新しいバージョンが利用可能です update_available_dialog_desc=CrossPaste の新しいバージョンが利用可能です。今すぐ更新するとダウンロードして再起動します。後で行うこともできます。 update_now=今すぐ更新 diff --git a/app/src/desktopMain/resources/i18n/ko.properties b/app/src/desktopMain/resources/i18n/ko.properties index 184ff273a..80e27d0c1 100644 --- a/app/src/desktopMain/resources/i18n/ko.properties +++ b/app/src/desktopMain/resources/i18n/ko.properties @@ -139,6 +139,7 @@ manual_clear=수동 지우기 manual_proxy_configuration=수동 프록시 구성 max_back_up_file_size=최대백업파일크기 max_non_file_paste_size=비파일 클립보드 크기 상한 +max_non_file_paste_size_desc=상한을 초과하는 텍스트 / HTML / 서식 있는 텍스트 / 링크는 기록되지 않습니다. 파일과 이미지는 이 제한의 대상이 아닙니다(동기화 파일 크기 제한으로 관리) max_sync_file_size=최대동기화파일크기 maximum_storage=최대저장용량 mcp_claude_config_hint=터미널에서 이 명령을 실행하여 CrossPaste를 Claude Code에 추가하세요: @@ -171,8 +172,6 @@ no_data_import=가져온데이터없음 no_new_version_available=새버전이없습니다 new_version_available=새 버전이 있습니다 non_file_paste_discarded_too_large=텍스트 / HTML / 서식 있는 텍스트 / 링크 내용이 너무 커서 기록되지 않았습니다 -non_file_paste_size_limit=비파일 클립보드 크기 제한 -non_file_paste_size_limit_desc=상한을 초과하는 텍스트 / HTML / 서식 있는 텍스트 / 링크는 기록되지 않습니다. 파일과 이미지는 이 제한의 대상이 아닙니다(동기화 파일 크기 제한으로 관리) update_available=새 버전이 있습니다 update_available_dialog_desc=CrossPaste의 새 버전이 준비되었습니다. 지금 업데이트하면 다운로드 후 재시작합니다. 나중에 할 수도 있습니다. update_now=지금 업데이트 diff --git a/app/src/desktopMain/resources/i18n/pt.properties b/app/src/desktopMain/resources/i18n/pt.properties index 457e49a1c..67e1df4c8 100644 --- a/app/src/desktopMain/resources/i18n/pt.properties +++ b/app/src/desktopMain/resources/i18n/pt.properties @@ -139,6 +139,7 @@ manual_clear=Limpeza manual manual_proxy_configuration=Configuração manual de proxy max_back_up_file_size=Tamanho máximo do arquivo de backup max_non_file_paste_size=Limite de tamanho de itens não-arquivo +max_non_file_paste_size_desc=Itens de texto, HTML, texto rico e links acima do limite não serão registrados; arquivos e imagens não são afetados (controlados pelo limite de arquivo sinc.) max_sync_file_size=Tam. máx. arquivo sinc. maximum_storage=Armazenamento máximo mcp_claude_config_hint=Execute este comando no seu terminal para adicionar o CrossPaste ao Claude Code: @@ -171,8 +172,6 @@ no_data_import=Nenhum dado para importar no_new_version_available=Nenhuma nova versão disponível new_version_available=Uma nova versão está disponível non_file_paste_discarded_too_large=Conteúdo de texto, HTML, texto rico ou link é grande demais e não foi registrado -non_file_paste_size_limit=Limitar tamanho de itens não-arquivo -non_file_paste_size_limit_desc=Itens de texto, HTML, texto rico e links acima do limite não serão registrados; arquivos e imagens não são afetados (controlados pelo limite de arquivo sinc.) update_available=Uma nova versão está disponível update_available_dialog_desc=Uma versão mais recente do CrossPaste está pronta. Atualize agora para baixar e reiniciar, ou faça isso mais tarde. update_now=Atualizar agora diff --git a/app/src/desktopMain/resources/i18n/zh.properties b/app/src/desktopMain/resources/i18n/zh.properties index 81461f47c..3745fa8ad 100644 --- a/app/src/desktopMain/resources/i18n/zh.properties +++ b/app/src/desktopMain/resources/i18n/zh.properties @@ -139,6 +139,7 @@ manual_clear=手动清理 manual_proxy_configuration=手动代理配置 max_back_up_file_size=最大备份文件大小 max_non_file_paste_size=非文件类粘贴板大小上限 +max_non_file_paste_size_desc=超过上限的文本 / HTML / 富文本 / 链接类粘贴板将不被记录;文件与图片不受此限制(由同步文件大小限制管控) max_sync_file_size=最大同步文件大小 maximum_storage=最大存储容量 mcp_claude_config_hint=在终端中运行此命令以将 CrossPaste 添加到 Claude Code: @@ -171,8 +172,6 @@ no_data_import=未导入任何数据 no_new_version_available=没有新版本可用 new_version_available=有新版本可用 non_file_paste_discarded_too_large=文本 / HTML / 富文本 / 链接类内容过大,未记录 -non_file_paste_size_limit=限制非文件类粘贴板大小 -non_file_paste_size_limit_desc=超过上限的文本 / HTML / 富文本 / 链接类粘贴板将不被记录;文件与图片不受此限制(由同步文件大小限制管控) update_available=有新版本可用 update_available_dialog_desc=CrossPaste 有新版本可用。可立即更新(下载并重启),也可以稍后再更新。 update_now=立即更新 diff --git a/app/src/desktopMain/resources/i18n/zh_hant.properties b/app/src/desktopMain/resources/i18n/zh_hant.properties index 2934c85f0..51066199c 100644 --- a/app/src/desktopMain/resources/i18n/zh_hant.properties +++ b/app/src/desktopMain/resources/i18n/zh_hant.properties @@ -139,6 +139,7 @@ manual_clear=手動清理 manual_proxy_configuration=手動代理配置 max_back_up_file_size=最大備份檔案大小 max_non_file_paste_size=非檔案類貼上板大小上限 +max_non_file_paste_size_desc=超過上限的文字 / HTML / 富文字 / 連結類貼上板將不被記錄;檔案與圖片不受此限制(由同步檔案大小限制管控) max_sync_file_size=最大同步檔案大小 maximum_storage=最大儲存容量 mcp_claude_config_hint=在終端機中執行此命令以將 CrossPaste 新增到 Claude Code: @@ -171,8 +172,6 @@ no_data_import=未匯入任何資料 no_new_version_available=沒有新版本可用 new_version_available=有新版本可用 non_file_paste_discarded_too_large=文字 / HTML / 富文字 / 連結類內容過大,未記錄 -non_file_paste_size_limit=限制非檔案類貼上板大小 -non_file_paste_size_limit_desc=超過上限的文字 / HTML / 富文字 / 連結類貼上板將不被記錄;檔案與圖片不受此限制(由同步檔案大小限制管控) update_available=有新版本可用 update_available_dialog_desc=CrossPaste 有新版本可用。可立即更新(下載並重新啟動),也可以稍後再更新。 update_now=立即更新 diff --git a/app/src/desktopTest/kotlin/com/crosspaste/config/DesktopAppConfigTest.kt b/app/src/desktopTest/kotlin/com/crosspaste/config/DesktopAppConfigTest.kt index 74ff1626e..dd580d035 100644 --- a/app/src/desktopTest/kotlin/com/crosspaste/config/DesktopAppConfigTest.kt +++ b/app/src/desktopTest/kotlin/com/crosspaste/config/DesktopAppConfigTest.kt @@ -39,7 +39,6 @@ class DesktopAppConfigTest { assertEquals(32L, config.maxBackupFileSize) assertTrue(config.enabledSyncFileSizeLimit) assertEquals(512L, config.maxSyncFileSize) - assertTrue(config.enabledNonFilePasteSizeLimit) assertEquals(8L, config.maxNonFilePasteSize) assertTrue(config.useDefaultStoragePath) assertEquals("", config.storagePath) @@ -107,23 +106,20 @@ class DesktopAppConfigTest { } @Test - fun `copy updates non-file paste size limit configs`() { + fun `copy updates maxNonFilePasteSize`() { val config: AppConfig = createDefaultConfig() - val disabled = config.copy("enabledNonFilePasteSizeLimit", false) - assertFalse(disabled.enabledNonFilePasteSizeLimit) val resized = config.copy("maxNonFilePasteSize", 16L) assertEquals(16L, resized.maxNonFilePasteSize) } @Test - fun `legacy config without non-file paste size limit uses defaults`() { + fun `legacy config without maxNonFilePasteSize uses default`() { val config = getJsonUtils().JSON.decodeFromString( DesktopAppConfig.serializer(), """{"language":"zh"}""", ) - assertTrue(config.enabledNonFilePasteSizeLimit) assertEquals(8L, config.maxNonFilePasteSize) } diff --git a/app/src/desktopTest/kotlin/com/crosspaste/config/TestAppConfig.kt b/app/src/desktopTest/kotlin/com/crosspaste/config/TestAppConfig.kt index 1145af199..c39c0157c 100644 --- a/app/src/desktopTest/kotlin/com/crosspaste/config/TestAppConfig.kt +++ b/app/src/desktopTest/kotlin/com/crosspaste/config/TestAppConfig.kt @@ -23,7 +23,6 @@ data class TestAppConfig( override val maxBackupFileSize: Long = 32, override val enabledSyncFileSizeLimit: Boolean = true, override val maxSyncFileSize: Long = 512, - override val enabledNonFilePasteSizeLimit: Boolean = true, override val maxNonFilePasteSize: Long = 8, override val useDefaultStoragePath: Boolean = true, override val storagePath: String = "", diff --git a/app/src/desktopTest/kotlin/com/crosspaste/paste/PasteReleaseServiceRemoteDiscardTest.kt b/app/src/desktopTest/kotlin/com/crosspaste/paste/PasteReleaseServiceRemoteDiscardTest.kt index 80a846746..71f9ccf19 100644 --- a/app/src/desktopTest/kotlin/com/crosspaste/paste/PasteReleaseServiceRemoteDiscardTest.kt +++ b/app/src/desktopTest/kotlin/com/crosspaste/paste/PasteReleaseServiceRemoteDiscardTest.kt @@ -28,15 +28,15 @@ class PasteReleaseServiceRemoteDiscardTest { private val maxSizeBytes = 8L * 1024 * 1024 - private fun configManager(enabled: Boolean): CommonConfigManager { + private fun configManager(maxNonFilePasteSize: Long = 8): CommonConfigManager { val configManager = mockk(relaxed = true) every { configManager.getCurrentConfig() } returns - TestAppConfig(enabledNonFilePasteSizeLimit = enabled, maxNonFilePasteSize = 8) + TestAppConfig(maxNonFilePasteSize = maxNonFilePasteSize) return configManager } private fun newService( - commonConfigManager: CommonConfigManager = configManager(enabled = true), + commonConfigManager: CommonConfigManager = configManager(), notificationManager: NotificationManager = mockk(relaxed = true), syncRuntimeInfoDao: SyncRuntimeInfoDao = mockk(relaxed = true), taskSubmitter: TaskSubmitter = mockk(relaxed = true), @@ -121,13 +121,13 @@ class PasteReleaseServiceRemoteDiscardTest { } @Test - fun `disabled limit releases oversized remote paste normally`() = + fun `raised limit releases previously oversized remote paste normally`() = runBlocking { val notificationManager = mockk(relaxed = true) val taskSubmitter = mockk(relaxed = true) val service = newService( - commonConfigManager = configManager(enabled = false), + commonConfigManager = configManager(maxNonFilePasteSize = 64), notificationManager = notificationManager, taskSubmitter = taskSubmitter, ) diff --git a/app/src/desktopTest/kotlin/com/crosspaste/paste/plugin/process/DiscardOversizedNonFilePluginTest.kt b/app/src/desktopTest/kotlin/com/crosspaste/paste/plugin/process/DiscardOversizedNonFilePluginTest.kt index 844c83e06..c2f2d160d 100644 --- a/app/src/desktopTest/kotlin/com/crosspaste/paste/plugin/process/DiscardOversizedNonFilePluginTest.kt +++ b/app/src/desktopTest/kotlin/com/crosspaste/paste/plugin/process/DiscardOversizedNonFilePluginTest.kt @@ -26,10 +26,9 @@ class DiscardOversizedNonFilePluginTest { private val maxSizeBytes = 8L * 1024 * 1024 - private fun configManager(enabled: Boolean): CommonConfigManager { + private fun configManager(): CommonConfigManager { val configManager = mockk(relaxed = true) - every { configManager.getCurrentConfig() } returns - TestAppConfig(enabledNonFilePasteSizeLimit = enabled, maxNonFilePasteSize = 8) + every { configManager.getCurrentConfig() } returns TestAppConfig(maxNonFilePasteSize = 8) return configManager } @@ -44,7 +43,7 @@ class DiscardOversizedNonFilePluginTest { @Test fun `all oversized items are discarded and notification is sent`() { val notificationManager = mockk(relaxed = true) - val plugin = DiscardOversizedNonFilePlugin(configManager(enabled = true), notificationManager) + val plugin = DiscardOversizedNonFilePlugin(configManager(), notificationManager) val result = plugin.process(coord, listOf(oversizedTextItem()), null) @@ -55,7 +54,7 @@ class DiscardOversizedNonFilePluginTest { @Test fun `partial oversized items are degraded silently`() { val notificationManager = mockk(relaxed = true) - val plugin = DiscardOversizedNonFilePlugin(configManager(enabled = true), notificationManager) + val plugin = DiscardOversizedNonFilePlugin(configManager(), notificationManager) val withinLimit = createTextPasteItem(text = "small") val result = plugin.process(coord, listOf(withinLimit, oversizedTextItem()), null) @@ -65,9 +64,11 @@ class DiscardOversizedNonFilePluginTest { } @Test - fun `disabled limit passes items through unchanged`() { + fun `raised limit retains previously oversized items`() { val notificationManager = mockk(relaxed = true) - val plugin = DiscardOversizedNonFilePlugin(configManager(enabled = false), notificationManager) + val configManager = mockk(relaxed = true) + every { configManager.getCurrentConfig() } returns TestAppConfig(maxNonFilePasteSize = 64) + val plugin = DiscardOversizedNonFilePlugin(configManager, notificationManager) val items = listOf(oversizedTextItem()) val result = plugin.process(coord, items, null) @@ -79,7 +80,7 @@ class DiscardOversizedNonFilePluginTest { @Test fun `file and image items are exempt regardless of size`() { val notificationManager = mockk(relaxed = true) - val plugin = DiscardOversizedNonFilePlugin(configManager(enabled = true), notificationManager) + val plugin = DiscardOversizedNonFilePlugin(configManager(), notificationManager) val bigTree = SingleFileInfoTree(size = maxSizeBytes * 10, hash = "big") val filesItem = @@ -101,7 +102,7 @@ class DiscardOversizedNonFilePluginTest { @Test fun `empty list returns empty without notification`() { val notificationManager = mockk(relaxed = true) - val plugin = DiscardOversizedNonFilePlugin(configManager(enabled = true), notificationManager) + val plugin = DiscardOversizedNonFilePlugin(configManager(), notificationManager) val result = plugin.process(coord, emptyList(), null) @@ -112,7 +113,7 @@ class DiscardOversizedNonFilePluginTest { @Test fun `item exactly at limit is retained`() { val notificationManager = mockk(relaxed = true) - val plugin = DiscardOversizedNonFilePlugin(configManager(enabled = true), notificationManager) + val plugin = DiscardOversizedNonFilePlugin(configManager(), notificationManager) val atLimit = TextPasteItem( diff --git a/cli/src/cliNativeMain/kotlin/com/crosspaste/cli/CliAdapters.kt b/cli/src/cliNativeMain/kotlin/com/crosspaste/cli/CliAdapters.kt index cf54eeaa3..5cd0ab36e 100644 --- a/cli/src/cliNativeMain/kotlin/com/crosspaste/cli/CliAdapters.kt +++ b/cli/src/cliNativeMain/kotlin/com/crosspaste/cli/CliAdapters.kt @@ -40,7 +40,6 @@ data class CliReadOnlyAppConfig( override val maxBackupFileSize: Long = 32, override val enabledSyncFileSizeLimit: Boolean = true, override val maxSyncFileSize: Long = 512, - override val enabledNonFilePasteSizeLimit: Boolean = true, override val maxNonFilePasteSize: Long = 8, override val useDefaultStoragePath: Boolean = true, override val storagePath: String = "", diff --git a/e2e/src/desktopMain/kotlin/com/crosspaste/e2e/peer/E2eAppConfig.kt b/e2e/src/desktopMain/kotlin/com/crosspaste/e2e/peer/E2eAppConfig.kt index 912fd69f3..36782ec87 100644 --- a/e2e/src/desktopMain/kotlin/com/crosspaste/e2e/peer/E2eAppConfig.kt +++ b/e2e/src/desktopMain/kotlin/com/crosspaste/e2e/peer/E2eAppConfig.kt @@ -31,7 +31,6 @@ data class E2eAppConfig( override val maxBackupFileSize: Long = 32, override val enabledSyncFileSizeLimit: Boolean = true, override val maxSyncFileSize: Long = 512, - override val enabledNonFilePasteSizeLimit: Boolean = true, override val maxNonFilePasteSize: Long = 8, override val useDefaultStoragePath: Boolean = true, override val storagePath: String = "", diff --git a/shared/src/commonMain/kotlin/com/crosspaste/config/AppConfig.kt b/shared/src/commonMain/kotlin/com/crosspaste/config/AppConfig.kt index 9771d5e53..e2f7f6094 100644 --- a/shared/src/commonMain/kotlin/com/crosspaste/config/AppConfig.kt +++ b/shared/src/commonMain/kotlin/com/crosspaste/config/AppConfig.kt @@ -26,11 +26,8 @@ interface AppConfig { val enabledSyncFileSizeLimit: Boolean val maxSyncFileSize: Long - // Limit applies only to non-file paste items (text/html/rtf/url/color); - // file and image pastes are governed by maxSyncFileSize instead - val enabledNonFilePasteSizeLimit: Boolean - - // MB + // Always-on limit for non-file paste items (text/html/rtf/url/color); + // file and image pastes are governed by maxSyncFileSize instead. MB val maxNonFilePasteSize: Long val useDefaultStoragePath: Boolean val storagePath: String From a0b3067e8681ce591eb0e8500c2087438c7c28c6 Mon Sep 17 00:00:00 2001 From: Yiqun Zhang Date: Fri, 31 Jul 2026 12:59:27 +0800 Subject: [PATCH 3/4] :art: align non-file paste size setting naming with sibling items and shorten its subtitle --- app/src/desktopMain/resources/i18n/de.properties | 4 ++-- app/src/desktopMain/resources/i18n/en.properties | 4 ++-- app/src/desktopMain/resources/i18n/es.properties | 4 ++-- app/src/desktopMain/resources/i18n/fa.properties | 4 ++-- app/src/desktopMain/resources/i18n/fr.properties | 4 ++-- app/src/desktopMain/resources/i18n/ja.properties | 4 ++-- app/src/desktopMain/resources/i18n/ko.properties | 4 ++-- app/src/desktopMain/resources/i18n/pt.properties | 4 ++-- app/src/desktopMain/resources/i18n/zh.properties | 4 ++-- app/src/desktopMain/resources/i18n/zh_hant.properties | 4 ++-- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/src/desktopMain/resources/i18n/de.properties b/app/src/desktopMain/resources/i18n/de.properties index 57bdc9775..2ea8b9a2c 100644 --- a/app/src/desktopMain/resources/i18n/de.properties +++ b/app/src/desktopMain/resources/i18n/de.properties @@ -138,8 +138,8 @@ local_version_low_desc=Aktuelle Softwareversion zu niedrig, Upgrade empfohlen. manual_clear=Manuelle Bereinigung manual_proxy_configuration=Manuelle Proxy-Konfiguration max_back_up_file_size=Maximale Backup-Dateigröße -max_non_file_paste_size=Größenlimit für Nicht-Datei-Einträge -max_non_file_paste_size_desc=Text-, HTML-, Rich-Text- und Link-Einträge über dem Limit werden nicht gespeichert; Dateien und Bilder sind nicht betroffen (über das Sync-Dateigrößenlimit geregelt) +max_non_file_paste_size=Maximale Größe für Nicht-Datei-Einträge +max_non_file_paste_size_desc=Dateien und Bilder sind nicht betroffen max_sync_file_size=Maximale Sync-Dateigröße maximum_storage=Maximaler Speicher mcp_claude_config_hint=Führen Sie diesen Befehl im Terminal aus, um CrossPaste zu Claude Code hinzuzufügen: diff --git a/app/src/desktopMain/resources/i18n/en.properties b/app/src/desktopMain/resources/i18n/en.properties index 9bc2c4900..48f85122f 100644 --- a/app/src/desktopMain/resources/i18n/en.properties +++ b/app/src/desktopMain/resources/i18n/en.properties @@ -138,8 +138,8 @@ local_version_low_desc=Current software version is too low, upgrading is recomme manual_clear=Manual clear manual_proxy_configuration=Manual proxy configuration max_back_up_file_size=Max back up file size -max_non_file_paste_size=Non-file clipboard item size limit -max_non_file_paste_size_desc=Text, HTML, rich text, and link items over the limit will not be recorded; files and images are not affected (managed by the sync file size limit) +max_non_file_paste_size=Max non-file paste size +max_non_file_paste_size_desc=Files and images are not limited max_sync_file_size=Max sync file size maximum_storage=Maximum Storage mcp_claude_config_hint=Run this command in your terminal to add CrossPaste to Claude Code: diff --git a/app/src/desktopMain/resources/i18n/es.properties b/app/src/desktopMain/resources/i18n/es.properties index 8a53d462a..d707058f4 100644 --- a/app/src/desktopMain/resources/i18n/es.properties +++ b/app/src/desktopMain/resources/i18n/es.properties @@ -138,8 +138,8 @@ local_version_low_desc=La versión actual de software es demasiado baja, se reco manual_clear=Limpieza manual manual_proxy_configuration=Configuración manual de proxy max_back_up_file_size=Tamaño máximo de archivo backup -max_non_file_paste_size=Límite de tamaño de elementos que no son archivos -max_non_file_paste_size_desc=Los elementos de texto, HTML, texto enriquecido y enlaces que superen el límite no se registrarán; los archivos e imágenes no se ven afectados (gestionados por el límite de tamaño de archivo de sincronización) +max_non_file_paste_size=Tamaño máximo de elementos que no son archivos +max_non_file_paste_size_desc=Los archivos e imágenes no están limitados max_sync_file_size=Tamaño máximo de archivo de sincronización maximum_storage=Almacenamiento máximo mcp_claude_config_hint=Ejecute este comando en su terminal para añadir CrossPaste a Claude Code: diff --git a/app/src/desktopMain/resources/i18n/fa.properties b/app/src/desktopMain/resources/i18n/fa.properties index 616461fac..0bc0517c6 100644 --- a/app/src/desktopMain/resources/i18n/fa.properties +++ b/app/src/desktopMain/resources/i18n/fa.properties @@ -138,8 +138,8 @@ local_version_low_desc=نسخه نرم‌افزار فعلی بیش از حد ق manual_clear=پاک‌سازی دستی manual_proxy_configuration=پیکربندی دستی پروکسی max_back_up_file_size=حداکثر اندازه فایل پشتیبان -max_non_file_paste_size=حداکثر اندازه موارد غیرفایلی کلیپ‌بورد -max_non_file_paste_size_desc=موارد متن / HTML / متن غنی / پیوند بزرگ‌تر از حد مجاز ثبت نمی‌شوند؛ فایل‌ها و تصاویر مشمول این محدودیت نیستند (توسط محدودیت اندازه فایل همگام‌سازی مدیریت می‌شوند) +max_non_file_paste_size=حداکثر اندازه موارد غیرفایلی +max_non_file_paste_size_desc=فایل‌ها و تصاویر محدود نمی‌شوند max_sync_file_size=حداکثر اندازه فایل برای همگام‌سازی maximum_storage=حداکثر فضای ذخیره‌سازی mcp_claude_config_hint=این دستور را در ترمینال اجرا کنید تا CrossPaste به Claude Code اضافه شود: diff --git a/app/src/desktopMain/resources/i18n/fr.properties b/app/src/desktopMain/resources/i18n/fr.properties index bfd5ff136..5b036aff9 100644 --- a/app/src/desktopMain/resources/i18n/fr.properties +++ b/app/src/desktopMain/resources/i18n/fr.properties @@ -138,8 +138,8 @@ local_version_low_desc=Version actuelle du logiciel trop ancienne, mise à jour manual_clear=Nettoyage manuel manual_proxy_configuration=Configuration manuelle du proxy max_back_up_file_size=Taille maximale du fichier de sauvegarde -max_non_file_paste_size=Limite de taille des éléments non-fichiers -max_non_file_paste_size_desc=Les éléments texte, HTML, texte enrichi et liens dépassant la limite ne seront pas enregistrés ; les fichiers et images ne sont pas concernés (gérés par la limite de taille de fichier de synchronisation) +max_non_file_paste_size=Taille maximale des éléments non-fichiers +max_non_file_paste_size_desc=Les fichiers et images ne sont pas limités max_sync_file_size=Taille maximale du fichier de synchronisation maximum_storage=Stockage maximum mcp_claude_config_hint=Exécutez cette commande dans votre terminal pour ajouter CrossPaste à Claude Code : diff --git a/app/src/desktopMain/resources/i18n/ja.properties b/app/src/desktopMain/resources/i18n/ja.properties index fd252791a..8fefa7a50 100644 --- a/app/src/desktopMain/resources/i18n/ja.properties +++ b/app/src/desktopMain/resources/i18n/ja.properties @@ -138,8 +138,8 @@ local_version_low_desc=現在のソフトウェアバージョンが古すぎま manual_clear=手動クリア manual_proxy_configuration=手動プロキシ設定 max_back_up_file_size=最大バックアップファイルサイズ -max_non_file_paste_size=非ファイル系クリップボードのサイズ上限 -max_non_file_paste_size_desc=上限を超えるテキスト / HTML / リッチテキスト / リンクは記録されません。ファイルと画像はこの制限の対象外です(同期ファイルサイズ制限で管理) +max_non_file_paste_size=最大非ファイル系クリップボードサイズ +max_non_file_paste_size_desc=ファイルと画像は制限されません max_sync_file_size=最大同期ファイルサイズ maximum_storage=最大ストレージ mcp_claude_config_hint=ターミナルでこのコマンドを実行してCrossPasteをClaude Codeに追加してください: diff --git a/app/src/desktopMain/resources/i18n/ko.properties b/app/src/desktopMain/resources/i18n/ko.properties index 80e27d0c1..a9f16891a 100644 --- a/app/src/desktopMain/resources/i18n/ko.properties +++ b/app/src/desktopMain/resources/i18n/ko.properties @@ -138,8 +138,8 @@ local_version_low_desc=현재 소프트웨어 버전이 너무 낮습니다. 업 manual_clear=수동 지우기 manual_proxy_configuration=수동 프록시 구성 max_back_up_file_size=최대백업파일크기 -max_non_file_paste_size=비파일 클립보드 크기 상한 -max_non_file_paste_size_desc=상한을 초과하는 텍스트 / HTML / 서식 있는 텍스트 / 링크는 기록되지 않습니다. 파일과 이미지는 이 제한의 대상이 아닙니다(동기화 파일 크기 제한으로 관리) +max_non_file_paste_size=최대 비파일 클립보드 크기 +max_non_file_paste_size_desc=파일과 이미지는 제한되지 않습니다 max_sync_file_size=최대동기화파일크기 maximum_storage=최대저장용량 mcp_claude_config_hint=터미널에서 이 명령을 실행하여 CrossPaste를 Claude Code에 추가하세요: diff --git a/app/src/desktopMain/resources/i18n/pt.properties b/app/src/desktopMain/resources/i18n/pt.properties index 67e1df4c8..1ce50b62b 100644 --- a/app/src/desktopMain/resources/i18n/pt.properties +++ b/app/src/desktopMain/resources/i18n/pt.properties @@ -138,8 +138,8 @@ local_version_low_desc=A versão atual do software é muito antiga, recomenda-se manual_clear=Limpeza manual manual_proxy_configuration=Configuração manual de proxy max_back_up_file_size=Tamanho máximo do arquivo de backup -max_non_file_paste_size=Limite de tamanho de itens não-arquivo -max_non_file_paste_size_desc=Itens de texto, HTML, texto rico e links acima do limite não serão registrados; arquivos e imagens não são afetados (controlados pelo limite de arquivo sinc.) +max_non_file_paste_size=Tam. máx. itens não-arquivo +max_non_file_paste_size_desc=Arquivos e imagens não são limitados max_sync_file_size=Tam. máx. arquivo sinc. maximum_storage=Armazenamento máximo mcp_claude_config_hint=Execute este comando no seu terminal para adicionar o CrossPaste ao Claude Code: diff --git a/app/src/desktopMain/resources/i18n/zh.properties b/app/src/desktopMain/resources/i18n/zh.properties index 3745fa8ad..a0e641f4f 100644 --- a/app/src/desktopMain/resources/i18n/zh.properties +++ b/app/src/desktopMain/resources/i18n/zh.properties @@ -138,8 +138,8 @@ local_version_low_desc=当前软件版本过低,建议升级版本。 manual_clear=手动清理 manual_proxy_configuration=手动代理配置 max_back_up_file_size=最大备份文件大小 -max_non_file_paste_size=非文件类粘贴板大小上限 -max_non_file_paste_size_desc=超过上限的文本 / HTML / 富文本 / 链接类粘贴板将不被记录;文件与图片不受此限制(由同步文件大小限制管控) +max_non_file_paste_size=最大非文件类粘贴板大小 +max_non_file_paste_size_desc=文件与图片不受此限制 max_sync_file_size=最大同步文件大小 maximum_storage=最大存储容量 mcp_claude_config_hint=在终端中运行此命令以将 CrossPaste 添加到 Claude Code: diff --git a/app/src/desktopMain/resources/i18n/zh_hant.properties b/app/src/desktopMain/resources/i18n/zh_hant.properties index 51066199c..d4bdf068e 100644 --- a/app/src/desktopMain/resources/i18n/zh_hant.properties +++ b/app/src/desktopMain/resources/i18n/zh_hant.properties @@ -138,8 +138,8 @@ local_version_low_desc=當前軟體版本過低,建議升級版本。 manual_clear=手動清理 manual_proxy_configuration=手動代理配置 max_back_up_file_size=最大備份檔案大小 -max_non_file_paste_size=非檔案類貼上板大小上限 -max_non_file_paste_size_desc=超過上限的文字 / HTML / 富文字 / 連結類貼上板將不被記錄;檔案與圖片不受此限制(由同步檔案大小限制管控) +max_non_file_paste_size=最大非檔案類貼上板大小 +max_non_file_paste_size_desc=檔案與圖片不受此限制 max_sync_file_size=最大同步檔案大小 maximum_storage=最大儲存容量 mcp_claude_config_hint=在終端機中執行此命令以將 CrossPaste 新增到 Claude Code: From 47afdb684620ce65018920b11a66b6a0f425d856 Mon Sep 17 00:00:00 2001 From: Yiqun Zhang Date: Fri, 31 Jul 2026 14:47:23 +0800 Subject: [PATCH 4/4] :bug: use min-height for setting list items so two-line rows are not vertically clipped --- .../crosspaste/ui/settings/Settings.desktop.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/desktopMain/kotlin/com/crosspaste/ui/settings/Settings.desktop.kt b/app/src/desktopMain/kotlin/com/crosspaste/ui/settings/Settings.desktop.kt index 64fe4ebb8..cd15c3483 100644 --- a/app/src/desktopMain/kotlin/com/crosspaste/ui/settings/Settings.desktop.kt +++ b/app/src/desktopMain/kotlin/com/crosspaste/ui/settings/Settings.desktop.kt @@ -1,7 +1,7 @@ package com.crosspaste.ui.settings import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.material3.ListItem import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.MaterialTheme @@ -32,8 +32,8 @@ actual fun SettingListItem( ListItem( modifier = Modifier - .height( - if (isSmallItem) xxxxLarge else huge, + .heightIn( + min = if (isSmallItem) xxxxLarge else huge, ).then( if (onClick != null) Modifier.clickable { onClick() } else Modifier, ), @@ -78,8 +78,8 @@ actual fun SettingListItem( ListItem( modifier = Modifier - .height( - if (isSmallItem) xxxxLarge else huge, + .heightIn( + min = if (isSmallItem) xxxxLarge else huge, ).then( if (onClick != null) Modifier.clickable { onClick() } else Modifier, ), @@ -117,8 +117,8 @@ actual fun SettingListItem( ListItem( modifier = Modifier - .height( - if (isSmallItem) xxxxLarge else huge, + .heightIn( + min = if (isSmallItem) xxxxLarge else huge, ).then( if (onClick != null) Modifier.clickable { onClick() } else Modifier, ), @@ -152,8 +152,8 @@ actual fun SettingListSwitchItem( val isSmallItem = LocalSmallSettingItemState.current ListItem( modifier = - Modifier.height( - if (isSmallItem) xxxxLarge else huge, + Modifier.heightIn( + min = if (isSmallItem) xxxxLarge else huge, ), headlineContent = { Text(copywriter.getText(title), style = MaterialTheme.typography.bodyMedium) }, supportingContent =