Merge branch 'develop' into sw-file
This commit is contained in:
commit
7de7ecb566
|
@ -1 +1 @@
|
|||
20.18.1
|
||||
22.15.0
|
||||
|
|
19
CHANGELOG.md
19
CHANGELOG.md
|
@ -1,12 +1,29 @@
|
|||
## Unreleased
|
||||
## 2025.8.0
|
||||
|
||||
### Note
|
||||
- サポートされるNode.jsの最小バージョンが**22.15.0**になりました
|
||||
|
||||
### General
|
||||
- ノートを削除した際、関連するノートが同時に削除されないようになりました
|
||||
- APIで、「replyIdが存在しているのにreplyがnull」や「renoteIdが存在しているのにrenoteがnull」であるという、今までにはなかったパターンが表れることになります
|
||||
- 定期的に参照されていない古いリモートの投稿を削除する機能が実装されました(コントロールパネル→パフォーマンス→Remote Notes Cleaning)
|
||||
- 既存のサーバーでは**デフォルトでオフ**、新規サーバーでは**デフォルトでオン**になります
|
||||
- データベースの肥大化を防止することが可能です
|
||||
- 既存のサーバーで当機能を有効化した場合は、処理量が多くなるため、一時的にストレージ使用量が増加する可能性があります。
|
||||
- 増加量を抑えるには、最大処理継続時間をデフォルトより短くしてください。
|
||||
- サーバーの初期設定が完了するまでは連合がオンにならないようになりました
|
||||
- 日本語における公開範囲名称の「ダイレクト」が「指名」に改称されました
|
||||
|
||||
### Client
|
||||
- Feat: セーフモード
|
||||
- プラグイン・テーマ・カスタムCSSの使用でクライアントの起動に問題が発生した際に、これらを無効にして起動できます
|
||||
- 以下の方法でセーフモードを起動できます
|
||||
- `g` キーを連打する
|
||||
- URLに`?safemode=true`を付ける
|
||||
- PWAのショートカットで Safemode を選択して起動する
|
||||
- Fix: 一部の設定検索結果が存在しないパスになる問題を修正
|
||||
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1171)
|
||||
- Fix: テーマエディタが動作しない問題を修正
|
||||
|
||||
### Server
|
||||
- Enhance: ノートの削除処理の効率化
|
||||
|
|
|
@ -1008,6 +1008,8 @@ lastNDays: "آخر {n} أيام"
|
|||
surrender: "ألغِ"
|
||||
postForm: "أنشئ ملاحظة"
|
||||
information: "عن"
|
||||
inMinutes: "د"
|
||||
inDays: "ي"
|
||||
_chat:
|
||||
invitations: "دعوة"
|
||||
noHistory: "السجل فارغ"
|
||||
|
|
|
@ -848,6 +848,8 @@ sourceCode: "সোর্স কোড"
|
|||
flip: "উল্টান"
|
||||
postForm: "নোট লিখুন"
|
||||
information: "আপনার সম্পর্কে"
|
||||
inMinutes: "মিনিট"
|
||||
inDays: "দিন"
|
||||
_chat:
|
||||
invitations: "আমন্ত্রণ"
|
||||
noHistory: "কোনো ইতিহাস নেই"
|
||||
|
|
|
@ -896,7 +896,7 @@ searchResult: "Resultats de la cerca"
|
|||
hashtags: "Etiquetes"
|
||||
troubleshooting: "Solucionar problemes"
|
||||
useBlurEffect: "Fes servir efectes de desenfocament a la interfície"
|
||||
learnMore: "Saber més "
|
||||
learnMore: "Saber-ne més "
|
||||
misskeyUpdated: "Misskey s'ha actualitzat "
|
||||
whatIsNew: "Mostra canvis"
|
||||
translate: "Traduir "
|
||||
|
@ -1368,6 +1368,8 @@ redisplayAllTips: "Torna ha mostrat tots els trucs i consells"
|
|||
hideAllTips: "Amagar tots els trucs i consells"
|
||||
defaultImageCompressionLevel: "Nivell de comprensió de la imatge per defecte"
|
||||
defaultImageCompressionLevel_description: "Baixa, conserva la qualitat de la imatge però la mida de l'arxiu és més gran. <br>Alta, redueix la mida de l'arxiu però també la qualitat de la imatge."
|
||||
inMinutes: "Minut(s)"
|
||||
inDays: "Di(a)(es)"
|
||||
_order:
|
||||
newest: "Més recent"
|
||||
oldest: "Cronològic"
|
||||
|
|
|
@ -1107,6 +1107,8 @@ lastNDays: "Posledních {n} dnů"
|
|||
surrender: "Zrušit"
|
||||
postForm: "Formulář pro odeslání"
|
||||
information: "Informace"
|
||||
inMinutes: "Minut"
|
||||
inDays: "Dnů"
|
||||
_chat:
|
||||
invitations: "Pozvat"
|
||||
noHistory: "Žádná historie"
|
||||
|
|
|
@ -1368,6 +1368,8 @@ redisplayAllTips: "Alle „Tipps und Tricks“ wieder anzeigen"
|
|||
hideAllTips: "Alle „Tipps und Tricks“ ausblenden"
|
||||
defaultImageCompressionLevel: "Standard-Bildkomprimierungsstufe"
|
||||
defaultImageCompressionLevel_description: "Ein niedrigerer Wert erhält die Bildqualität, erhöht aber die Dateigröße. <br>Höhere Werte reduzieren die Dateigröße, verringern aber die Bildqualität."
|
||||
inMinutes: "Minute(n)"
|
||||
inDays: "Tag(en)"
|
||||
_order:
|
||||
newest: "Neueste zuerst"
|
||||
oldest: "Älteste zuerst"
|
||||
|
|
|
@ -1302,7 +1302,7 @@ passkeyVerificationSucceededButPasswordlessLoginDisabled: "Passkey verification
|
|||
messageToFollower: "Message to followers"
|
||||
target: "Target"
|
||||
testCaptchaWarning: "This function is intended for CAPTCHA testing purposes.\n<strong>Do not use in a production environment.</strong>"
|
||||
prohibitedWordsForNameOfUser: "Prohibited words for user names"
|
||||
prohibitedWordsForNameOfUser: "Prohibited words for usernames"
|
||||
prohibitedWordsForNameOfUserDescription: "If any of the strings in this list are included in the user's name, the name will be denied. Users with moderator privileges are not affected by this restriction."
|
||||
yourNameContainsProhibitedWords: "Your name contains prohibited words"
|
||||
yourNameContainsProhibitedWordsDescription: "If you wish to use this name, please contact your server administrator."
|
||||
|
@ -1368,6 +1368,8 @@ redisplayAllTips: "Show all “Tips & Tricks” again"
|
|||
hideAllTips: "Hide all \"Tips & Tricks\""
|
||||
defaultImageCompressionLevel: "Default image compression level"
|
||||
defaultImageCompressionLevel_description: "Lower level preserves image quality but increases file size.<br>Higher level reduce file size, but reduce image quality."
|
||||
inMinutes: "Minute(s)"
|
||||
inDays: "Day(s)"
|
||||
_order:
|
||||
newest: "Newest First"
|
||||
oldest: "Oldest First"
|
||||
|
|
|
@ -280,8 +280,8 @@ featured: "Destacados"
|
|||
usernameOrUserId: "Nombre o ID del usuario"
|
||||
noSuchUser: "No se encuentra el usuario"
|
||||
lookup: "Búsqueda"
|
||||
announcements: "Anuncios"
|
||||
imageUrl: "URL de la imágen"
|
||||
announcements: "Avisos"
|
||||
imageUrl: "URL de la imagen."
|
||||
remove: "Borrar"
|
||||
removed: "Borrado"
|
||||
removeAreYouSure: "¿Desea borrar \"{x}\"?"
|
||||
|
@ -842,7 +842,7 @@ unlikeConfirm: "¿Quitar como favorito?"
|
|||
fullView: "Vista completa"
|
||||
quitFullView: "quitar vista completa"
|
||||
addDescription: "Agregar descripción"
|
||||
userPagePinTip: "Puede mantener sus notas visibles aquí seleccionando Pin en el menú de notas individuales"
|
||||
userPagePinTip: "Puede mantener sus notas visibles aquí seleccionando 'Fijar al perfil' en el menú de notas individuales"
|
||||
notSpecifiedMentionWarning: "Algunas menciones no están incluidas en el destino"
|
||||
info: "Información"
|
||||
userInfo: "Información del usuario"
|
||||
|
@ -877,7 +877,7 @@ popularPosts: "Más vistos"
|
|||
shareWithNote: "Compartir con una nota"
|
||||
ads: "Anuncios"
|
||||
expiration: "Termina el"
|
||||
startingperiod: "periodo de inicio"
|
||||
startingperiod: "Comienzo"
|
||||
memo: "Notas"
|
||||
priority: "Prioridad"
|
||||
high: "Alta"
|
||||
|
@ -1143,7 +1143,7 @@ channelArchiveConfirmTitle: "¿Seguro de archivar {name}?"
|
|||
channelArchiveConfirmDescription: "Un canal archivado no aparecerá en la lista de canales ni en los resultados. Las nuevas publicaciones tampoco serán añadidas."
|
||||
thisChannelArchived: "El canal ha sido archivado."
|
||||
displayOfNote: "Mostrar notas"
|
||||
initialAccountSetting: "Configración inicial de su cuenta\nか\nConfigración de inicio"
|
||||
initialAccountSetting: "Configración inicial de su cuenta"
|
||||
youFollowing: "Siguiendo"
|
||||
preventAiLearning: "Rechazar el uso en el Aprendizaje de Máquinas. (IA Generativa)"
|
||||
preventAiLearningDescription: "Pedirle a las arañas (crawlers) no usar los textos publicados o imágenes en el aprendizaje automático (IA Predictiva / Generativa). Ésto se logra añadiendo una marca respuesta HTML con la cadena \"noai\" al cantenido. Una prevención total no podría lograrse sólo usando ésta marca, ya que puede ser simplemente ignorada."
|
||||
|
@ -1358,8 +1358,8 @@ advice: "Consejos"
|
|||
realtimeMode: "Modo en tiempo real"
|
||||
turnItOn: "Activar"
|
||||
turnItOff: "Desactivar"
|
||||
emojiMute: "Silenciar emojis"
|
||||
emojiUnmute: "No Silenciar emojis"
|
||||
emojiMute: "Silenciar emoji"
|
||||
emojiUnmute: "No silenciar emoji"
|
||||
muteX: "Silenciar {x}"
|
||||
unmuteX: "Dejar de silenciar {x}"
|
||||
abort: "Abortar"
|
||||
|
@ -1368,6 +1368,8 @@ redisplayAllTips: "Volver a mostrar todos \"Trucos y consejos\""
|
|||
hideAllTips: "Ocultar todos los \"Trucos y consejos\""
|
||||
defaultImageCompressionLevel: "Nivel de compresión de la imagen por defecto"
|
||||
defaultImageCompressionLevel_description: "Baja, conserva la calidad de la imagen pero la medida del archivo es más grande. <br>Alta, reduce la medida del archivo pero también la calidad de la imagen."
|
||||
inMinutes: "Minutos"
|
||||
inDays: "Días"
|
||||
_order:
|
||||
newest: "Los más recientes primero"
|
||||
oldest: "Los más antiguos primero"
|
||||
|
@ -1530,7 +1532,7 @@ _announcement:
|
|||
tooManyActiveAnnouncementDescription: "Tener demasiados anuncios activos empeora la experiencia de usuario. Por favor, considera archivar aquellos anuncios que hayan quedado obsoletos."
|
||||
readConfirmTitle: "¿Marcar como leído?"
|
||||
readConfirmText: "Esto marcará el contenido de \"{title}\" como leído."
|
||||
shouldNotBeUsedToPresentPermanentInfo: "Dado que puede impactar en la experiencia de usuario de forma significativa, es recomendable usar notificaciones en el flujo de información en vez de información persistente."
|
||||
shouldNotBeUsedToPresentPermanentInfo: "Se recomienda utilizar los avisos para publicar información que requiera inmediatez, en lugar de hacerlo constantemente, ya que esto perjudica especialmente la UX de los nuevos usuarios."
|
||||
dialogAnnouncementUxWarn: "Mostrar dos o más notificaciones en formato diálogo a la vez puede impactar en la experiencia de usuario de forma significativa, úsalos con cuidado."
|
||||
silence: "Silenciar notificaciones"
|
||||
silenceDescription: "Si lo activas, no enviarás notificación sobre este anuncio y el usuario no tendrá que leerlo."
|
||||
|
@ -3121,7 +3123,7 @@ _uploader:
|
|||
tip: "El archivo aún no se ha cargado, por lo que este cuadro de diálogo te permite confirmar, renombrar, comprimir y recortar el archivo antes de cargarlo. Cuando esté listo, puedes iniciar la carga pulsando el botón \"Cargar\"."
|
||||
_clientPerformanceIssueTip:
|
||||
title: "Si crees que el consumo de batería es demasiado alto"
|
||||
makeSureDisabledAdBlocker: "Por favor, desactive el bloqueador de publicidad."
|
||||
makeSureDisabledAdBlocker: "Por favor, desactiva el bloqueador de publicidad."
|
||||
makeSureDisabledAdBlocker_description: "Los bloqueadores de anuncios pueden afectar al rendimiento. Asegúrate de que no están activados en tu sistema o en las funciones/extensiones de tu navegador."
|
||||
makeSureDisabledCustomCss: "Desactiva el CSS personalizado"
|
||||
makeSureDisabledCustomCss_description: "Anular estilos puede afectar al rendimiento. Asegúrate de que el CSS personalizado o las extensiones que sobrescriben estilos no están activados."
|
||||
|
|
|
@ -1272,6 +1272,8 @@ pleaseSelectAccount: "Sélectionner un compte"
|
|||
availableRoles: "Rôles disponibles"
|
||||
postForm: "Formulaire de publication"
|
||||
information: "Informations"
|
||||
inMinutes: "min"
|
||||
inDays: "j"
|
||||
_chat:
|
||||
invitations: "Inviter"
|
||||
noHistory: "Pas d'historique"
|
||||
|
|
|
@ -1263,6 +1263,8 @@ thereAreNChanges: "Ada {n} perubahan"
|
|||
prohibitedWordsForNameOfUser: "Kata yang dilarang untuk nama pengguna"
|
||||
postForm: "Buat catatan"
|
||||
information: "Informasi"
|
||||
inMinutes: "menit"
|
||||
inDays: "hari"
|
||||
_chat:
|
||||
invitations: "Undang"
|
||||
noHistory: "Tidak ada riwayat"
|
||||
|
|
|
@ -315,11 +315,11 @@ export interface Locale extends ILocale {
|
|||
*/
|
||||
"mention": string;
|
||||
/**
|
||||
* あなた宛て
|
||||
* メンション
|
||||
*/
|
||||
"mentions": string;
|
||||
/**
|
||||
* ダイレクト投稿
|
||||
* 指名
|
||||
*/
|
||||
"directNotes": string;
|
||||
/**
|
||||
|
@ -5493,6 +5493,30 @@ export interface Locale extends ILocale {
|
|||
* 低くすると画質を保てますが、ファイルサイズは増加します。<br>高くするとファイルサイズを減らせますが、画質は低下します。
|
||||
*/
|
||||
"defaultImageCompressionLevel_description": string;
|
||||
/**
|
||||
* 分
|
||||
*/
|
||||
"inMinutes": string;
|
||||
/**
|
||||
* 日
|
||||
*/
|
||||
"inDays": string;
|
||||
/**
|
||||
* セーフモードが有効です
|
||||
*/
|
||||
"safeModeEnabled": string;
|
||||
/**
|
||||
* セーフモードが有効なため、プラグインはすべて無効化されています。
|
||||
*/
|
||||
"pluginsAreDisabledBecauseSafeMode": string;
|
||||
/**
|
||||
* セーフモードが有効なため、カスタムCSSは適用されていません。
|
||||
*/
|
||||
"customCssIsDisabledBecauseSafeMode": string;
|
||||
/**
|
||||
* セーフモードが有効な間はデフォルトのテーマが使用されます。セーフモードをオフにすると元に戻ります。
|
||||
*/
|
||||
"themeIsDefaultBecauseSafeMode": string;
|
||||
"_order": {
|
||||
/**
|
||||
* 新しい順
|
||||
|
@ -6329,7 +6353,7 @@ export interface Locale extends ILocale {
|
|||
*/
|
||||
"followers": string;
|
||||
/**
|
||||
* 指定したユーザーにのみ公開され、また相手に通知が入ります。ダイレクトメッセージのかわりにお使いいただけます。
|
||||
* 指定したユーザーにのみ公開され、また相手に通知が入ります。
|
||||
*/
|
||||
"direct": string;
|
||||
/**
|
||||
|
@ -6337,7 +6361,7 @@ export interface Locale extends ILocale {
|
|||
*/
|
||||
"doNotSendConfidencialOnDirect1": string;
|
||||
/**
|
||||
* 送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーにダイレクト投稿を送信する場合は、機密情報の扱いに注意が必要です。
|
||||
* 送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーが含まれる限定公開のノートを作成する際は、機密情報の扱いに注意が必要です。
|
||||
*/
|
||||
"doNotSendConfidencialOnDirect2": string;
|
||||
/**
|
||||
|
@ -6486,6 +6510,22 @@ export interface Locale extends ILocale {
|
|||
* 有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。
|
||||
*/
|
||||
"reactionsBufferingDescription": string;
|
||||
/**
|
||||
* リモート投稿の自動クリーニング
|
||||
*/
|
||||
"remoteNotesCleaning": string;
|
||||
/**
|
||||
* 有効にすると、参照されていない古いリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。
|
||||
*/
|
||||
"remoteNotesCleaning_description": string;
|
||||
/**
|
||||
* 最大クリーニング処理継続時間
|
||||
*/
|
||||
"remoteNotesCleaningMaxProcessingDuration": string;
|
||||
/**
|
||||
* 最低ノート保持日数
|
||||
*/
|
||||
"remoteNotesCleaningExpiryDaysForEachNotes": string;
|
||||
/**
|
||||
* 問い合わせ先URL
|
||||
*/
|
||||
|
@ -6558,6 +6598,14 @@ export interface Locale extends ILocale {
|
|||
* サーバーで受信したリモートのコンテンツを含め、サーバー内の全てのコンテンツを無条件でインターネットに公開することはリスクが伴います。特に、分散型の特性を知らない閲覧者にとっては、リモートのコンテンツであってもサーバー内で作成されたコンテンツであると誤って認識してしまう可能性があるため、注意が必要です。
|
||||
*/
|
||||
"userGeneratedContentsVisibilityForVisitor_description2": string;
|
||||
/**
|
||||
* サーバーの初期設定ウィザードをやり直しますか?
|
||||
*/
|
||||
"restartServerSetupWizardConfirm_title": string;
|
||||
/**
|
||||
* 現在の一部の設定はリセットされます。
|
||||
*/
|
||||
"restartServerSetupWizardConfirm_text": string;
|
||||
"_userGeneratedContentsVisibilityForVisitor": {
|
||||
/**
|
||||
* 全て公開
|
||||
|
@ -9593,7 +9641,7 @@ export interface Locale extends ILocale {
|
|||
*/
|
||||
"followersDescription": string;
|
||||
/**
|
||||
* ダイレクト
|
||||
* 指名
|
||||
*/
|
||||
"specified": string;
|
||||
/**
|
||||
|
@ -10482,11 +10530,11 @@ export interface Locale extends ILocale {
|
|||
*/
|
||||
"channel": string;
|
||||
/**
|
||||
* あなた宛て
|
||||
* メンション
|
||||
*/
|
||||
"mentions": string;
|
||||
/**
|
||||
* ダイレクト
|
||||
* 指名
|
||||
*/
|
||||
"direct": string;
|
||||
/**
|
||||
|
@ -11807,6 +11855,10 @@ export interface Locale extends ILocale {
|
|||
* 修復ツールを起動
|
||||
*/
|
||||
"otherOption3": string;
|
||||
/**
|
||||
* Misskeyをセーフモードで起動
|
||||
*/
|
||||
"otherOption4": string;
|
||||
};
|
||||
"_search": {
|
||||
/**
|
||||
|
@ -11943,6 +11995,14 @@ export interface Locale extends ILocale {
|
|||
* 連合可能なサーバーの指定など、高度な設定も後ほど可能です。
|
||||
*/
|
||||
"youCanConfigureMoreFederationSettingsLater": string;
|
||||
/**
|
||||
* 受信コンテンツの自動クリーニング
|
||||
*/
|
||||
"remoteContentsCleaning": string;
|
||||
/**
|
||||
* 連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、参照されていない古くなったコンテンツを自動でサーバーから削除し、ストレージを節約できます。
|
||||
*/
|
||||
"remoteContentsCleaning_description": string;
|
||||
/**
|
||||
* 管理者情報
|
||||
*/
|
||||
|
|
|
@ -1313,6 +1313,7 @@ availableRoles: "Ruoli disponibili"
|
|||
acknowledgeNotesAndEnable: "Attivare dopo averne compreso il comportamento."
|
||||
federationSpecified: "Questo server è federato solo con istanze specifiche del Fediverso. Puoi interagire solo con quelle scelte dall'amministrazione."
|
||||
federationDisabled: "Questo server ha la federazione disabilitata. Non puoi interagire con profili provenienti da altri server."
|
||||
draft: "Bozza"
|
||||
confirmOnReact: "Confermare le reazioni"
|
||||
reactAreYouSure: "Vuoi davvero reagire con {emoji} ?"
|
||||
markAsSensitiveConfirm: "Vuoi davvero indicare questo contenuto multimediale come esplicito?"
|
||||
|
@ -1367,6 +1368,11 @@ redisplayAllTips: "Mostra tutti i suggerimenti"
|
|||
hideAllTips: "Nascondi tutti i suggerimenti"
|
||||
defaultImageCompressionLevel: "Livello predefinito di compressione immagini"
|
||||
defaultImageCompressionLevel_description: "La compressione diminuisce la qualità dell'immagine, poca compressione mantiene alta qualità delle immagini. Aumentandola, si riducono le dimensioni del file, a discapito della qualità dell'immagine."
|
||||
inMinutes: "min"
|
||||
inDays: "giorni"
|
||||
_order:
|
||||
newest: "Prima i più recenti"
|
||||
oldest: "Meno recenti prima"
|
||||
_chat:
|
||||
noMessagesYet: "Ancora nessun messaggio"
|
||||
newMessage: "Nuovo messaggio"
|
||||
|
@ -1993,6 +1999,8 @@ _role:
|
|||
uploadableFileTypes: "Tipi di file caricabili"
|
||||
uploadableFileTypes_caption: "Specifica il tipo MIME. Puoi specificare più valori separandoli andando a capo, oppure indicare caratteri jolly con un asterisco (*). Ad esempio: image/*"
|
||||
uploadableFileTypes_caption2: "A seconda del file, il tipo potrebbe non essere determinato. Se si desidera consentire tali file, aggiungere {x} alla specifica."
|
||||
noteDraftLimit: "Numero massimo di Note in bozza, lato server"
|
||||
watermarkAvailable: "Disponibilità della funzione filigrana"
|
||||
_condition:
|
||||
roleAssignedTo: "Assegnato a ruoli manualmente"
|
||||
isLocal: "Profilo locale"
|
||||
|
@ -2152,6 +2160,7 @@ _theme:
|
|||
install: "Installa un tema"
|
||||
manage: "Gestione dei temi"
|
||||
code: "Codice tema"
|
||||
copyThemeCode: "Copia il codice del Tema"
|
||||
description: "Descrizione"
|
||||
installed: "{name} è installato"
|
||||
installedThemes: "Temi installati"
|
||||
|
@ -2800,6 +2809,7 @@ _fileViewer:
|
|||
url: "URL"
|
||||
uploadedAt: "Caricato il"
|
||||
attachedNotes: "Note a cui è allegato"
|
||||
usage: "In uso"
|
||||
thisPageCanBeSeenFromTheAuthor: "Questa pagina può essere vista solo da chi ha caricato il file."
|
||||
_externalResourceInstaller:
|
||||
title: "Installa da sito esterno"
|
||||
|
@ -3103,6 +3113,7 @@ _serverSetupWizard:
|
|||
text2: "Se puoi, ti preghiamo di prendere in considerazione l'idea di fare una donazione, così potremo continuare a sviluppare."
|
||||
text3: "Sono previsti anche dei vantaggi speciali per i sostenitori!"
|
||||
_uploader:
|
||||
editImage: "Modifica immagine"
|
||||
compressedToX: "Compresso in {x}"
|
||||
savedXPercent: "{x}% risparmiati"
|
||||
abortConfirm: "Alcuni file non sono stati caricati. Vuoi annullare l'operazione?"
|
||||
|
@ -3169,5 +3180,20 @@ _imageEffector:
|
|||
stripe: "Strisce"
|
||||
polkadot: "A pallini"
|
||||
checker: "revisore"
|
||||
blockNoise: "Attenua rumore"
|
||||
tearing: "Strappa immagine"
|
||||
drafts: "Bozza"
|
||||
_drafts:
|
||||
select: "Selezionare bozza"
|
||||
cannotCreateDraftAnymore: "Hai superato il numero massimo di bozze ammissibili."
|
||||
cannotCreateDraft: "Impossibile creare una bozza di questo contenuto."
|
||||
delete: "Elimina bozza"
|
||||
deleteAreYouSure: "Vuoi davvero eliminare la bozza?"
|
||||
noDrafts: "Non c'è nessuna bozza."
|
||||
replyTo: "Rispondere a {user}"
|
||||
quoteOf: "Citare la nota di {user}"
|
||||
postTo: "Inserire in {channel}"
|
||||
saveToDraft: "Salva come bozza"
|
||||
restoreFromDraft: "Recuperare dalle bozze"
|
||||
restore: "Ripristina"
|
||||
listDrafts: "Elenco bozze"
|
||||
|
|
|
@ -74,8 +74,8 @@ youGotNewFollower: "フォローされました"
|
|||
receiveFollowRequest: "フォローリクエストされました"
|
||||
followRequestAccepted: "フォローが承認されました"
|
||||
mention: "メンション"
|
||||
mentions: "あなた宛て"
|
||||
directNotes: "ダイレクト投稿"
|
||||
mentions: "メンション"
|
||||
directNotes: "指名"
|
||||
importAndExport: "インポートとエクスポート"
|
||||
import: "インポート"
|
||||
export: "エクスポート"
|
||||
|
@ -1368,6 +1368,12 @@ redisplayAllTips: "全ての「ヒントとコツ」を再表示"
|
|||
hideAllTips: "全ての「ヒントとコツ」を非表示"
|
||||
defaultImageCompressionLevel: "デフォルトの画像圧縮度"
|
||||
defaultImageCompressionLevel_description: "低くすると画質を保てますが、ファイルサイズは増加します。<br>高くするとファイルサイズを減らせますが、画質は低下します。"
|
||||
inMinutes: "分"
|
||||
inDays: "日"
|
||||
safeModeEnabled: "セーフモードが有効です"
|
||||
pluginsAreDisabledBecauseSafeMode: "セーフモードが有効なため、プラグインはすべて無効化されています。"
|
||||
customCssIsDisabledBecauseSafeMode: "セーフモードが有効なため、カスタムCSSは適用されていません。"
|
||||
themeIsDefaultBecauseSafeMode: "セーフモードが有効な間はデフォルトのテーマが使用されます。セーフモードをオフにすると元に戻ります。"
|
||||
|
||||
_order:
|
||||
newest: "新しい順"
|
||||
|
@ -1603,9 +1609,9 @@ _initialTutorial:
|
|||
public: "すべてのユーザーに公開。"
|
||||
home: "ホームタイムラインのみに公開。フォロワー・プロフィールを見に来た人・リノートから、他のユーザーも見ることができます。"
|
||||
followers: "フォロワーにのみ公開。本人以外がリノートすることはできず、またフォロワー以外は閲覧できません。"
|
||||
direct: "指定したユーザーにのみ公開され、また相手に通知が入ります。ダイレクトメッセージのかわりにお使いいただけます。"
|
||||
direct: "指定したユーザーにのみ公開され、また相手に通知が入ります。"
|
||||
doNotSendConfidencialOnDirect1: "機密情報は送信する際は注意してください。"
|
||||
doNotSendConfidencialOnDirect2: "送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーにダイレクト投稿を送信する場合は、機密情報の扱いに注意が必要です。"
|
||||
doNotSendConfidencialOnDirect2: "送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーが含まれる限定公開のノートを作成する際は、機密情報の扱いに注意が必要です。"
|
||||
localOnly: "他のサーバーに投稿を連合しません。上記の公開範囲に関わらず、他のサーバーのユーザーは、この設定がついたノートを直接閲覧することができなくなります。"
|
||||
_cw:
|
||||
title: "内容を隠す(CW)"
|
||||
|
@ -1649,6 +1655,10 @@ _serverSettings:
|
|||
fanoutTimelineDbFallback: "データベースへのフォールバック"
|
||||
fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。"
|
||||
reactionsBufferingDescription: "有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。"
|
||||
remoteNotesCleaning: "リモート投稿の自動クリーニング"
|
||||
remoteNotesCleaning_description: "有効にすると、参照されていない古いリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。"
|
||||
remoteNotesCleaningMaxProcessingDuration: "最大クリーニング処理継続時間"
|
||||
remoteNotesCleaningExpiryDaysForEachNotes: "最低ノート保持日数"
|
||||
inquiryUrl: "問い合わせ先URL"
|
||||
inquiryUrlDescription: "サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定します。"
|
||||
openRegistration: "アカウントの作成をオープンにする"
|
||||
|
@ -1667,6 +1677,8 @@ _serverSettings:
|
|||
userGeneratedContentsVisibilityForVisitor: "非利用者に対するユーザー作成コンテンツの公開範囲"
|
||||
userGeneratedContentsVisibilityForVisitor_description: "モデレーションが行き届きにくい不適切なリモートコンテンツなどが、自サーバー経由で図らずもインターネットに公開されてしまうことによるトラブル防止などに役立ちます。"
|
||||
userGeneratedContentsVisibilityForVisitor_description2: "サーバーで受信したリモートのコンテンツを含め、サーバー内の全てのコンテンツを無条件でインターネットに公開することはリスクが伴います。特に、分散型の特性を知らない閲覧者にとっては、リモートのコンテンツであってもサーバー内で作成されたコンテンツであると誤って認識してしまう可能性があるため、注意が必要です。"
|
||||
restartServerSetupWizardConfirm_title: "サーバーの初期設定ウィザードをやり直しますか?"
|
||||
restartServerSetupWizardConfirm_text: "現在の一部の設定はリセットされます。"
|
||||
|
||||
_userGeneratedContentsVisibilityForVisitor:
|
||||
all: "全て公開"
|
||||
|
@ -2524,7 +2536,7 @@ _visibility:
|
|||
homeDescription: "ホームタイムラインのみに公開"
|
||||
followers: "フォロワー"
|
||||
followersDescription: "自分のフォロワーのみに公開"
|
||||
specified: "ダイレクト"
|
||||
specified: "指名"
|
||||
specifiedDescription: "指定したユーザーのみに公開"
|
||||
disableFederation: "連合なし"
|
||||
disableFederationDescription: "他サーバーへの配信を行いません"
|
||||
|
@ -2770,8 +2782,8 @@ _deck:
|
|||
antenna: "アンテナ"
|
||||
list: "リスト"
|
||||
channel: "チャンネル"
|
||||
mentions: "あなた宛て"
|
||||
direct: "ダイレクト"
|
||||
mentions: "メンション"
|
||||
direct: "指名"
|
||||
roleTimeline: "ロールタイムライン"
|
||||
chat: "チャット"
|
||||
|
||||
|
@ -3156,6 +3168,7 @@ _bootErrors:
|
|||
otherOption1: "クライアント設定とキャッシュを削除"
|
||||
otherOption2: "簡易クライアントを起動"
|
||||
otherOption3: "修復ツールを起動"
|
||||
otherOption4: "Misskeyをセーフモードで起動"
|
||||
|
||||
_search:
|
||||
searchScopeAll: "全て"
|
||||
|
@ -3194,6 +3207,8 @@ _serverSetupWizard:
|
|||
doYouConnectToFediverse_description1: "分散型サーバーで構成されるネットワーク(Fediverse)に接続すると、他のサーバーと相互にコンテンツのやり取りが可能です。"
|
||||
doYouConnectToFediverse_description2: "Fediverseと接続することは「連合」とも呼ばれます。"
|
||||
youCanConfigureMoreFederationSettingsLater: "連合可能なサーバーの指定など、高度な設定も後ほど可能です。"
|
||||
remoteContentsCleaning: "受信コンテンツの自動クリーニング"
|
||||
remoteContentsCleaning_description: "連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、参照されていない古くなったコンテンツを自動でサーバーから削除し、ストレージを節約できます。"
|
||||
adminInfo: "管理者情報"
|
||||
adminInfo_description: "問い合わせを受け付けるために使用される管理者情報を設定します。"
|
||||
adminInfo_mustBeFilled: "オープンサーバー、または連合がオンの場合は必ず入力が必要です。"
|
||||
|
|
|
@ -300,6 +300,7 @@ uploadFromUrlMayTakeTime: "アップロード終わるんにちょい時間か
|
|||
explore: "みつける"
|
||||
messageRead: "もう読んだ"
|
||||
noMoreHistory: "これより昔のんはあらへんで"
|
||||
startChat: "チャットを始めよか"
|
||||
nUsersRead: "{n}人が読んでもうた"
|
||||
agreeTo: "{0}に同意したで"
|
||||
agree: "せやな"
|
||||
|
@ -324,6 +325,7 @@ dark: "ダーク"
|
|||
lightThemes: "デイゲーム"
|
||||
darkThemes: "ナイトゲーム"
|
||||
syncDeviceDarkMode: "デバイスのダークモードと一緒にする"
|
||||
switchDarkModeManuallyWhenSyncEnabledConfirm: "「{x}」がオンになってるで。同期をオフにして手動でモードを切り替えることにします?"
|
||||
drive: "ドライブ"
|
||||
fileName: "ファイル名"
|
||||
selectFile: "ファイル選んでや"
|
||||
|
@ -422,6 +424,7 @@ antennaExcludeBots: "Botアカウントを除外"
|
|||
antennaKeywordsDescription: "スペースで区切ったるとAND指定で、改行で区切ったるとOR指定や"
|
||||
notifyAntenna: "新しいノートを通知すんで"
|
||||
withFileAntenna: "なんか添付されたノートだけ"
|
||||
excludeNotesInSensitiveChannel: "センシティブなチャンネルのノートは入れんとくわ"
|
||||
enableServiceworker: "ブラウザにプッシュ通知が行くようにする"
|
||||
antennaUsersDescription: "ユーザー名を改行で区切ったってな"
|
||||
caseSensitive: "大文字と小文字は別もんや"
|
||||
|
@ -693,6 +696,7 @@ userSaysSomethingAbout: "{name}が「{word}」についてなんか言うてた
|
|||
makeActive: "使うで"
|
||||
display: "表示"
|
||||
copy: "コピー"
|
||||
copiedToClipboard: "クリップボードにコピーされたで"
|
||||
metrics: "メトリクス"
|
||||
overview: "概要"
|
||||
logs: "ログ"
|
||||
|
@ -787,6 +791,7 @@ wide: "広い"
|
|||
narrow: "狭い"
|
||||
reloadToApplySetting: "設定はページリロード後に反映されるで。今リロードしとくか?"
|
||||
needReloadToApply: "反映には再起動せなあかんで"
|
||||
needToRestartServerToApply: "反映にはサーバーを再起動せなあかんのよ。"
|
||||
showTitlebar: "タイトルバーを見せる"
|
||||
clearCache: "キャッシュをほかす"
|
||||
onlineUsersCount: "{n}人が起きとるで"
|
||||
|
@ -974,6 +979,7 @@ document: "ドキュメント"
|
|||
numberOfPageCache: "ページ、どんだけキャッシュすんの?"
|
||||
numberOfPageCacheDescription: "増やすと使いやすくなるけど、負荷とメモリ使用量が増えてくで。一長一短やな。"
|
||||
logoutConfirm: "ログアウトしまっか?"
|
||||
logoutWillClearClientData: "ログアウトするとクライアントの設定情報がブラウザから消されてまうで。再ログイン時に設定情報を復元できるようにするためには、設定の自動バックアップを有効にするとええで。"
|
||||
lastActiveDate: "最後に使った日時"
|
||||
statusbar: "ステータスバー"
|
||||
pleaseSelect: "選んだってやー"
|
||||
|
@ -992,6 +998,7 @@ failedToUpload: "アップロードに失敗してもうたわ…"
|
|||
cannotUploadBecauseInappropriate: "きわどい内容を含むかもしれへんって言われたからアップロードできへんわ。"
|
||||
cannotUploadBecauseNoFreeSpace: "ドライブがもうパンパンやからアップロードできへんわ。"
|
||||
cannotUploadBecauseExceedsFileSizeLimit: "ファイルが思うたよりも大きいさかいアップロードできへんでこれ。"
|
||||
cannotUploadBecauseUnallowedFileType: "許可されてへんファイル種別やからアップロードできへんっぽい。"
|
||||
beta: "ベータ"
|
||||
enableAutoSensitive: "自動できわどいか判断する"
|
||||
enableAutoSensitiveDescription: "使える時は、機械学習を使って自動でメディアにNSFWフラグを設定するで。この機能をオフにしても、サーバーによっては自動で設定されることがあるで。"
|
||||
|
@ -1304,11 +1311,37 @@ federationSpecified: "このサーバーはホワイトリスト連合で運用
|
|||
federationDisabled: "このサーバーは連合が無効化されてるで。他のサーバーのユーザーとやり取りすることはできひんで。"
|
||||
confirmOnReact: "ツッコむときに確認とる"
|
||||
reactAreYouSure: "\" {emoji} \" でツッコむ?"
|
||||
markAsSensitiveConfirm: "このメディアをきわどい扱いしときますか?"
|
||||
unmarkAsSensitiveConfirm: "このメディアはやっぱきわどくなかったってことでええんか?"
|
||||
noName: "名前はあらへんで"
|
||||
preferenceSyncConflictTitle: "サーバーに設定値があるみたいやわ"
|
||||
preferenceSyncConflictText: "同期が有効にされた設定項目は設定値をサーバーに保存するねんけど、この設定項目はサーバーに保存されたやつがあるみたいやわ。どないするん?"
|
||||
preferenceSyncConflictChoiceMerge: "ガッチャンコしよか"
|
||||
preferenceSyncConflictChoiceCancel: "同期の有効化はやめとくわ"
|
||||
postForm: "投稿フォーム"
|
||||
information: "情報"
|
||||
migrateOldSettings: "旧設定情報をお引っ越し"
|
||||
migrateOldSettings_description: "通常これは自動で行われるはずなんやけど、なんかの理由で上手く移行できへんかったときは手動で移行処理をポチっとできるで。今の設定情報は上書きされるで。"
|
||||
settingsMigrating: "設定を移行しとるで。ちょっと待っとってな... (後で、設定→その他→旧設定情報を移行 で手動で移行することもできるで)"
|
||||
driveAboutTip: "ドライブでは、今までアップロードしたファイルがずらーっと表示されるで。<br>\nノートにファイルをもっかいのっけたり、あとで投稿するファイルをその辺に置いとくこともできるねん。<br>\n<b>ファイルをほかすと、前にそのファイルをのっけた全部の場所(ノート、ページ、アバター、バナー等)からも見えんくなるから気いつけてな。</b><br>\nフォルダを作って整理することもできるで。"
|
||||
turnItOn: "オンにしとこ"
|
||||
turnItOff: "オフでええわ"
|
||||
emojiUnmute: "絵文字ミュートやめたる"
|
||||
unmuteX: "{x}のミュートやめたる"
|
||||
redisplayAllTips: "全部の「ヒントとコツ」をもっかい見して"
|
||||
hideAllTips: "「ヒントとコツ」は全部表示せんでええ"
|
||||
defaultImageCompressionLevel_description: "低くすると画質は保てるんやけど、ファイルサイズが増えるで。<br>高くするとファイルサイズは減らせるんやけど、画質が落ちるで。"
|
||||
inMinutes: "分"
|
||||
inDays: "日"
|
||||
_chat:
|
||||
noMessagesYet: "まだメッセージはあらへんで"
|
||||
individualChat_description: "特定のユーザーと一対一でチャットができるで。"
|
||||
roomChat_description: "複数人でチャットできるで。\nあと、個人チャットを許可してへんユーザーとでも、相手がええって言うならチャットできるで。"
|
||||
inviteUserToChat: "ユーザーを招待してチャットを始めてみ"
|
||||
invitations: "来てや"
|
||||
noInvitations: "招待はあらへんで"
|
||||
noHistory: "履歴はないわ。"
|
||||
noRooms: "ルームはあらへんで"
|
||||
members: "メンバーはん"
|
||||
home: "ホーム"
|
||||
send: "送信"
|
||||
|
@ -2617,7 +2650,7 @@ _externalResourceInstaller:
|
|||
_errors:
|
||||
_invalidParams:
|
||||
title: ""
|
||||
description: ""
|
||||
description: "外部サイトからデータを持ってくるのに欲しい情報が足らへんみたいやわ。URLは合っとる?"
|
||||
_resourceTypeNotSupported:
|
||||
title: ""
|
||||
description: ""
|
||||
|
@ -2648,7 +2681,7 @@ _dataSaver:
|
|||
title: "アイコンの絵"
|
||||
description: "アイコン画像のアニメが止まるで。普通の画像よりもデータ量がでかいから、もっと通信量を節約できるねん。"
|
||||
_code:
|
||||
title: "コードハイライト"
|
||||
title: "コードハイライトは表示せんでええ"
|
||||
description: "MFMとかでコードハイライト記法が使われてるとき、タップするまで読み込まれへんくなるで。コードハイライトではハイライトする言語ごとにその決めてるファイルを読む必要はあんねんな。けどな、それは自動で読み込まれなくなるから、通信量を少なくできることができるねん。"
|
||||
_hemisphere:
|
||||
N: "北半球"
|
||||
|
@ -2858,3 +2891,8 @@ _watermarkEditor:
|
|||
image: "画像"
|
||||
advanced: "高度"
|
||||
angle: "角度"
|
||||
_imageEffector:
|
||||
discardChangesConfirm: "変更をせんで終わるか?"
|
||||
_drafts:
|
||||
deleteAreYouSure: "下書きをほかしてもええか?"
|
||||
noDrafts: "下書きはあらへん"
|
||||
|
|
|
@ -1368,6 +1368,8 @@ redisplayAllTips: "모든 '팁과 유용한 정보'를 재표시"
|
|||
hideAllTips: "모든 '팁과 유용한 정보'를 비표시"
|
||||
defaultImageCompressionLevel: "기본 이미지 압축 정도"
|
||||
defaultImageCompressionLevel_description: "낮추면 화질을 유지합니다만 파일 크기는 증가합니다. <br>높이면 파일 크기를 줄일 수 있습니다만 화질은 저하됩니다."
|
||||
inMinutes: "분"
|
||||
inDays: "일"
|
||||
_order:
|
||||
newest: "최신 순"
|
||||
oldest: "오래된 순"
|
||||
|
|
|
@ -461,6 +461,8 @@ replies: "Svar"
|
|||
renotes: "Renote"
|
||||
surrender: "Avbryt"
|
||||
information: "Informasjon"
|
||||
inMinutes: "Minutter"
|
||||
inDays: "Dager"
|
||||
_chat:
|
||||
invitations: "Inviter"
|
||||
members: "Medlemmer"
|
||||
|
|
|
@ -1040,6 +1040,8 @@ surrender: "Odrzuć"
|
|||
gameRetry: "Spróbuj ponownie"
|
||||
postForm: "Formularz tworzenia wpisu"
|
||||
information: "Informacje"
|
||||
inMinutes: "minuta"
|
||||
inDays: "dzień"
|
||||
_chat:
|
||||
invitations: "Zaproś"
|
||||
noHistory: "Brak historii"
|
||||
|
|
|
@ -1368,6 +1368,8 @@ redisplayAllTips: "Mostrar todas as \"Dicas e Truques\" novamente"
|
|||
hideAllTips: "Ocultas todas as \"Dicas e Truques\""
|
||||
defaultImageCompressionLevel: "Nível de compressão de imagem padrão"
|
||||
defaultImageCompressionLevel_description: "Alto, reduz o tamanho do arquivo mas, também, a qualidade da imagem.<br>Alto, reduz o tamanho do arquivo mas, também, a qualidade da imagem."
|
||||
inMinutes: "Minuto(s)"
|
||||
inDays: "Dia(s)"
|
||||
_order:
|
||||
newest: "Priorizar Mais Novos"
|
||||
oldest: "Priorizar Mais Antigos"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
_lang_: "Русский"
|
||||
headlineMisskey: "Сеть, сплетённая из заметок"
|
||||
introMisskey: "Добро пожаловать! Misskey — это децентрализованный сервис микроблогов с открытым исходным кодом.\nПишите «заметки» — делитесь со всеми происходящим вокруг или рассказывайте о себе 📡\nСтавьте «реакции» — выражайте свои чувства и эмоции от заметок других 👍\nОткройте для себя новый мир 🚀"
|
||||
poweredByMisskeyDescription: "{name} – сервис на платформе с открытым исходным кодом <b>Misskey</b>, называемый экземпляром Misskey."
|
||||
poweredByMisskeyDescription: "{name} – один из инстансов (также называемый экземпляром Misskey), использующий платформу с открытым исходным кодом <b>Misskey</b>."
|
||||
monthAndDay: "{day}.{month}"
|
||||
search: "Поиск"
|
||||
reset: "Сброс"
|
||||
|
@ -82,7 +82,7 @@ export: "Экспорт"
|
|||
files: "Файлы"
|
||||
download: "Скачать"
|
||||
driveFileDeleteConfirm: "Удалить файл «{name}»? Заметки с ним также будут удалены."
|
||||
unfollowConfirm: "Удалить из подписок пользователя {name}?"
|
||||
unfollowConfirm: "Отписаться от {name} ?"
|
||||
exportRequested: "Вы запросили экспорт. Это может занять некоторое время. Результат будет добавлен на «Диск»."
|
||||
importRequested: "Вы запросили импорт. Это может занять некоторое время."
|
||||
lists: "Списки"
|
||||
|
@ -298,6 +298,7 @@ uploadFromUrl: "Загрузить по ссылке"
|
|||
uploadFromUrlDescription: "Ссылка на файл, который хотите загрузить"
|
||||
uploadFromUrlRequested: "Загрузка выбранного"
|
||||
uploadFromUrlMayTakeTime: "Загрузка может занять некоторое время."
|
||||
uploadNFiles: "Загрузить {n} файл"
|
||||
explore: "Обзор"
|
||||
messageRead: "Прочитали"
|
||||
noMoreHistory: "История закончилась"
|
||||
|
@ -575,8 +576,10 @@ showFixedPostForm: "Показывать поле для ввода новой
|
|||
showFixedPostFormInChannel: "Показывать поле для ввода новой заметки наверху ленты (каналы)"
|
||||
withRepliesByDefaultForNewlyFollowed: "По умолчанию включайте ответы новых пользователей, на которых вы подписались, во временную шкалу"
|
||||
newNoteRecived: "Появилась новая заметка"
|
||||
newNote: "Новая заметка"
|
||||
sounds: "Звуки"
|
||||
sound: "Звуки"
|
||||
notificationSoundSettings: "Настройки звука уведомлений"
|
||||
listen: "Слушать"
|
||||
none: "Ничего"
|
||||
showInPage: "Показать страницу"
|
||||
|
@ -791,6 +794,7 @@ wide: "Толстый"
|
|||
narrow: "Тонкий"
|
||||
reloadToApplySetting: "Это настройка вступает в силу при загрузке страницы. Перезагрузить сейчас?"
|
||||
needReloadToApply: "Изменения вступят в силу после перезагрузки страницы."
|
||||
needToRestartServerToApply: "Для вступления изменений в силу необходимо перезапустить сервер."
|
||||
showTitlebar: "Показать заголовок"
|
||||
clearCache: "Очистить кэш"
|
||||
onlineUsersCount: "Пользователей сейчас в сети: {n}"
|
||||
|
@ -1176,13 +1180,25 @@ unused: "Неиспользованное"
|
|||
used: "Использован"
|
||||
expired: "Срок действия приглашения истёк"
|
||||
doYouAgree: "Согласны?"
|
||||
beSureToReadThisAsItIsImportant: "Это важно, поэтому, пожалуйста, прочтите это."
|
||||
iHaveReadXCarefullyAndAgree: "Я прочитал(а) и согласен(сна) с условиями \"{x}"
|
||||
dialog: "Диалог"
|
||||
icon: "Аватар"
|
||||
currentAnnouncements: "Текущие новости"
|
||||
pastAnnouncements: "Предыдущие новости"
|
||||
youHaveUnreadAnnouncements: "У вас есть непрочитанные уведомления"
|
||||
replies: "Ответы"
|
||||
renotes: "Репост"
|
||||
loadReplies: "Показать ответы"
|
||||
loadConversation: "Загрузить беседу"
|
||||
pinnedList: "Закреплённый список"
|
||||
keepScreenOn: "Держать экран включённым"
|
||||
unnotifyNotes: "Отписаться от сообщений"
|
||||
authentication: "Аутентификация"
|
||||
authenticationRequiredToContinue: "Пожалуйста, пройдите аутентификацию, чтобы продолжить"
|
||||
dateAndTime: "Дата и время"
|
||||
showRenotes: "Показывать репосты"
|
||||
edited: "Изменено"
|
||||
mutualFollow: "Взаимные подписки"
|
||||
followingOrFollower: "Подписки или подписчики"
|
||||
fileAttachedOnly: "Только заметки с файлами"
|
||||
|
@ -1193,30 +1209,71 @@ sourceCode: "Исходный код"
|
|||
sourceCodeIsNotYetProvided: "Исходный код пока не доступен. Свяжитесь с администратором, чтобы исправить эту проблему."
|
||||
repositoryUrl: "Ссылка на репозиторий"
|
||||
repositoryUrlDescription: "Если вы используете Misskey как есть (без изменений в исходном коде), введите https://github.com/misskey-dev/misskey"
|
||||
feedback: "Обратная связь"
|
||||
privacyPolicy: "Политика Конфиденциальности"
|
||||
privacyPolicyUrl: "Ссылка на Политику Конфиденциальности"
|
||||
tosAndPrivacyPolicy: "Условия использования и политика конфиденциальности"
|
||||
avatarDecorations: "Украшения для аватара"
|
||||
attach: "Прикрепить"
|
||||
angle: "Угол"
|
||||
flip: "Переворот"
|
||||
showAvatarDecorations: "Показать украшения для аватара"
|
||||
pullDownToRefresh: "Опустите что бы обновить"
|
||||
useGroupedNotifications: "Отображать уведомления сгруппировано"
|
||||
signupPendingError: "Возникла проблема с подтверждением вашего адреса электронной почты. Возможно, срок действия ссылки истёк."
|
||||
cwNotationRequired: "Если включена опция «Скрыть содержимое», необходимо написать аннотацию."
|
||||
doReaction: "Добавить реакцию"
|
||||
code: "Код"
|
||||
reloadRequiredToApplySettings: "Для применения настроек необходима обновить страницу."
|
||||
remainingN: "Остаётся: {n}"
|
||||
overwriteContentConfirm: "Текущее содержимое будет перезаписано. Вы уверены?"
|
||||
seasonalScreenEffect: "Эффект времени года на экране"
|
||||
decorate: "Украсить"
|
||||
addMfmFunction: "Добавить MFM"
|
||||
bubbleGame: "BubbleGame"
|
||||
sfx: "Звуковые эффекты"
|
||||
soundWillBePlayed: "Будет воспроизведен звук"
|
||||
showReplay: "Показать повтор"
|
||||
endReplay: "Конец повтора"
|
||||
lastNDays: "Последние {n} сут"
|
||||
hemisphere: "Место проживания"
|
||||
userSaysSomethingSensitive: "Сообщение, содержит конфиденциальные файлы от {name}"
|
||||
enableHorizontalSwipe: "Смахните в сторону, чтобы сменить вкладки"
|
||||
surrender: "Этот пост не может быть отменен."
|
||||
gameRetry: "Повторить попытку"
|
||||
notUsePleaseLeaveBlank: "Если не используется, оставьте пустым"
|
||||
useNativeUIForVideoAudioPlayer: "Использовать интерфейс браузера при проигрывании видео и звука"
|
||||
keepOriginalFilename: "Сохранять исходное имя файла"
|
||||
keepOriginalFilenameDescription: "Если вы выключите данную настройку, имена файлов будут автоматически заменены случайной строкой при загрузке."
|
||||
alwaysConfirmFollow: "Всегда подтверждать подписку"
|
||||
inquiry: "Связаться"
|
||||
fromX: "Из {x}"
|
||||
genEmbedCode: "Сгенерировать код для "
|
||||
noteOfThisUser: "Список заметок этого пользователя"
|
||||
clipNoteLimitExceeded: "К этому клипу больше нельзя добавить заметки"
|
||||
performance: "Производительность"
|
||||
modified: "Изменено"
|
||||
signinWithPasskey: "Войдите в систему, используя свой пароль"
|
||||
unknownWebAuthnKey: "Не известный ключ "
|
||||
passkeyVerificationFailed: "Ошибка проверка ключа доступа "
|
||||
messageToFollower: "Сообщение подписчикам"
|
||||
testCaptchaWarning: "Эта функция предназначена для тестирования CAPTCHA. <strong>Не использовать это в рабочей среде</strong>"
|
||||
prohibitedWordsForNameOfUser: "Запрещенные слова (имя пользователя)"
|
||||
prohibitedWordsForNameOfUserDescription: "Если имя пользователя содержит строку из этого списка, изменение имени пользователя будет запрещено. На пользователей с правами модератора это ограничение не распространяется. Имена пользователей также проверяются путём замены всех букв в нижнем регистре"
|
||||
yourNameContainsProhibitedWords: "Имя, которое вы пытаетесь изменить, содержит запрещенную строку символов"
|
||||
yourNameContainsProhibitedWordsDescription: "Имя содержит запрещённую строку символов. Если вы хотите использовать это имя, обратитесь к администратору сервера"
|
||||
thisContentsAreMarkedAsSigninRequiredByAuthor: "Автор сообщения установил требование в виде авторизации для просмотра"
|
||||
lockdown: "Доступ ограничен"
|
||||
pleaseSelectAccount: "Выберите свой аккаунт"
|
||||
availableRoles: "Доступные роли"
|
||||
federationDisabled: "Федерация отключена для этого сервера. Вы не можете взаимодействовать с пользователями на других серверах."
|
||||
draft: "Черновик"
|
||||
markAsSensitiveConfirm: "Отметить контент как чувствительный?"
|
||||
resetToDefaultValue: "Сбросить настройки до стандартных"
|
||||
postForm: "Форма отправки"
|
||||
information: "Описание"
|
||||
inMinutes: "мин"
|
||||
inDays: "сут"
|
||||
_chat:
|
||||
invitations: "Пригласить"
|
||||
noHistory: "История пока пуста"
|
||||
|
@ -2200,3 +2257,4 @@ _watermarkEditor:
|
|||
image: "Изображения"
|
||||
advanced: "Для продвинутых"
|
||||
angle: "Угол"
|
||||
drafts: "Черновик"
|
||||
|
|
|
@ -913,6 +913,8 @@ flip: "Preklopiť"
|
|||
lastNDays: "Posledných {n} dní"
|
||||
postForm: "Napísať poznámku"
|
||||
information: "Informácie"
|
||||
inMinutes: "min"
|
||||
inDays: "dní"
|
||||
_chat:
|
||||
invitations: "Pozvať"
|
||||
noHistory: "Žiadna história"
|
||||
|
|
|
@ -1368,6 +1368,8 @@ redisplayAllTips: "แสดงคำแนะนำและเคล็ดล
|
|||
hideAllTips: "ซ่อนคำแนะนำและเคล็ดลับทั้งหมด"
|
||||
defaultImageCompressionLevel: "ความละเอียดเริ่มต้นสำหรับการบีบอัดภาพ"
|
||||
defaultImageCompressionLevel_description: "หากตั้งค่าต่ำ จะรักษาคุณภาพภาพได้ดีขึ้นแต่ขนาดไฟล์จะเพิ่มขึ้น<br>หากตั้งค่าสูง จะลดขนาดไฟล์ได้ แต่คุณภาพภาพจะลดลง"
|
||||
inMinutes: "นาที"
|
||||
inDays: "วัน"
|
||||
_order:
|
||||
newest: "เรียงจากใหม่ไปเก่า"
|
||||
oldest: "เรียงจากเก่าไปใหม่"
|
||||
|
|
|
@ -919,6 +919,8 @@ flip: "Перевернути"
|
|||
lastNDays: "Останні {n} днів"
|
||||
postForm: "Створення нотатки"
|
||||
information: "Інформація"
|
||||
inMinutes: "х"
|
||||
inDays: "д"
|
||||
_chat:
|
||||
invitations: "Запросити"
|
||||
noHistory: "Історія порожня"
|
||||
|
|
|
@ -1221,6 +1221,8 @@ information: "Giới thiệu"
|
|||
chat: "Trò chuyện"
|
||||
migrateOldSettings: "Di chuyển cài đặt cũ"
|
||||
migrateOldSettings_description: "Thông thường, quá trình này diễn ra tự động, nhưng nếu vì lý do nào đó mà quá trình di chuyển không thành công, bạn có thể kích hoạt thủ công quy trình di chuyển, quá trình này sẽ ghi đè lên thông tin cấu hình hiện tại của bạn."
|
||||
inMinutes: "phút"
|
||||
inDays: "ngày"
|
||||
_chat:
|
||||
invitations: "Mời"
|
||||
noHistory: "Không có dữ liệu"
|
||||
|
|
|
@ -1318,7 +1318,7 @@ confirmOnReact: "发送回应前需要确认"
|
|||
reactAreYouSure: "要用「{emoji}」进行回应吗?"
|
||||
markAsSensitiveConfirm: "要将此媒体标记为敏感吗?"
|
||||
unmarkAsSensitiveConfirm: "要将此媒体解除敏感标记吗?"
|
||||
preferences: "设置"
|
||||
preferences: "偏好设置"
|
||||
accessibility: "辅助功能"
|
||||
preferencesProfile: "设置的配置"
|
||||
copyPreferenceId: "复制设置 ID"
|
||||
|
@ -1368,6 +1368,8 @@ redisplayAllTips: "重新显示所有的提示和技巧"
|
|||
hideAllTips: "隐藏所有的提示和技巧"
|
||||
defaultImageCompressionLevel: "默认图像压缩等级"
|
||||
defaultImageCompressionLevel_description: "较低的等级可以保持画质,但会增加文件大小。<br>较高的等级可以减少文件大小,但相对应的画质将会降低。"
|
||||
inMinutes: "分"
|
||||
inDays: "日"
|
||||
_order:
|
||||
newest: "从新到旧"
|
||||
oldest: "从旧到新"
|
||||
|
@ -1927,7 +1929,7 @@ _role:
|
|||
name: "角色名称"
|
||||
description: "角色描述"
|
||||
permission: "角色权限"
|
||||
descriptionOfPermission: "<b>监察员</b>可以执行基本地审核操作。\n<b>管理员</b>可以更改服务器的所有设置。"
|
||||
descriptionOfPermission: "<b>监察员</b>可以执行基本的审核操作。\n<b>管理员</b>可以更改实例的所有设置。"
|
||||
assignTarget: "授权对象"
|
||||
descriptionOfAssignTarget: "<b>手动</b>指手动选择谁被包括在这个角色中。\n<b>符合条件</b>指设置条件以自动包括符合条件的用户。"
|
||||
manual: "手动"
|
||||
|
|
|
@ -638,7 +638,7 @@ inboxUrl: "收件夾 URL"
|
|||
addedRelays: "已加入的中繼器"
|
||||
serviceworkerInfo: "如要使用推播通知,需要啟用此選項並設定金鑰。"
|
||||
deletedNote: "已刪除的貼文"
|
||||
invisibleNote: "私人貼文"
|
||||
invisibleNote: "私密的貼文"
|
||||
enableInfiniteScroll: "啟用自動滾動頁面模式"
|
||||
visibility: "可見性"
|
||||
poll: "票選活動"
|
||||
|
@ -1368,6 +1368,8 @@ redisplayAllTips: "重新顯示所有「提示與技巧」"
|
|||
hideAllTips: "隱藏所有「提示與技巧」"
|
||||
defaultImageCompressionLevel: "預設的影像壓縮程度"
|
||||
defaultImageCompressionLevel_description: "低的話可以保留畫質,但是會增加檔案的大小。<br>高的話可以減少檔案大小,但是會降低畫質。"
|
||||
inMinutes: "分鐘"
|
||||
inDays: "日"
|
||||
_order:
|
||||
newest: "最新的在前"
|
||||
oldest: "最舊的在前"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "misskey",
|
||||
"version": "2025.7.0",
|
||||
"version": "2025.8.0-alpha.2",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class RemoteNotesCleaning1753863104203 {
|
||||
name = 'RemoteNotesCleaning1753863104203'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "enableRemoteNotesCleaning" boolean NOT NULL DEFAULT true`);
|
||||
await queryRunner.query('ALTER TABLE "meta" ADD "remoteNotesCleaningMaxProcessingDurationInMinutes" integer NOT NULL DEFAULT \'60\'');
|
||||
await queryRunner.query('ALTER TABLE "meta" ADD "remoteNotesCleaningExpiryDaysForEachNotes" integer NOT NULL DEFAULT \'90\'');
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query('ALTER TABLE "meta" DROP COLUMN "remoteNotesCleaningExpiryDaysForEachNotes"');
|
||||
await queryRunner.query('ALTER TABLE "meta" DROP COLUMN "remoteNotesCleaningMaxProcessingDurationInMinutes"');
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableRemoteNotesCleaning"`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class TweakDefaultFederationSettings1754019326356 {
|
||||
name = 'TweakDefaultFederationSettings1754019326356'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "federation" SET DEFAULT 'none'`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "enableRemoteNotesCleaning" SET DEFAULT false`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "enableRemoteNotesCleaning" SET DEFAULT true`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "federation" SET DEFAULT 'all'`);
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^20.18.1 || ^22.0.0"
|
||||
"node": "^22.15.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node ./built/boot/entry.js",
|
||||
|
|
|
@ -53,6 +53,37 @@ export const QUEUE_TYPES = [
|
|||
'systemWebhookDeliver',
|
||||
] as const;
|
||||
|
||||
const REPEATABLE_SYSTEM_JOB_DEF = [{
|
||||
name: 'tickCharts',
|
||||
pattern: '55 * * * *',
|
||||
}, {
|
||||
name: 'resyncCharts',
|
||||
pattern: '0 0 * * *',
|
||||
}, {
|
||||
name: 'cleanCharts',
|
||||
pattern: '0 0 * * *',
|
||||
}, {
|
||||
name: 'aggregateRetention',
|
||||
pattern: '0 0 * * *',
|
||||
}, {
|
||||
name: 'clean',
|
||||
pattern: '0 0 * * *',
|
||||
}, {
|
||||
name: 'checkExpiredMutings',
|
||||
pattern: '*/5 * * * *',
|
||||
}, {
|
||||
name: 'bakeBufferedReactions',
|
||||
pattern: '0 0 * * *',
|
||||
}, {
|
||||
name: 'checkModeratorsActivity',
|
||||
// 毎時30分に起動
|
||||
pattern: '30 * * * *',
|
||||
}, {
|
||||
name: 'cleanRemoteNotes',
|
||||
// 毎日午前4時に起動(最も人の少ない時間帯)
|
||||
pattern: '0 4 * * *',
|
||||
}];
|
||||
|
||||
@Injectable()
|
||||
export class QueueService {
|
||||
constructor(
|
||||
|
@ -69,85 +100,30 @@ export class QueueService {
|
|||
@Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue,
|
||||
@Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue,
|
||||
) {
|
||||
this.systemQueue.upsertJobScheduler('tickCharts', {
|
||||
pattern: '55 * * * *',
|
||||
}, {
|
||||
name: 'tickCharts',
|
||||
opts: {
|
||||
removeOnComplete: 10,
|
||||
removeOnFail: 30,
|
||||
},
|
||||
});
|
||||
for (const def of REPEATABLE_SYSTEM_JOB_DEF) {
|
||||
this.systemQueue.upsertJobScheduler(def.name, {
|
||||
pattern: def.pattern,
|
||||
}, {
|
||||
name: def.name,
|
||||
opts: {
|
||||
// 期限ではなくcountで設定したいが、ジョブごとではなくキュー全体でカウントされるため、高頻度で実行されるジョブによって低頻度で実行されるジョブのログが消えることになる
|
||||
removeOnComplete: {
|
||||
age: 3600 * 24 * 7, // keep up to 7 days
|
||||
},
|
||||
removeOnFail: {
|
||||
age: 3600 * 24 * 7, // keep up to 7 days
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.systemQueue.upsertJobScheduler('resyncCharts', {
|
||||
pattern: '0 0 * * *',
|
||||
}, {
|
||||
name: 'resyncCharts',
|
||||
opts: {
|
||||
removeOnComplete: 10,
|
||||
removeOnFail: 30,
|
||||
},
|
||||
});
|
||||
|
||||
this.systemQueue.upsertJobScheduler('cleanCharts', {
|
||||
pattern: '0 0 * * *',
|
||||
}, {
|
||||
name: 'cleanCharts',
|
||||
opts: {
|
||||
removeOnComplete: 10,
|
||||
removeOnFail: 30,
|
||||
},
|
||||
});
|
||||
|
||||
this.systemQueue.upsertJobScheduler('aggregateRetention', {
|
||||
pattern: '0 0 * * *',
|
||||
}, {
|
||||
name: 'aggregateRetention',
|
||||
opts: {
|
||||
removeOnComplete: 10,
|
||||
removeOnFail: 30,
|
||||
},
|
||||
});
|
||||
|
||||
this.systemQueue.upsertJobScheduler('clean', {
|
||||
pattern: '0 0 * * *',
|
||||
}, {
|
||||
name: 'clean',
|
||||
opts: {
|
||||
removeOnComplete: 10,
|
||||
removeOnFail: 30,
|
||||
},
|
||||
});
|
||||
|
||||
this.systemQueue.upsertJobScheduler('checkExpiredMutings', {
|
||||
pattern: '*/5 * * * *',
|
||||
}, {
|
||||
name: 'checkExpiredMutings',
|
||||
opts: {
|
||||
removeOnComplete: 10,
|
||||
removeOnFail: 30,
|
||||
},
|
||||
});
|
||||
|
||||
this.systemQueue.upsertJobScheduler('bakeBufferedReactions', {
|
||||
pattern: '0 0 * * *',
|
||||
}, {
|
||||
name: 'bakeBufferedReactions',
|
||||
opts: {
|
||||
removeOnComplete: 10,
|
||||
removeOnFail: 30,
|
||||
},
|
||||
});
|
||||
|
||||
this.systemQueue.upsertJobScheduler('checkModeratorsActivity', {
|
||||
// 毎時30分に起動
|
||||
pattern: '30 * * * *',
|
||||
}, {
|
||||
name: 'checkModeratorsActivity',
|
||||
opts: {
|
||||
removeOnComplete: 10,
|
||||
removeOnFail: 30,
|
||||
},
|
||||
// 古いバージョンで作成され現在使われなくなったrepeatableジョブをクリーンアップ
|
||||
this.systemQueue.getJobSchedulers().then(schedulers => {
|
||||
for (const scheduler of schedulers) {
|
||||
if (!REPEATABLE_SYSTEM_JOB_DEF.some(def => def.name === scheduler.key)) {
|
||||
this.systemQueue.removeJobScheduler(scheduler.key);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -834,6 +810,13 @@ export class QueueService {
|
|||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async queueGetJobLogs(queueType: typeof QUEUE_TYPES[number], jobId: string) {
|
||||
const queue = this.getQueue(queueType);
|
||||
const result = await queue.getJobLogs(jobId);
|
||||
return result.logs;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async queueGetJobs(queueType: typeof QUEUE_TYPES[number], jobTypes: JobType[], search?: string) {
|
||||
const RETURN_LIMIT = 100;
|
||||
|
|
|
@ -227,9 +227,9 @@ export class SearchService {
|
|||
|
||||
if (opts.host) {
|
||||
if (opts.host === '.') {
|
||||
query.andWhere('user.host IS NULL');
|
||||
query.andWhere('note.userHost IS NULL');
|
||||
} else {
|
||||
query.andWhere('user.host = :host', { host: opts.host });
|
||||
query.andWhere('note.userHost = :host', { host: opts.host });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -654,7 +654,7 @@ export class MiMeta {
|
|||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
default: 'all',
|
||||
default: 'none',
|
||||
})
|
||||
public federation: 'all' | 'specified' | 'none';
|
||||
|
||||
|
@ -701,6 +701,21 @@ export class MiMeta {
|
|||
default: true,
|
||||
})
|
||||
public allowExternalApRedirect: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public enableRemoteNotesCleaning: boolean;
|
||||
|
||||
@Column('integer', {
|
||||
default: 60, // minutes
|
||||
})
|
||||
public remoteNotesCleaningMaxProcessingDurationInMinutes: number;
|
||||
|
||||
@Column('integer', {
|
||||
default: 90, // days
|
||||
})
|
||||
public remoteNotesCleaningExpiryDaysForEachNotes: number;
|
||||
}
|
||||
|
||||
export type SoftwareSuspension = {
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { CoreModule } from '@/core/CoreModule.js';
|
||||
import { GlobalModule } from '@/GlobalModule.js';
|
||||
import { CheckModeratorsActivityProcessorService } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
|
||||
import { QueueLoggerService } from './QueueLoggerService.js';
|
||||
import { QueueProcessorService } from './QueueProcessorService.js';
|
||||
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
|
||||
|
@ -18,6 +17,8 @@ import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMu
|
|||
import { BakeBufferedReactionsProcessorService } from './processors/BakeBufferedReactionsProcessorService.js';
|
||||
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
|
||||
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
||||
import { CheckModeratorsActivityProcessorService } from './processors/CheckModeratorsActivityProcessorService.js';
|
||||
import { CleanRemoteNotesProcessorService } from './processors/CleanRemoteNotesProcessorService.js';
|
||||
import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js';
|
||||
import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js';
|
||||
import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js';
|
||||
|
@ -83,6 +84,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
|
|||
AggregateRetentionProcessorService,
|
||||
CheckExpiredMutingsProcessorService,
|
||||
CheckModeratorsActivityProcessorService,
|
||||
CleanRemoteNotesProcessorService,
|
||||
QueueProcessorService,
|
||||
],
|
||||
exports: [
|
||||
|
|
|
@ -43,6 +43,7 @@ import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMu
|
|||
import { BakeBufferedReactionsProcessorService } from './processors/BakeBufferedReactionsProcessorService.js';
|
||||
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
||||
import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
|
||||
import { CleanRemoteNotesProcessorService } from './processors/CleanRemoteNotesProcessorService.js';
|
||||
import { QueueLoggerService } from './QueueLoggerService.js';
|
||||
import { QUEUE, baseWorkerOptions } from './const.js';
|
||||
|
||||
|
@ -123,6 +124,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
private bakeBufferedReactionsProcessorService: BakeBufferedReactionsProcessorService,
|
||||
private checkModeratorsActivityProcessorService: CheckModeratorsActivityProcessorService,
|
||||
private cleanProcessorService: CleanProcessorService,
|
||||
private cleanRemoteNotesProcessorService: CleanRemoteNotesProcessorService,
|
||||
) {
|
||||
this.logger = this.queueLoggerService.logger;
|
||||
|
||||
|
@ -164,6 +166,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
case 'bakeBufferedReactions': return this.bakeBufferedReactionsProcessorService.process();
|
||||
case 'checkModeratorsActivity': return this.checkModeratorsActivityProcessorService.process();
|
||||
case 'clean': return this.cleanProcessorService.process();
|
||||
case 'cleanRemoteNotes': return this.cleanRemoteNotesProcessorService.process(job);
|
||||
default: throw new Error(`unrecognized job type ${job.name} for system`);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { And, In, IsNull, LessThan, MoreThan, Not } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MiMeta, MiNote, NoteFavoritesRepository, NotesRepository, UserNotePiningsRepository } from '@/models/_.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
||||
@Injectable()
|
||||
export class CleanRemoteNotesProcessorService {
|
||||
private logger: Logger;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.meta)
|
||||
private meta: MiMeta,
|
||||
|
||||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
@Inject(DI.noteFavoritesRepository)
|
||||
private noteFavoritesRepository: NoteFavoritesRepository,
|
||||
|
||||
@Inject(DI.userNotePiningsRepository)
|
||||
private userNotePiningsRepository: UserNotePiningsRepository,
|
||||
|
||||
private idService: IdService,
|
||||
private queueLoggerService: QueueLoggerService,
|
||||
) {
|
||||
this.logger = this.queueLoggerService.logger.createSubLogger('clean-remote-notes');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<Record<string, unknown>>): Promise<{
|
||||
deletedCount: number;
|
||||
oldest: number | null;
|
||||
newest: number | null;
|
||||
skipped?: boolean;
|
||||
}> {
|
||||
if (!this.meta.enableRemoteNotesCleaning) {
|
||||
this.logger.info('Remote notes cleaning is disabled, skipping...');
|
||||
return {
|
||||
deletedCount: 0,
|
||||
oldest: null,
|
||||
newest: null,
|
||||
skipped: true,
|
||||
};
|
||||
}
|
||||
|
||||
this.logger.info('cleaning remote notes...');
|
||||
|
||||
const maxDuration = this.meta.remoteNotesCleaningMaxProcessingDurationInMinutes * 60 * 1000; // Convert minutes to milliseconds
|
||||
const startAt = Date.now();
|
||||
|
||||
const MAX_NOTE_COUNT_PER_QUERY = 50;
|
||||
|
||||
const stats = {
|
||||
deletedCount: 0,
|
||||
oldest: null as number | null,
|
||||
newest: null as number | null,
|
||||
};
|
||||
|
||||
let cursor: MiNote['id'] = this.idService.gen(Date.now() - (1000 * 60 * 60 * 24 * this.meta.remoteNotesCleaningExpiryDaysForEachNotes));
|
||||
|
||||
while (true) {
|
||||
const batchBeginAt = Date.now();
|
||||
|
||||
let notes: Pick<MiNote, 'id'>[] = await this.notesRepository.find({
|
||||
where: {
|
||||
id: LessThan(cursor),
|
||||
userHost: Not(IsNull()),
|
||||
clippedCount: 0,
|
||||
renoteCount: 0,
|
||||
},
|
||||
take: MAX_NOTE_COUNT_PER_QUERY,
|
||||
order: {
|
||||
// 新しい順
|
||||
// https://github.com/misskey-dev/misskey/pull/16292#issuecomment-3139376314
|
||||
id: -1,
|
||||
},
|
||||
select: ['id'],
|
||||
});
|
||||
|
||||
const fetchedCount = notes.length;
|
||||
|
||||
for (const note of notes) {
|
||||
if (note.id < cursor) {
|
||||
cursor = note.id;
|
||||
}
|
||||
}
|
||||
|
||||
const pinings = notes.length === 0 ? [] : await this.userNotePiningsRepository.find({
|
||||
where: {
|
||||
noteId: In(notes.map(note => note.id)),
|
||||
},
|
||||
select: ['noteId'],
|
||||
});
|
||||
|
||||
notes = notes.filter(note => {
|
||||
return !pinings.some(pining => pining.noteId === note.id);
|
||||
});
|
||||
|
||||
const favorites = notes.length === 0 ? [] : await this.noteFavoritesRepository.find({
|
||||
where: {
|
||||
noteId: In(notes.map(note => note.id)),
|
||||
},
|
||||
select: ['noteId'],
|
||||
});
|
||||
|
||||
notes = notes.filter(note => {
|
||||
return !favorites.some(favorite => favorite.noteId === note.id);
|
||||
});
|
||||
|
||||
const replies = notes.length === 0 ? [] : await this.notesRepository.find({
|
||||
where: {
|
||||
replyId: In(notes.map(note => note.id)),
|
||||
userHost: IsNull(),
|
||||
},
|
||||
select: ['replyId'],
|
||||
});
|
||||
|
||||
notes = notes.filter(note => {
|
||||
return !replies.some(reply => reply.replyId === note.id);
|
||||
});
|
||||
|
||||
if (notes.length > 0) {
|
||||
await this.notesRepository.delete(notes.map(note => note.id));
|
||||
|
||||
for (const note of notes) {
|
||||
const t = this.idService.parse(note.id).date.getTime();
|
||||
if (stats.oldest === null || t < stats.oldest) {
|
||||
stats.oldest = t;
|
||||
}
|
||||
if (stats.newest === null || t > stats.newest) {
|
||||
stats.newest = t;
|
||||
}
|
||||
}
|
||||
|
||||
stats.deletedCount += notes.length;
|
||||
}
|
||||
|
||||
job.log(`Deleted ${notes.length} of ${fetchedCount}; ${Date.now() - batchBeginAt}ms`);
|
||||
|
||||
const elapsed = Date.now() - startAt;
|
||||
|
||||
if (elapsed >= maxDuration) {
|
||||
this.logger.info(`Reached maximum duration of ${maxDuration}ms, stopping...`);
|
||||
job.log('Reached maximum duration, stopping cleaning.');
|
||||
job.updateProgress(100);
|
||||
break;
|
||||
}
|
||||
|
||||
job.updateProgress((elapsed / maxDuration) * 100);
|
||||
|
||||
await setTimeout(1000 * 5); // Wait a moment to avoid overwhelming the db
|
||||
}
|
||||
|
||||
this.logger.succ('cleaning of remote notes completed.');
|
||||
|
||||
return {
|
||||
deletedCount: stats.deletedCount,
|
||||
oldest: stats.oldest,
|
||||
newest: stats.newest,
|
||||
skipped: false,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -70,6 +70,7 @@ export * as 'admin/queue/inbox-delayed' from './endpoints/admin/queue/inbox-dela
|
|||
export * as 'admin/queue/retry-job' from './endpoints/admin/queue/retry-job.js';
|
||||
export * as 'admin/queue/remove-job' from './endpoints/admin/queue/remove-job.js';
|
||||
export * as 'admin/queue/show-job' from './endpoints/admin/queue/show-job.js';
|
||||
export * as 'admin/queue/show-job-logs' from './endpoints/admin/queue/show-job-logs.js';
|
||||
export * as 'admin/queue/promote-jobs' from './endpoints/admin/queue/promote-jobs.js';
|
||||
export * as 'admin/queue/jobs' from './endpoints/admin/queue/jobs.js';
|
||||
export * as 'admin/queue/stats' from './endpoints/admin/queue/stats.js';
|
||||
|
|
|
@ -571,6 +571,18 @@ export const meta = {
|
|||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
enableRemoteNotesCleaning: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
remoteNotesCleaningExpiryDaysForEachNotes: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
remoteNotesCleaningMaxProcessingDurationInMinutes: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
@ -722,6 +734,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
proxyRemoteFiles: instance.proxyRemoteFiles,
|
||||
signToActivityPubGet: instance.signToActivityPubGet,
|
||||
allowExternalApRedirect: instance.allowExternalApRedirect,
|
||||
enableRemoteNotesCleaning: instance.enableRemoteNotesCleaning,
|
||||
remoteNotesCleaningExpiryDaysForEachNotes: instance.remoteNotesCleaningExpiryDaysForEachNotes,
|
||||
remoteNotesCleaningMaxProcessingDurationInMinutes: instance.remoteNotesCleaningMaxProcessingDurationInMinutes,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:queue',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
optional: false, nullable: false,
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
queue: { type: 'string', enum: QUEUE_TYPES },
|
||||
jobId: { type: 'string' },
|
||||
},
|
||||
required: ['queue', 'jobId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private queueService: QueueService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
return this.queueService.queueGetJobLogs(ps.queue, ps.jobId);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -205,6 +205,9 @@ export const paramDef = {
|
|||
proxyRemoteFiles: { type: 'boolean' },
|
||||
signToActivityPubGet: { type: 'boolean' },
|
||||
allowExternalApRedirect: { type: 'boolean' },
|
||||
enableRemoteNotesCleaning: { type: 'boolean' },
|
||||
remoteNotesCleaningExpiryDaysForEachNotes: { type: 'number' },
|
||||
remoteNotesCleaningMaxProcessingDurationInMinutes: { type: 'number' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
@ -723,6 +726,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
set.allowExternalApRedirect = ps.allowExternalApRedirect;
|
||||
}
|
||||
|
||||
if (ps.enableRemoteNotesCleaning !== undefined) {
|
||||
set.enableRemoteNotesCleaning = ps.enableRemoteNotesCleaning;
|
||||
}
|
||||
|
||||
if (ps.remoteNotesCleaningExpiryDaysForEachNotes !== undefined) {
|
||||
set.remoteNotesCleaningExpiryDaysForEachNotes = ps.remoteNotesCleaningExpiryDaysForEachNotes;
|
||||
}
|
||||
|
||||
if (ps.remoteNotesCleaningMaxProcessingDurationInMinutes !== undefined) {
|
||||
set.remoteNotesCleaningMaxProcessingDurationInMinutes = ps.remoteNotesCleaningMaxProcessingDurationInMinutes;
|
||||
}
|
||||
|
||||
const before = await this.metaService.fetch(true);
|
||||
|
||||
await this.metaService.update(set);
|
||||
|
|
|
@ -194,6 +194,10 @@ export class ClientServerService {
|
|||
],
|
||||
},
|
||||
},
|
||||
'shortcuts': [{
|
||||
'name': 'Safemode',
|
||||
'url': '/?safemode=true',
|
||||
}],
|
||||
};
|
||||
|
||||
manifest = {
|
||||
|
|
|
@ -94,23 +94,37 @@
|
|||
}
|
||||
//#endregion
|
||||
|
||||
//#region Theme
|
||||
const theme = localStorage.getItem('theme');
|
||||
if (theme) {
|
||||
for (const [k, v] of Object.entries(JSON.parse(theme))) {
|
||||
document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
|
||||
let isSafeMode = (localStorage.getItem('isSafeMode') === 'true');
|
||||
|
||||
// HTMLの theme-color 適用
|
||||
if (k === 'htmlThemeColor') {
|
||||
for (const tag of document.head.children) {
|
||||
if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
|
||||
tag.setAttribute('content', v);
|
||||
break;
|
||||
if (!isSafeMode) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
if (urlParams.has('safemode') && urlParams.get('safemode') === 'true') {
|
||||
localStorage.setItem('isSafeMode', 'true');
|
||||
isSafeMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
//#region Theme
|
||||
if (!isSafeMode) {
|
||||
const theme = localStorage.getItem('theme');
|
||||
if (theme) {
|
||||
for (const [k, v] of Object.entries(JSON.parse(theme))) {
|
||||
document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
|
||||
|
||||
// HTMLの theme-color 適用
|
||||
if (k === 'htmlThemeColor') {
|
||||
for (const tag of document.head.children) {
|
||||
if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
|
||||
tag.setAttribute('content', v);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const colorScheme = localStorage.getItem('colorScheme');
|
||||
if (colorScheme) {
|
||||
document.documentElement.style.setProperty('color-scheme', colorScheme);
|
||||
|
@ -127,11 +141,13 @@
|
|||
document.documentElement.classList.add('useSystemFont');
|
||||
}
|
||||
|
||||
const customCss = localStorage.getItem('customCss');
|
||||
if (customCss && customCss.length > 0) {
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = customCss;
|
||||
document.head.appendChild(style);
|
||||
if (!isSafeMode) {
|
||||
const customCss = localStorage.getItem('customCss');
|
||||
if (customCss && customCss.length > 0) {
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = customCss;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
}
|
||||
|
||||
async function addStyle(styleText) {
|
||||
|
@ -159,9 +175,13 @@
|
|||
otherOption1: 'Clear preferences and cache',
|
||||
otherOption2: 'Start the simple client',
|
||||
otherOption3: 'Start the repair tool',
|
||||
otherOption4: 'Start Misskey in safe mode',
|
||||
}, locale?._bootErrors || {});
|
||||
const reload = locale?.reload || 'Reload';
|
||||
|
||||
const safeModeUrl = new URL(window.location.href);
|
||||
safeModeUrl.searchParams.set('safemode', 'true');
|
||||
|
||||
let errorsElement = document.getElementById('errors');
|
||||
|
||||
if (!errorsElement) {
|
||||
|
@ -182,6 +202,12 @@
|
|||
<p>${messages.solution4}</p>
|
||||
<details style="color: #86b300;">
|
||||
<summary>${messages.otherOption}</summary>
|
||||
<a href="${safeModeUrl}">
|
||||
<button class="button-small">
|
||||
<span class="button-label-small">${messages.otherOption4}</span>
|
||||
</button>
|
||||
</a>
|
||||
<br>
|
||||
<a href="/flush">
|
||||
<button class="button-small">
|
||||
<span class="button-label-small">${messages.otherOption1}</span>
|
||||
|
|
|
@ -40,5 +40,11 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "Safemode",
|
||||
"url": "/?safemode=true"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ export const version = _VERSION_;
|
|||
export const instanceName = (siteName === 'Misskey' || siteName == null) ? host : siteName;
|
||||
export const ui = localStorage.getItem('ui');
|
||||
export const debug = localStorage.getItem('debug') === 'true';
|
||||
export const isSafeMode = localStorage.getItem('isSafeMode') === 'true';
|
||||
|
||||
export function updateLocale(newLocale: Locale): void {
|
||||
locale = newLocale;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { computed, watch, version as vueVersion } from 'vue';
|
||||
import { compareVersions } from 'compare-versions';
|
||||
import { version, lang, updateLocale, locale, apiUrl } from '@@/js/config.js';
|
||||
import { version, lang, updateLocale, locale, apiUrl, isSafeMode } from '@@/js/config.js';
|
||||
import defaultLightTheme from '@@/themes/l-light.json5';
|
||||
import defaultDarkTheme from '@@/themes/d-green-lime.json5';
|
||||
import type { App } from 'vue';
|
||||
|
@ -168,28 +168,35 @@ export async function common(createVue: () => Promise<App<Element>>) {
|
|||
|
||||
// NOTE: この処理は必ずクライアント更新チェック処理より後に来ること(テーマ再構築のため)
|
||||
watch(store.r.darkMode, (darkMode) => {
|
||||
applyTheme(darkMode
|
||||
? (prefer.s.darkTheme ?? defaultDarkTheme)
|
||||
: (prefer.s.lightTheme ?? defaultLightTheme),
|
||||
);
|
||||
}, { immediate: miLocalStorage.getItem('theme') == null });
|
||||
const theme = (() => {
|
||||
if (darkMode) {
|
||||
return isSafeMode ? defaultDarkTheme : (prefer.s.darkTheme ?? defaultDarkTheme);
|
||||
} else {
|
||||
return isSafeMode ? defaultLightTheme : (prefer.s.lightTheme ?? defaultLightTheme);
|
||||
}
|
||||
})();
|
||||
|
||||
applyTheme(theme);
|
||||
}, { immediate: isSafeMode || miLocalStorage.getItem('theme') == null });
|
||||
|
||||
window.document.documentElement.dataset.colorScheme = store.s.darkMode ? 'dark' : 'light';
|
||||
|
||||
const darkTheme = prefer.model('darkTheme');
|
||||
const lightTheme = prefer.model('lightTheme');
|
||||
if (!isSafeMode) {
|
||||
const darkTheme = prefer.model('darkTheme');
|
||||
const lightTheme = prefer.model('lightTheme');
|
||||
|
||||
watch(darkTheme, (theme) => {
|
||||
if (store.s.darkMode) {
|
||||
applyTheme(theme ?? defaultDarkTheme);
|
||||
}
|
||||
});
|
||||
watch(darkTheme, (theme) => {
|
||||
if (store.s.darkMode) {
|
||||
applyTheme(theme ?? defaultDarkTheme);
|
||||
}
|
||||
});
|
||||
|
||||
watch(lightTheme, (theme) => {
|
||||
if (!store.s.darkMode) {
|
||||
applyTheme(theme ?? defaultLightTheme);
|
||||
}
|
||||
});
|
||||
watch(lightTheme, (theme) => {
|
||||
if (!store.s.darkMode) {
|
||||
applyTheme(theme ?? defaultLightTheme);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//#region Sync dark mode
|
||||
if (prefer.s.syncDeviceDarkMode) {
|
||||
|
@ -203,17 +210,19 @@ export async function common(createVue: () => Promise<App<Element>>) {
|
|||
});
|
||||
//#endregion
|
||||
|
||||
if (prefer.s.darkTheme && store.s.darkMode) {
|
||||
if (miLocalStorage.getItem('themeId') !== prefer.s.darkTheme.id) applyTheme(prefer.s.darkTheme);
|
||||
} else if (prefer.s.lightTheme && !store.s.darkMode) {
|
||||
if (miLocalStorage.getItem('themeId') !== prefer.s.lightTheme.id) applyTheme(prefer.s.lightTheme);
|
||||
}
|
||||
if (!isSafeMode) {
|
||||
if (prefer.s.darkTheme && store.s.darkMode) {
|
||||
if (miLocalStorage.getItem('themeId') !== prefer.s.darkTheme.id) applyTheme(prefer.s.darkTheme);
|
||||
} else if (prefer.s.lightTheme && !store.s.darkMode) {
|
||||
if (miLocalStorage.getItem('themeId') !== prefer.s.lightTheme.id) applyTheme(prefer.s.lightTheme);
|
||||
}
|
||||
|
||||
fetchInstanceMetaPromise.then(() => {
|
||||
// TODO: instance.defaultLightTheme/instance.defaultDarkThemeが不正な形式だった場合のケア
|
||||
if (prefer.s.lightTheme == null && instance.defaultLightTheme != null) prefer.commit('lightTheme', JSON.parse(instance.defaultLightTheme));
|
||||
if (prefer.s.darkTheme == null && instance.defaultDarkTheme != null) prefer.commit('darkTheme', JSON.parse(instance.defaultDarkTheme));
|
||||
});
|
||||
fetchInstanceMetaPromise.then(() => {
|
||||
// TODO: instance.defaultLightTheme/instance.defaultDarkThemeが不正な形式だった場合のケア
|
||||
if (prefer.s.lightTheme == null && instance.defaultLightTheme != null) prefer.commit('lightTheme', JSON.parse(instance.defaultLightTheme));
|
||||
if (prefer.s.darkTheme == null && instance.defaultDarkTheme != null) prefer.commit('darkTheme', JSON.parse(instance.defaultDarkTheme));
|
||||
});
|
||||
}
|
||||
|
||||
watch(prefer.r.overridedDeviceKind, (kind) => {
|
||||
updateDeviceKind(kind);
|
||||
|
|
|
@ -28,8 +28,8 @@ import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom
|
|||
import { prefer } from '@/preferences.js';
|
||||
import { launchPlugins } from '@/plugin.js';
|
||||
import { updateCurrentAccountPartial } from '@/accounts.js';
|
||||
import { signout } from '@/signout.js';
|
||||
import { migrateOldSettings } from '@/pref-migrate.js';
|
||||
import { unisonReload } from '@/utility/unison-reload.js';
|
||||
|
||||
export async function mainBoot() {
|
||||
const { isClientUpdated, lastVersion } = await common(async () => {
|
||||
|
@ -391,6 +391,8 @@ export async function mainBoot() {
|
|||
}
|
||||
|
||||
// shortcut
|
||||
let safemodeRequestCount = 0;
|
||||
let safemodeRequestTimer: number | null = null;
|
||||
const keymap = {
|
||||
'p|n': () => {
|
||||
if ($i == null) return;
|
||||
|
@ -402,6 +404,24 @@ export async function mainBoot() {
|
|||
's': () => {
|
||||
mainRouter.push('/search');
|
||||
},
|
||||
'g': {
|
||||
callback: () => {
|
||||
// mを5回押すとセーフモードに入る
|
||||
safemodeRequestCount++;
|
||||
if (safemodeRequestCount >= 5) {
|
||||
miLocalStorage.setItem('isSafeMode', 'true');
|
||||
unisonReload();
|
||||
} else {
|
||||
if (safemodeRequestTimer != null) {
|
||||
window.clearTimeout(safemodeRequestTimer);
|
||||
}
|
||||
safemodeRequestTimer = window.setTimeout(() => {
|
||||
safemodeRequestCount = 0;
|
||||
}, 300);
|
||||
}
|
||||
},
|
||||
allowRepeat: true,
|
||||
}
|
||||
} as const satisfies Keymap;
|
||||
window.document.addEventListener('keydown', makeHotkey(keymap), { passive: false });
|
||||
|
||||
|
|
|
@ -145,7 +145,7 @@ import { claimAchievement } from '@/utility/achievements.js';
|
|||
import { prefer } from '@/preferences.js';
|
||||
import { chooseFileFromPcAndUpload, selectDriveFolder } from '@/utility/drive.js';
|
||||
import { store } from '@/store.js';
|
||||
import { isSeparatorNeeded, getSeparatorInfo, makeDateGroupedTimelineComputedRef } from '@/utility/timeline-date-separate.js';
|
||||
import { makeDateGroupedTimelineComputedRef } from '@/utility/timeline-date-separate.js';
|
||||
import { globalEvents, useGlobalEvent } from '@/events.js';
|
||||
import { checkDragDataType, getDragData, setDragData } from '@/drag-and-drop.js';
|
||||
import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js';
|
||||
|
|
|
@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
@contextmenu.stop
|
||||
@keydown.stop
|
||||
>
|
||||
<button v-if="hide" :class="$style.hidden" @click="show">
|
||||
<button v-if="hide" :class="$style.hidden" @click="reveal">
|
||||
<div :class="$style.hiddenTextWrapper">
|
||||
<b v-if="audio.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ prefer.s.dataSaver.media ? ` (${i18n.ts.audio}${audio.size ? ' ' + bytes(audio.size) : ''})` : '' }}</b>
|
||||
<b v-else style="display: block;"><i class="ti ti-music"></i> {{ prefer.s.dataSaver.media && audio.size ? bytes(audio.size) : i18n.ts.audio }}</b>
|
||||
|
@ -157,7 +157,7 @@ const audioEl = useTemplateRef('audioEl');
|
|||
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
|
||||
const hide = ref((prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.audio.isSensitive && prefer.s.nsfw !== 'ignore'));
|
||||
|
||||
async function show() {
|
||||
async function reveal() {
|
||||
if (props.audio.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'question',
|
||||
|
|
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template>
|
||||
<div :class="$style.root">
|
||||
<MkMediaAudio v-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" :audio="media"/>
|
||||
<div v-else-if="media.isSensitive && hide" :class="$style.sensitive" @click="show">
|
||||
<div v-else-if="media.isSensitive && hide" :class="$style.sensitive" @click="reveal">
|
||||
<span style="font-size: 1.6em;"><i class="ti ti-alert-triangle"></i></span>
|
||||
<b>{{ i18n.ts.sensitive }}</b>
|
||||
<span>{{ i18n.ts.clickToShow }}</span>
|
||||
|
@ -37,7 +37,7 @@ const props = defineProps<{
|
|||
|
||||
const hide = ref(true);
|
||||
|
||||
async function show() {
|
||||
async function reveal() {
|
||||
if (props.media.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'question',
|
||||
|
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<div :class="[hide ? $style.hidden : $style.visible, (image.isSensitive && prefer.s.highlightSensitiveMedia) && $style.sensitive]" @click="onclick">
|
||||
<div :class="[hide ? $style.hidden : $style.visible, (image.isSensitive && prefer.s.highlightSensitiveMedia) && $style.sensitive]" @click="reveal">
|
||||
<component
|
||||
:is="disableImageLink ? 'div' : 'a'"
|
||||
v-bind="disableImageLink ? {
|
||||
|
@ -96,10 +96,10 @@ const url = computed(() => (props.raw || prefer.s.loadRawImages)
|
|||
? props.image.url
|
||||
: prefer.s.disableShowingAnimatedImages
|
||||
? getStaticImageUrl(props.image.url)
|
||||
: props.image.thumbnailUrl,
|
||||
: props.image.thumbnailUrl!,
|
||||
);
|
||||
|
||||
async function onclick(ev: MouseEvent) {
|
||||
async function reveal(ev: MouseEvent) {
|
||||
if (!props.controls) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
@contextmenu.stop
|
||||
@keydown.stop
|
||||
>
|
||||
<button v-if="hide" :class="$style.hidden" @click="show">
|
||||
<button v-if="hide" :class="$style.hidden" @click="reveal">
|
||||
<div :class="$style.hiddenTextWrapper">
|
||||
<b v-if="video.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ prefer.s.dataSaver.media ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b>
|
||||
<b v-else style="display: block;"><i class="ti ti-movie"></i> {{ prefer.s.dataSaver.media && video.size ? bytes(video.size) : i18n.ts.video }}</b>
|
||||
|
@ -178,7 +178,7 @@ function hasFocus() {
|
|||
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
|
||||
const hide = ref((prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.video.isSensitive && prefer.s.nsfw !== 'ignore'));
|
||||
|
||||
async function show() {
|
||||
async function reveal() {
|
||||
if (props.video.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'question',
|
||||
|
|
|
@ -10,15 +10,22 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #default="{ items: notes }">
|
||||
<div :class="[$style.root, { [$style.noGap]: noGap, '_gaps': !noGap }]">
|
||||
<template v-for="(note, i) in notes" :key="note.id">
|
||||
<div v-if="i > 0 && isSeparatorNeeded(paginator.items.value[i -1].createdAt, note.createdAt)" :data-scroll-anchor="note.id">
|
||||
<div :class="$style.date">
|
||||
<span><i class="ti ti-chevron-up"></i> {{ getSeparatorInfo(paginator.items.value[i -1].createdAt, note.createdAt).prevText }}</span>
|
||||
<div
|
||||
v-if="i > 0 && isSeparatorNeeded(paginator.items.value[i - 1].createdAt, note.createdAt)"
|
||||
:data-scroll-anchor="note.id"
|
||||
:class="{ '_gaps': !noGap }"
|
||||
>
|
||||
<div :class="[$style.date, { [$style.noGap]: noGap }]">
|
||||
<span><i class="ti ti-chevron-up"></i> {{ getSeparatorInfo(paginator.items.value[i - 1].createdAt, note.createdAt)?.prevText }}</span>
|
||||
<span style="height: 1em; width: 1px; background: var(--MI_THEME-divider);"></span>
|
||||
<span>{{ getSeparatorInfo(paginator.items.value[i -1].createdAt, note.createdAt).nextText }} <i class="ti ti-chevron-down"></i></span>
|
||||
<span>{{ getSeparatorInfo(paginator.items.value[i - 1].createdAt, note.createdAt)?.nextText }} <i class="ti ti-chevron-down"></i></span>
|
||||
</div>
|
||||
<MkNote :class="$style.note" :note="note" :withHardMute="true"/>
|
||||
<div v-if="note._shouldInsertAd_" :class="$style.ad">
|
||||
<MkAd :preferForms="['horizontal', 'horizontal-big']"/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="note._shouldInsertAd_" :class="[$style.noteWithAd, { '_gaps': !noGap }]" :data-scroll-anchor="note.id">
|
||||
<div v-else-if="note._shouldInsertAd_" :class="{ '_gaps': !noGap }" :data-scroll-anchor="note.id">
|
||||
<MkNote :class="$style.note" :note="note" :withHardMute="true"/>
|
||||
<div :class="$style.ad">
|
||||
<MkAd :preferForms="['horizontal', 'horizontal-big']"/>
|
||||
|
@ -103,7 +110,10 @@ defineExpose({
|
|||
opacity: 0.75;
|
||||
padding: 8px 8px;
|
||||
margin: 0 auto;
|
||||
border-bottom: solid 0.5px var(--MI_THEME-divider);
|
||||
|
||||
&.noGap {
|
||||
border-bottom: solid 0.5px var(--MI_THEME-divider);
|
||||
}
|
||||
}
|
||||
|
||||
.ad:empty {
|
||||
|
|
|
@ -55,7 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #icon><i class="ti ti-planet"></i></template>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<div>{{ i18n.ts._serverSetupWizard.doYouConnectToFediverse_description1 }}<br>{{ i18n.ts._serverSetupWizard.doYouConnectToFediverse_description2 }}</div>
|
||||
<div>{{ i18n.ts._serverSetupWizard.doYouConnectToFediverse_description1 }}<br>{{ i18n.ts._serverSetupWizard.doYouConnectToFediverse_description2 }}<br><MkLink target="_blank" url="https://wikipedia.org/wiki/Fediverse">{{ i18n.ts.learnMore }}</MkLink></div>
|
||||
|
||||
<MkRadios v-model="q_federation" :vertical="true">
|
||||
<option value="yes">{{ i18n.ts.yes }}</option>
|
||||
|
@ -63,6 +63,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkRadios>
|
||||
|
||||
<MkInfo v-if="q_federation === 'yes'">{{ i18n.ts._serverSetupWizard.youCanConfigureMoreFederationSettingsLater }}</MkInfo>
|
||||
|
||||
<MkSwitch v-if="q_federation === 'yes'" v-model="q_remoteContentsCleaning">
|
||||
<template #label>{{ i18n.ts._serverSetupWizard.remoteContentsCleaning }}</template>
|
||||
<template #caption>{{ i18n.ts._serverSetupWizard.remoteContentsCleaning_description }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
|
@ -110,6 +115,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div><b>{{ i18n.ts.federation }}:</b></div>
|
||||
<div>{{ serverSettings.federation === 'none' ? i18n.ts.no : i18n.ts.all }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div><b>{{ i18n.ts._serverSettings.remoteNotesCleaning }}:</b></div>
|
||||
<div>{{ serverSettings.enableRemoteNotesCleaning ? i18n.ts.yes : i18n.ts.no }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div><b>FTT:</b></div>
|
||||
<div>{{ serverSettings.enableFanoutTimeline ? i18n.ts.yes : i18n.ts.no }}</div>
|
||||
|
@ -185,7 +194,9 @@ import { misskeyApi } from '@/utility/misskey-api.js';
|
|||
import { i18n } from '@/i18n.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkRadios from '@/components/MkRadios.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkLink from '@/components/MkLink.vue';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'finished'): void;
|
||||
|
@ -200,6 +211,7 @@ const q_name = ref('');
|
|||
const q_use = ref('single');
|
||||
const q_scale = ref('small');
|
||||
const q_federation = ref('yes');
|
||||
const q_remoteContentsCleaning = ref(true);
|
||||
const q_adminName = ref('');
|
||||
const q_adminEmail = ref('');
|
||||
|
||||
|
@ -217,6 +229,7 @@ const serverSettings = computed<Misskey.entities.AdminUpdateMetaRequest>(() => {
|
|||
emailRequiredForSignup: q_use.value === 'open',
|
||||
enableIpLogging: q_use.value === 'open',
|
||||
federation: q_federation.value === 'yes' ? 'all' : 'none',
|
||||
enableRemoteNotesCleaning: q_remoteContentsCleaning.value,
|
||||
enableFanoutTimeline: true,
|
||||
enableFanoutTimelineDbFallback: q_use.value === 'single',
|
||||
enableReactionsBuffering,
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<MkModalWindow
|
||||
ref="windowEl"
|
||||
:withOkButton="false"
|
||||
:okButtonDisabled="false"
|
||||
:width="500"
|
||||
:height="600"
|
||||
@close="onCloseModalWindow"
|
||||
@closed="emit('closed')"
|
||||
>
|
||||
<template #header>Server setup wizard</template>
|
||||
<div class="_spacer" style="--MI_SPACER-min: 20px; --MI_SPACER-max: 28px;">
|
||||
<Suspense>
|
||||
<template #default>
|
||||
<MkServerSetupWizard @finished="onWizardFinished"/>
|
||||
</template>
|
||||
<template #fallback>
|
||||
<MkLoading/>
|
||||
</template>
|
||||
</Suspense>
|
||||
</div>
|
||||
</MkModalWindow>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useTemplateRef } from 'vue';
|
||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||
import MkServerSetupWizard from '@/components/MkServerSetupWizard.vue';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'closed'),
|
||||
}>();
|
||||
|
||||
const windowEl = useTemplateRef('windowEl');
|
||||
|
||||
function onWizardFinished() {
|
||||
windowEl.value?.close();
|
||||
}
|
||||
|
||||
function onCloseModalWindow() {
|
||||
windowEl.value?.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style module lang="scss">
|
||||
.root {
|
||||
max-height: 410px;
|
||||
height: 410px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
|
@ -32,9 +32,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template v-for="(note, i) in paginator.items.value" :key="note.id">
|
||||
<div v-if="i > 0 && isSeparatorNeeded(paginator.items.value[i -1].createdAt, note.createdAt)" :data-scroll-anchor="note.id">
|
||||
<div :class="$style.date">
|
||||
<span><i class="ti ti-chevron-up"></i> {{ getSeparatorInfo(paginator.items.value[i -1].createdAt, note.createdAt).prevText }}</span>
|
||||
<span><i class="ti ti-chevron-up"></i> {{ getSeparatorInfo(paginator.items.value[i -1].createdAt, note.createdAt)?.prevText }}</span>
|
||||
<span style="height: 1em; width: 1px; background: var(--MI_THEME-divider);"></span>
|
||||
<span>{{ getSeparatorInfo(paginator.items.value[i -1].createdAt, note.createdAt).nextText }} <i class="ti ti-chevron-down"></i></span>
|
||||
<span>{{ getSeparatorInfo(paginator.items.value[i -1].createdAt, note.createdAt)?.nextText }} <i class="ti ti-chevron-down"></i></span>
|
||||
</div>
|
||||
<MkNote :class="$style.note" :note="note" :withHardMute="true"/>
|
||||
</div>
|
||||
|
|
|
@ -25,11 +25,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
>
|
||||
<div v-for="(notification, i) in paginator.items.value" :key="notification.id" :data-scroll-anchor="notification.id" :class="$style.item">
|
||||
<div v-if="i > 0 && isSeparatorNeeded(paginator.items.value[i -1].createdAt, notification.createdAt)" :class="$style.date">
|
||||
<span><i class="ti ti-chevron-up"></i> {{ getSeparatorInfo(paginator.items.value[i -1].createdAt, notification.createdAt).prevText }}</span>
|
||||
<span><i class="ti ti-chevron-up"></i> {{ getSeparatorInfo(paginator.items.value[i -1].createdAt, notification.createdAt)?.prevText }}</span>
|
||||
<span style="height: 1em; width: 1px; background: var(--MI_THEME-divider);"></span>
|
||||
<span>{{ getSeparatorInfo(paginator.items.value[i -1].createdAt, notification.createdAt).nextText }} <i class="ti ti-chevron-down"></i></span>
|
||||
<span>{{ getSeparatorInfo(paginator.items.value[i -1].createdAt, notification.createdAt)?.nextText }} <i class="ti ti-chevron-down"></i></span>
|
||||
</div>
|
||||
<MkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :class="$style.content" :note="notification.note" :withHardMute="true"/>
|
||||
<MkNote v-if="['reply', 'quote', 'mention'].includes(notification.type) && 'note' in notification" :class="$style.content" :note="notification.note" :withHardMute="true"/>
|
||||
<XNotification v-else :class="$style.content" :notification="notification" :withTime="true" :full="true"/>
|
||||
</div>
|
||||
</component>
|
||||
|
|
|
@ -33,6 +33,7 @@ export type Keys = (
|
|||
'preferences' |
|
||||
'latestPreferencesUpdate' |
|
||||
'hidePreferencesRestoreSuggestion' |
|
||||
'isSafeMode' |
|
||||
`miux:${string}` |
|
||||
`ui:folder:${string}` |
|
||||
`themes:${string}` | // DEPRECATED
|
||||
|
|
|
@ -98,7 +98,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkKeyValue>
|
||||
<MkKeyValue v-if="job.progress != null && typeof job.progress === 'number' && job.progress > 0">
|
||||
<template #key>Progress</template>
|
||||
<template #value>{{ Math.floor(job.progress * 100) }}%</template>
|
||||
<template #value>{{ Math.floor(job.progress) }}%</template>
|
||||
</MkKeyValue>
|
||||
</div>
|
||||
<MkFolder :withSpacer="false">
|
||||
|
@ -150,11 +150,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkButton><i class="ti ti-device-floppy"></i> Update</MkButton>
|
||||
</div>
|
||||
<div v-else-if="tab === 'result'">
|
||||
<MkCode :code="String(job.returnValue)"/>
|
||||
<MkCode :code="JSON5.stringify(job.returnValue, null, '\t')" lang="json5"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'error'" class="_gaps_s">
|
||||
<MkCode v-for="log in job.stacktrace" :code="log" lang="stacktrace"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'logs'">
|
||||
<MkButton primary rounded @click="loadLogs()"><i class="ti ti-refresh"></i> Load logs</MkButton>
|
||||
<div v-for="log in logs">{{ log }}</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</template>
|
||||
|
||||
|
@ -198,6 +202,7 @@ const emit = defineEmits<{
|
|||
const tab = ref('info');
|
||||
const editData = ref(JSON5.stringify(props.job.data, null, '\t'));
|
||||
const canEdit = true;
|
||||
const logs = ref<string[]>([]);
|
||||
|
||||
type TlType = TlEvent<{
|
||||
type: 'created' | 'processed' | 'finished';
|
||||
|
@ -268,6 +273,10 @@ async function removeJob() {
|
|||
os.apiWithDialog('admin/queue/remove-job', { queue: props.queueType, jobId: props.job.id });
|
||||
}
|
||||
|
||||
async function loadLogs() {
|
||||
logs.value = await os.apiWithDialog('admin/queue/show-job-logs', { queue: props.queueType, jobId: props.job.id });
|
||||
}
|
||||
|
||||
// TODO
|
||||
// function moveJob() {
|
||||
//
|
||||
|
|
|
@ -101,6 +101,35 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkSwitch>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder :defaultOpen="true">
|
||||
<template #icon><i class="ti ti-recycle"></i></template>
|
||||
<template #label>Remote Notes Cleaning (仮)</template>
|
||||
<template v-if="remoteNotesCleaningForm.savedState.enableRemoteNotesCleaning" #suffix>Enabled</template>
|
||||
<template v-else #suffix>Disabled</template>
|
||||
<template v-if="remoteNotesCleaningForm.modified.value" #footer>
|
||||
<MkFormFooter :form="remoteNotesCleaningForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="remoteNotesCleaningForm.state.enableRemoteNotesCleaning">
|
||||
<template #label>{{ i18n.ts.enable }}<span v-if="remoteNotesCleaningForm.modifiedStates.enableRemoteNotesCleaning" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._serverSettings.remoteNotesCleaning_description }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<template v-if="remoteNotesCleaningForm.state.enableRemoteNotesCleaning">
|
||||
<MkInput v-model="remoteNotesCleaningForm.state.remoteNotesCleaningExpiryDaysForEachNotes" type="number">
|
||||
<template #label>{{ i18n.ts._serverSettings.remoteNotesCleaningExpiryDaysForEachNotes }} ({{ i18n.ts.inDays }})<span v-if="remoteNotesCleaningForm.modifiedStates.remoteNotesCleaningExpiryDaysForEachNotes" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #suffix>{{ i18n.ts._time.day }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="remoteNotesCleaningForm.state.remoteNotesCleaningMaxProcessingDurationInMinutes" type="number">
|
||||
<template #label>{{ i18n.ts._serverSettings.remoteNotesCleaningMaxProcessingDuration }} ({{ i18n.ts.inMinutes }})<span v-if="remoteNotesCleaningForm.modifiedStates.remoteNotesCleaningMaxProcessingDurationInMinutes" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #suffix>{{ i18n.ts._time.minute }}</template>
|
||||
</MkInput>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
|
@ -196,6 +225,19 @@ const rbtForm = useForm({
|
|||
fetchInstance(true);
|
||||
});
|
||||
|
||||
const remoteNotesCleaningForm = useForm({
|
||||
enableRemoteNotesCleaning: meta.enableRemoteNotesCleaning,
|
||||
remoteNotesCleaningExpiryDaysForEachNotes: meta.remoteNotesCleaningExpiryDaysForEachNotes,
|
||||
remoteNotesCleaningMaxProcessingDurationInMinutes: meta.remoteNotesCleaningMaxProcessingDurationInMinutes,
|
||||
}, async (state) => {
|
||||
await os.apiWithDialog('admin/update-meta', {
|
||||
enableRemoteNotesCleaning: state.enableRemoteNotesCleaning,
|
||||
remoteNotesCleaningExpiryDaysForEachNotes: state.remoteNotesCleaningExpiryDaysForEachNotes,
|
||||
remoteNotesCleaningMaxProcessingDurationInMinutes: state.remoteNotesCleaningMaxProcessingDurationInMinutes,
|
||||
});
|
||||
fetchInstance(true);
|
||||
});
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
const headerTabs = computed(() => []);
|
||||
|
|
|
@ -287,6 +287,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkTextarea>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkButton primary @click="openSetupWizard">
|
||||
Open setup wizard
|
||||
</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
|
@ -425,6 +429,20 @@ const proxyAccountForm = useForm({
|
|||
fetchInstance(true);
|
||||
});
|
||||
|
||||
async function openSetupWizard() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
title: i18n.ts._serverSettings.restartServerSetupWizardConfirm_title,
|
||||
text: i18n.ts._serverSettings.restartServerSetupWizardConfirm_text,
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkServerSetupWizardDialog.vue').then(x => x.default), {
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
}
|
||||
|
||||
const headerTabs = computed(() => []);
|
||||
|
||||
definePage(() => ({
|
||||
|
|
|
@ -366,6 +366,7 @@ definePage(() => ({
|
|||
|
||||
> .items {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
|
|
|
@ -7,6 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div class="_gaps_m">
|
||||
<FormInfo warn>{{ i18n.ts.customCssWarn }}</FormInfo>
|
||||
|
||||
<FormInfo v-if="isSafeMode" warn>{{ i18n.ts.customCssIsDisabledBecauseSafeMode }}</FormInfo>
|
||||
|
||||
<MkCodeEditor v-model="localCustomCss" manualSave lang="css">
|
||||
<template #label>CSS</template>
|
||||
</MkCodeEditor>
|
||||
|
@ -17,6 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
import { ref, watch, computed } from 'vue';
|
||||
import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
||||
import FormInfo from '@/components/MkInfo.vue';
|
||||
import { isSafeMode } from '@@/js/config.js';
|
||||
import * as os from '@/os.js';
|
||||
import { unisonReload } from '@/utility/unison-reload.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
|
|
@ -10,7 +10,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<SearchKeyword>{{ i18n.ts._settings.pluginBanner }}</SearchKeyword>
|
||||
</MkFeatureBanner>
|
||||
|
||||
<FormLink to="/settings/plugin/install"><template #icon><i class="ti ti-download"></i></template>{{ i18n.ts._plugin.install }}</FormLink>
|
||||
<MkInfo v-if="isSafeMode" warn>{{ i18n.ts.pluginsAreDisabledBecauseSafeMode }}</MkInfo>
|
||||
|
||||
<FormLink v-else to="/settings/plugin/install"><template #icon><i class="ti ti-download"></i></template>{{ i18n.ts._plugin.install }}</FormLink>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.manage }}</template>
|
||||
|
@ -103,10 +105,12 @@ import MkCode from '@/components/MkCode.vue';
|
|||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import { changePluginActive, configPlugin, pluginLogs, uninstallPlugin, reloadPlugin } from '@/plugin.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { isSafeMode } from '@@/js/config.js';
|
||||
import * as os from '@/os.js';
|
||||
|
||||
const plugins = prefer.r.plugins;
|
||||
|
|
|
@ -35,7 +35,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkInfo v-if="isSafeMode" warn>{{ i18n.ts.themeIsDefaultBecauseSafeMode }}</MkInfo>
|
||||
|
||||
<div v-else class="_gaps">
|
||||
<template v-if="!store.r.darkMode.value">
|
||||
<SearchMarker :keywords="['light', 'theme']">
|
||||
<MkFolder :defaultOpen="true" :max-height="500">
|
||||
|
@ -204,12 +206,14 @@ import JSON5 from 'json5';
|
|||
import defaultLightTheme from '@@/themes/l-light.json5';
|
||||
import defaultDarkTheme from '@@/themes/d-green-lime.json5';
|
||||
import type { Theme } from '@/theme.js';
|
||||
import { isSafeMode } from '@@/js/config.js';
|
||||
import * as os from '@/os.js';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkThemePreview from '@/components/MkThemePreview.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import { getBuiltinThemesRef, getThemesRef, removeTheme } from '@/theme.js';
|
||||
import { isDeviceDarkmode } from '@/utility/is-device-darkmode.js';
|
||||
import { store } from '@/store.js';
|
||||
|
|
|
@ -87,7 +87,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div>{{ i18n.ts._serverSetupWizard.settingsYouMakeHereCanBeChangedLater }}</div>
|
||||
</div>
|
||||
|
||||
<MkServerSetupWizard :token="token" @finished="onWizardFinished"/>
|
||||
<Suspense>
|
||||
<template #default>
|
||||
<MkServerSetupWizard :token="token" @finished="onWizardFinished"/>
|
||||
</template>
|
||||
<template #fallback>
|
||||
<MkLoading/>
|
||||
</template>
|
||||
</Suspense>
|
||||
|
||||
<MkButton rounded style="margin: 0 auto;" @click="skipSettings">
|
||||
{{ i18n.ts._serverSetupWizard.skipSettings }}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import { ref, defineAsyncComponent } from 'vue';
|
||||
import { Interpreter, Parser, utils, values } from '@syuilo/aiscript';
|
||||
import { compareVersions } from 'compare-versions';
|
||||
import { isSafeMode } from '@@/js/config.js';
|
||||
import { genId } from '@/utility/id.js';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js';
|
||||
|
@ -232,6 +233,7 @@ export function launchPlugins() {
|
|||
}
|
||||
|
||||
async function launchPlugin(id: Plugin['installId']): Promise<void> {
|
||||
if (isSafeMode) return;
|
||||
const plugin = prefer.s.plugins.find(x => x.installId === id);
|
||||
if (!plugin) return;
|
||||
|
||||
|
|
|
@ -64,12 +64,6 @@ html {
|
|||
}
|
||||
}
|
||||
|
||||
html._themeChangingFallback_ {
|
||||
&, * {
|
||||
transition: background 0.5s ease, border 0.5s ease !important;
|
||||
}
|
||||
}
|
||||
|
||||
html._themeChanging_ {
|
||||
view-transition-name: theme-changing;
|
||||
}
|
||||
|
|
|
@ -137,9 +137,10 @@ export function applyTheme(theme: Theme, persist = true) {
|
|||
}
|
||||
|
||||
if (deepEqual(currentTheme, theme)) return;
|
||||
currentTheme = theme;
|
||||
// リアクティビティ解除
|
||||
currentTheme = deepClone(theme);
|
||||
|
||||
if (window.document.startViewTransition != null && prefer.s.animation) {
|
||||
if (window.document.startViewTransition != null) {
|
||||
window.document.documentElement.classList.add('_themeChanging_');
|
||||
window.document.startViewTransition(async () => {
|
||||
applyThemeInternal(theme, persist);
|
||||
|
@ -150,15 +151,9 @@ export function applyTheme(theme: Theme, persist = true) {
|
|||
globalEvents.emit('themeChanged');
|
||||
});
|
||||
} else {
|
||||
// TODO: ViewTransition API が主要ブラウザで対応したら消す
|
||||
window.document.documentElement.classList.add('_themeChangingFallback_');
|
||||
timeout = window.setTimeout(() => {
|
||||
window.document.documentElement.classList.remove('_themeChangingFallback_');
|
||||
// 色計算など再度行えるようにクライアント全体に通知
|
||||
globalEvents.emit('themeChanged');
|
||||
}, 500);
|
||||
|
||||
applyThemeInternal(theme, persist);
|
||||
// 色計算など再度行えるようにクライアント全体に通知
|
||||
globalEvents.emit('themeChanged');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -94,6 +94,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div v-if="dev" id="devTicker"><span style="animation: dev-ticker-blink 2s infinite;">DEV BUILD</span></div>
|
||||
|
||||
<div v-if="$i && $i.isBot" id="botWarn"><span style="animation: dev-ticker-blink 2s infinite;">{{ i18n.ts.loggedInAsBot }}</span></div>
|
||||
|
||||
<div v-if="isSafeMode" id="safemodeWarn">
|
||||
<span style="animation: dev-ticker-blink 2s infinite;">{{ i18n.ts.safeModeEnabled }}</span>
|
||||
<button class="_textButton" style="pointer-events: all;" @click="exitSafeMode">{{ i18n.ts.turnItOff }}</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -101,7 +106,10 @@ import { defineAsyncComponent, ref, TransitionGroup } from 'vue';
|
|||
import * as Misskey from 'misskey-js';
|
||||
import { swInject } from './sw-inject.js';
|
||||
import XNotification from './notification.vue';
|
||||
import { isSafeMode } from '@@/js/config.js';
|
||||
import { popups } from '@/os.js';
|
||||
import { unisonReload } from '@/utility/unison-reload.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { pendingApiRequestsCount } from '@/utility/misskey-api.js';
|
||||
import * as sound from '@/utility/sound.js';
|
||||
import { $i } from '@/i.js';
|
||||
|
@ -144,6 +152,13 @@ function onNotification(notification: Misskey.entities.Notification, isClient =
|
|||
sound.playMisskeySfx('notification');
|
||||
}
|
||||
|
||||
function exitSafeMode() {
|
||||
miLocalStorage.removeItem('isSafeMode');
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.delete('safemode');
|
||||
unisonReload(url.toString());
|
||||
}
|
||||
|
||||
if ($i) {
|
||||
if (store.s.realtimeMode) {
|
||||
const connection = useStream().useChannel('main');
|
||||
|
@ -396,7 +411,7 @@ if ($i) {
|
|||
width: 100%;
|
||||
height: max-content;
|
||||
text-align: center;
|
||||
z-index: 2147483647;
|
||||
z-index: 2147483646;
|
||||
color: #ff0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
padding: 4px 7px;
|
||||
|
@ -405,6 +420,11 @@ if ($i) {
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
#safemodeWarn {
|
||||
@extend #botWarn;
|
||||
z-index: 2147483647;
|
||||
}
|
||||
|
||||
#devTicker {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
|
|
|
@ -296,6 +296,12 @@ type AdminQueueRemoveJobRequest = operations['admin___queue___remove-job']['requ
|
|||
// @public (undocumented)
|
||||
type AdminQueueRetryJobRequest = operations['admin___queue___retry-job']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type AdminQueueShowJobLogsRequest = operations['admin___queue___show-job-logs']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type AdminQueueShowJobLogsResponse = operations['admin___queue___show-job-logs']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type AdminQueueShowJobRequest = operations['admin___queue___show-job']['requestBody']['content']['application/json'];
|
||||
|
||||
|
@ -1559,6 +1565,8 @@ declare namespace entities {
|
|||
AdminQueueRetryJobRequest,
|
||||
AdminQueueShowJobRequest,
|
||||
AdminQueueShowJobResponse,
|
||||
AdminQueueShowJobLogsRequest,
|
||||
AdminQueueShowJobLogsResponse,
|
||||
AdminQueueStatsResponse,
|
||||
AdminRelaysAddRequest,
|
||||
AdminRelaysAddResponse,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type": "module",
|
||||
"name": "misskey-js",
|
||||
"version": "2025.7.0",
|
||||
"version": "2025.8.0-alpha.2",
|
||||
"description": "Misskey SDK for JavaScript",
|
||||
"license": "MIT",
|
||||
"main": "./built/index.js",
|
||||
|
|
|
@ -713,6 +713,17 @@ declare module '../api.js' {
|
|||
credential?: string | null,
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:admin:queue*
|
||||
*/
|
||||
request<E extends 'admin/queue/show-job-logs', P extends Endpoints[E]['req']>(
|
||||
endpoint: E,
|
||||
params: P,
|
||||
credential?: string | null,
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
|
|
|
@ -88,6 +88,8 @@ import type {
|
|||
AdminQueueRetryJobRequest,
|
||||
AdminQueueShowJobRequest,
|
||||
AdminQueueShowJobResponse,
|
||||
AdminQueueShowJobLogsRequest,
|
||||
AdminQueueShowJobLogsResponse,
|
||||
AdminQueueStatsResponse,
|
||||
AdminRelaysAddRequest,
|
||||
AdminRelaysAddResponse,
|
||||
|
@ -717,6 +719,7 @@ export type Endpoints = {
|
|||
'admin/queue/remove-job': { req: AdminQueueRemoveJobRequest; res: EmptyResponse };
|
||||
'admin/queue/retry-job': { req: AdminQueueRetryJobRequest; res: EmptyResponse };
|
||||
'admin/queue/show-job': { req: AdminQueueShowJobRequest; res: AdminQueueShowJobResponse };
|
||||
'admin/queue/show-job-logs': { req: AdminQueueShowJobLogsRequest; res: AdminQueueShowJobLogsResponse };
|
||||
'admin/queue/stats': { req: EmptyRequest; res: AdminQueueStatsResponse };
|
||||
'admin/relays/add': { req: AdminRelaysAddRequest; res: AdminRelaysAddResponse };
|
||||
'admin/relays/list': { req: EmptyRequest; res: AdminRelaysListResponse };
|
||||
|
|
|
@ -91,6 +91,8 @@ export type AdminQueueRemoveJobRequest = operations['admin___queue___remove-job'
|
|||
export type AdminQueueRetryJobRequest = operations['admin___queue___retry-job']['requestBody']['content']['application/json'];
|
||||
export type AdminQueueShowJobRequest = operations['admin___queue___show-job']['requestBody']['content']['application/json'];
|
||||
export type AdminQueueShowJobResponse = operations['admin___queue___show-job']['responses']['200']['content']['application/json'];
|
||||
export type AdminQueueShowJobLogsRequest = operations['admin___queue___show-job-logs']['requestBody']['content']['application/json'];
|
||||
export type AdminQueueShowJobLogsResponse = operations['admin___queue___show-job-logs']['responses']['200']['content']['application/json'];
|
||||
export type AdminQueueStatsResponse = operations['admin___queue___stats']['responses']['200']['content']['application/json'];
|
||||
export type AdminRelaysAddRequest = operations['admin___relays___add']['requestBody']['content']['application/json'];
|
||||
export type AdminRelaysAddResponse = operations['admin___relays___add']['responses']['200']['content']['application/json'];
|
||||
|
|
|
@ -584,6 +584,15 @@ export type paths = {
|
|||
*/
|
||||
post: operations['admin___queue___show-job'];
|
||||
};
|
||||
'/admin/queue/show-job-logs': {
|
||||
/**
|
||||
* admin/queue/show-job-logs
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:admin:queue*
|
||||
*/
|
||||
post: operations['admin___queue___show-job-logs'];
|
||||
};
|
||||
'/admin/queue/stats': {
|
||||
/**
|
||||
* admin/queue/stats
|
||||
|
@ -9370,6 +9379,9 @@ export interface operations {
|
|||
proxyRemoteFiles: boolean;
|
||||
signToActivityPubGet: boolean;
|
||||
allowExternalApRedirect: boolean;
|
||||
enableRemoteNotesCleaning: boolean;
|
||||
remoteNotesCleaningExpiryDaysForEachNotes: number;
|
||||
remoteNotesCleaningMaxProcessingDurationInMinutes: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -10164,6 +10176,73 @@ export interface operations {
|
|||
};
|
||||
};
|
||||
};
|
||||
'admin___queue___show-job-logs': {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
/** @enum {string} */
|
||||
queue: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
|
||||
jobId: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description OK (with results) */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
'application/json': string[];
|
||||
};
|
||||
};
|
||||
/** @description Client error */
|
||||
400: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description Authentication error */
|
||||
401: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description Forbidden error */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description I'm Ai */
|
||||
418: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description Internal server error */
|
||||
500: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
admin___queue___stats: {
|
||||
responses: {
|
||||
/** @description OK (with results) */
|
||||
|
@ -12599,6 +12678,9 @@ export interface operations {
|
|||
proxyRemoteFiles?: boolean;
|
||||
signToActivityPubGet?: boolean;
|
||||
allowExternalApRedirect?: boolean;
|
||||
enableRemoteNotesCleaning?: boolean;
|
||||
remoteNotesCleaningExpiryDaysForEachNotes?: number;
|
||||
remoteNotesCleaningMaxProcessingDurationInMinutes?: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue