Merge branch 'develop' into suspended-users-note
This commit is contained in:
commit
a81f802da2
|
@ -15,11 +15,7 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
# Chromatic is not likely to be available for fork repositories, so we disable for fork repositories.
|
# Chromatic is not likely to be available for fork repositories, so we disable for fork repositories.
|
||||||
# Neither Dependabot nor Renovate will change the actual behavior for components.
|
if: github.repository == 'misskey-dev/misskey'
|
||||||
if: >-
|
|
||||||
github.repository == 'misskey-dev/misskey' &&
|
|
||||||
startsWith(github.head_ref, 'refs/heads/dependabot/') != true &&
|
|
||||||
startsWith(github.head_ref, 'refs/heads/renovate/') != true
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
|
36
CHANGELOG.md
36
CHANGELOG.md
|
@ -1,14 +1,38 @@
|
||||||
## Unreleased
|
## 2025.4.1
|
||||||
|
|
||||||
### General
|
### General
|
||||||
|
- Feat: bull-boardに代わるジョブキューの管理ツールが実装されました
|
||||||
|
- Feat: アップロード可能な最大ファイルサイズをロールごとに設定可能に
|
||||||
|
- デフォルトで10MBになっています
|
||||||
|
- Enhance: チャットの新規メッセージをプッシュ通知するように
|
||||||
|
- Enhance: 依存関係の更新
|
||||||
|
- Enhance: 翻訳の更新
|
||||||
- Enhance: 凍結されたユーザのノートが各種タイムラインで表示されないように `#15775`
|
- Enhance: 凍結されたユーザのノートが各種タイムラインで表示されないように `#15775`
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
-
|
- Feat: チャットウィジェットを追加
|
||||||
|
- Feat: デッキにチャットカラムを追加
|
||||||
|
- Enhance: Unicode絵文字をslugから入力する際に`:ok:`のように最後の`:`を入力したあとにUnicode絵文字に変換できるように
|
||||||
|
- Enhance: コントロールパネルでジョブキューをクリアできるように
|
||||||
|
- Enhance: テーマでページヘッダーの色を変更できるように
|
||||||
|
- Enhance: スワイプでのタブ切り替えを強化
|
||||||
|
- Enhance: デザインのブラッシュアップ
|
||||||
|
- Fix: ログアウトした際に処理が終了しない問題を修正
|
||||||
|
- Fix: 自動バックアップが設定されている環境でログアウト直前に設定をバックアップするように
|
||||||
|
- Fix: フォルダを開いた状態でメニューからアップロードしてもルートフォルダにアップロードされる問題を修正 #15836
|
||||||
|
- Fix: タイムラインのスクロール位置を記憶するように修正
|
||||||
|
- Fix: ノートの直後のノートを表示する機能で表示が逆順になっていた問題を修正 #15841
|
||||||
|
- Fix: アカウントの移行時にアンテナのフィルターのユーザが更新されない問題を修正 #15843
|
||||||
|
- Fix: タイムラインでノートが重複して表示されることがあるのを修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
-
|
- Enhance: ジョブキューの成功/失敗したジョブも一定数・一定期間保存するようにし、後から問題を調査することを容易に
|
||||||
|
- Enhance: フォローしているユーザーならフォロワー限定投稿のノートでもアンテナで検知できるように
|
||||||
|
(Cherry-picked from https://github.com/yojo-art/cherrypick/pull/568 and https://github.com/team-shahu/misskey/pull/38)
|
||||||
|
- Enhance: ユーザーごとにノートの表示が高速化するように
|
||||||
|
- Fix: システムアカウントの名前がサーバー名と同期されない問題を修正
|
||||||
|
- Fix: 大文字を含むユーザの URL で紹介された場合に 404 エラーを返す問題 #15813
|
||||||
|
- Fix: リードレプリカ設定時にレコードの追加・更新・削除を伴うクエリを発行した際はmasterノードで実行されるように調整( #10897 )
|
||||||
|
|
||||||
## 2025.4.0
|
## 2025.4.0
|
||||||
|
|
||||||
|
@ -42,7 +66,7 @@
|
||||||
- プラグイン、テーマ、クライアントに追加されたすべてのアカウント情報も含まれるようになりました
|
- プラグイン、テーマ、クライアントに追加されたすべてのアカウント情報も含まれるようになりました
|
||||||
- 自動で設定データをサーバーにバックアップできるように
|
- 自動で設定データをサーバーにバックアップできるように
|
||||||
- 設定→設定のプロファイル→自動バックアップ で有効にできます
|
- 設定→設定のプロファイル→自動バックアップ で有効にできます
|
||||||
- 新しいデバイスからログインしたり、ブラウザから設定データが消えてしまったときに自動で復元されます(復元をスキップすることも可能)
|
- ログインしたとき、ブラウザから設定データが消えてしまったときに自動で復元されます(復元をスキップすることも可能)
|
||||||
- 任意の設定項目をデバイス間で同期できるように
|
- 任意の設定項目をデバイス間で同期できるように
|
||||||
- 設定項目の「...」メニュー→「デバイス間で同期」
|
- 設定項目の「...」メニュー→「デバイス間で同期」
|
||||||
- 同期をオンにした際にサーバーに保存された値とローカルの値が競合する場合はどちらを優先するか選択できます
|
- 同期をオンにした際にサーバーに保存された値とローカルの値が競合する場合はどちらを優先するか選択できます
|
||||||
|
@ -51,7 +75,7 @@
|
||||||
- アカウントごとに設定値が分離される設定とそうでないクライアント設定が混在していた(かつ分離するかどうかを設定不可だった)のを、基本的に一律でクライアント全体に適用されるようにし、個別でアカウントごとに異なる設定を行えるように
|
- アカウントごとに設定値が分離される設定とそうでないクライアント設定が混在していた(かつ分離するかどうかを設定不可だった)のを、基本的に一律でクライアント全体に適用されるようにし、個別でアカウントごとに異なる設定を行えるように
|
||||||
- 設定項目の「...」メニュー→「アカウントで上書き」をオンにすることで、設定値をそのアカウントでだけ適用するようにできます
|
- 設定項目の「...」メニュー→「アカウントで上書き」をオンにすることで、設定値をそのアカウントでだけ適用するようにできます
|
||||||
- ログアウトすると設定データもブラウザから消去されるようになりプライバシーが向上しました
|
- ログアウトすると設定データもブラウザから消去されるようになりプライバシーが向上しました
|
||||||
- 再度ログインすればサーバーのバックアップから設定データを復元可能です
|
- バックアップを有効にしている場合、ログインした後にバックアップから設定データを復元可能です
|
||||||
- エクスポートした設定データを他のサーバーでインポートして適用すること(設定の持ち運び)が可能になりました
|
- エクスポートした設定データを他のサーバーでインポートして適用すること(設定の持ち運び)が可能になりました
|
||||||
- 設定情報の移行は自動で行われますが、何らかの理由で失敗した場合、設定→その他→旧設定情報を移行 で再試行可能です
|
- 設定情報の移行は自動で行われますが、何らかの理由で失敗した場合、設定→その他→旧設定情報を移行 で再試行可能です
|
||||||
- 過去に作成されたバックアップデータとは現在互換性がありませんのでご注意ください
|
- 過去に作成されたバックアップデータとは現在互換性がありませんのでご注意ください
|
||||||
|
|
|
@ -280,7 +280,7 @@ featured: "Destacat"
|
||||||
usernameOrUserId: "Nom o ID d'usuari"
|
usernameOrUserId: "Nom o ID d'usuari"
|
||||||
noSuchUser: "No s'ha trobat l'usuari"
|
noSuchUser: "No s'ha trobat l'usuari"
|
||||||
lookup: "Cerca"
|
lookup: "Cerca"
|
||||||
announcements: "Anuncis"
|
announcements: "Avisos"
|
||||||
imageUrl: "URL de la imatge"
|
imageUrl: "URL de la imatge"
|
||||||
remove: "Eliminar"
|
remove: "Eliminar"
|
||||||
removed: "Eliminat"
|
removed: "Eliminat"
|
||||||
|
@ -356,7 +356,7 @@ banner: "Bàner"
|
||||||
displayOfSensitiveMedia: "Visualització de contingut sensible"
|
displayOfSensitiveMedia: "Visualització de contingut sensible"
|
||||||
whenServerDisconnected: "Quan es perdi la connexió al servidor"
|
whenServerDisconnected: "Quan es perdi la connexió al servidor"
|
||||||
disconnectedFromServer: "Desconnectat pel servidor"
|
disconnectedFromServer: "Desconnectat pel servidor"
|
||||||
reload: "Actualitza"
|
reload: "Actualitzar"
|
||||||
doNothing: "Ignora"
|
doNothing: "Ignora"
|
||||||
reloadConfirm: "Vols recarregar?"
|
reloadConfirm: "Vols recarregar?"
|
||||||
watch: "Veure"
|
watch: "Veure"
|
||||||
|
@ -708,7 +708,7 @@ notificationSetting: "Paràmetres de notificacions"
|
||||||
notificationSettingDesc: "Selecciona els tipus de notificacions que es mostraran"
|
notificationSettingDesc: "Selecciona els tipus de notificacions que es mostraran"
|
||||||
useGlobalSetting: "Fer servir la configuració global"
|
useGlobalSetting: "Fer servir la configuració global"
|
||||||
useGlobalSettingDesc: "Si s'activa, es farà servir la configuració de notificacions del teu comte. Si no s'activa es poden fer configuracions individuals."
|
useGlobalSettingDesc: "Si s'activa, es farà servir la configuració de notificacions del teu comte. Si no s'activa es poden fer configuracions individuals."
|
||||||
other: "Altre"
|
other: "Altres"
|
||||||
regenerateLoginToken: "Regenerar clau de seguretat d'inici de sessió"
|
regenerateLoginToken: "Regenerar clau de seguretat d'inici de sessió"
|
||||||
regenerateLoginTokenDescription: "Regenera la clau de seguretat que es fa servir internament durant l'inici de sessió. Normalment aquesta acció no és necessària. Si es regenera es tancarà la sessió a tots els dispositius amb una sessió activa."
|
regenerateLoginTokenDescription: "Regenera la clau de seguretat que es fa servir internament durant l'inici de sessió. Normalment aquesta acció no és necessària. Si es regenera es tancarà la sessió a tots els dispositius amb una sessió activa."
|
||||||
theKeywordWhenSearchingForCustomEmoji: "Cercar un emoji personalitzat "
|
theKeywordWhenSearchingForCustomEmoji: "Cercar un emoji personalitzat "
|
||||||
|
@ -871,7 +871,7 @@ gallery: "Galeria"
|
||||||
recentPosts: "Articles recents"
|
recentPosts: "Articles recents"
|
||||||
popularPosts: "Articles populars"
|
popularPosts: "Articles populars"
|
||||||
shareWithNote: "Comparteix amb una nota"
|
shareWithNote: "Comparteix amb una nota"
|
||||||
ads: "Anuncis"
|
ads: "Publicitat "
|
||||||
expiration: ""
|
expiration: ""
|
||||||
startingperiod: "Inici"
|
startingperiod: "Inici"
|
||||||
memo: "Recordatori"
|
memo: "Recordatori"
|
||||||
|
@ -979,6 +979,7 @@ document: "Documentació"
|
||||||
numberOfPageCache: "Nombre de pàgines a la memòria cau"
|
numberOfPageCache: "Nombre de pàgines a la memòria cau"
|
||||||
numberOfPageCacheDescription: "Incrementant aquest nombre farà que millori l'experiència de l'usuari, però es farà servir més memòria al dispositiu de l'usuari."
|
numberOfPageCacheDescription: "Incrementant aquest nombre farà que millori l'experiència de l'usuari, però es farà servir més memòria al dispositiu de l'usuari."
|
||||||
logoutConfirm: "Vols sortir?"
|
logoutConfirm: "Vols sortir?"
|
||||||
|
logoutWillClearClientData: "En tancar la sessió, la informació del client al navegador s'esborrarà. Per garantir que la informació de configuració es pugui restaurar en tornar a iniciar sessió activa la còpia de seguretat automàtica de la configuració."
|
||||||
lastActiveDate: "Fet servir per última vegada"
|
lastActiveDate: "Fet servir per última vegada"
|
||||||
statusbar: "Barra d'estat"
|
statusbar: "Barra d'estat"
|
||||||
pleaseSelect: "Selecciona una opció"
|
pleaseSelect: "Selecciona una opció"
|
||||||
|
@ -1109,7 +1110,7 @@ accountMigration: "Migració del compte"
|
||||||
accountMoved: "Aquest usuari té un compte nou:"
|
accountMoved: "Aquest usuari té un compte nou:"
|
||||||
accountMovedShort: "Aquest compte ha sigut migrat"
|
accountMovedShort: "Aquest compte ha sigut migrat"
|
||||||
operationForbidden: "Operació no permesa "
|
operationForbidden: "Operació no permesa "
|
||||||
forceShowAds: "Mostra els anuncis sempre "
|
forceShowAds: "Mostrar publicitat sempre "
|
||||||
addMemo: "Afegir recordatori"
|
addMemo: "Afegir recordatori"
|
||||||
editMemo: "Editar recordatori"
|
editMemo: "Editar recordatori"
|
||||||
reactionsList: "Reaccions"
|
reactionsList: "Reaccions"
|
||||||
|
@ -1184,8 +1185,8 @@ iHaveReadXCarefullyAndAgree: "He llegit {x} i estic d'acord."
|
||||||
dialog: "Diàleg "
|
dialog: "Diàleg "
|
||||||
icon: "Icona"
|
icon: "Icona"
|
||||||
forYou: "Per a tu"
|
forYou: "Per a tu"
|
||||||
currentAnnouncements: "Informes actuals"
|
currentAnnouncements: "Avisos actuals"
|
||||||
pastAnnouncements: "Informes passats"
|
pastAnnouncements: "Avisos passats"
|
||||||
youHaveUnreadAnnouncements: "Tens informes per llegir."
|
youHaveUnreadAnnouncements: "Tens informes per llegir."
|
||||||
useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey."
|
useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey."
|
||||||
replies: "Respostes"
|
replies: "Respostes"
|
||||||
|
@ -1334,16 +1335,17 @@ postForm: "Formulari de publicació"
|
||||||
textCount: "Nombre de caràcters "
|
textCount: "Nombre de caràcters "
|
||||||
information: "Informació"
|
information: "Informació"
|
||||||
chat: "Xat"
|
chat: "Xat"
|
||||||
migrateOldSettings: "Migració de la configuració antiga "
|
migrateOldSettings: "Migrar la configuració anterior"
|
||||||
migrateOldSettings_description: "Normalment això es fa automàticament, però si la transició no es fa, el procés es pot iniciar manualment. S'esborrarà la configuració actual."
|
migrateOldSettings_description: "Normalment això es fa automàticament, però si la transició no es fa, el procés es pot iniciar manualment. S'esborrarà la configuració actual."
|
||||||
compress: "Comprimir "
|
compress: "Comprimir "
|
||||||
right: "Dreta"
|
right: "Dreta"
|
||||||
bottom: "A baix "
|
bottom: "A baix "
|
||||||
top: "A dalt "
|
top: "A dalt "
|
||||||
embed: "Incrustar"
|
embed: "Incrustar"
|
||||||
settingsMigrating: "Estem fent la migració de la teva configuració. Si us plau espera un moment... (També pots fer la migració més tard i manualment anant a Preferències → Altres configuracions → Migrar configuració antiga)"
|
settingsMigrating: "Estem migrant la teva configuració. Si us plau espera un moment... (També pots fer la migració més tard, manualment, anant a Preferències → Altres → Migrar configuració antiga)"
|
||||||
readonly: "Només lectura"
|
readonly: "Només lectura"
|
||||||
goToDeck: "Tornar al tauler"
|
goToDeck: "Tornar al tauler"
|
||||||
|
federationJobs: "Treballs sindicats "
|
||||||
_chat:
|
_chat:
|
||||||
noMessagesYet: "Encara no tens missatges "
|
noMessagesYet: "Encara no tens missatges "
|
||||||
newMessage: "Missatge nou"
|
newMessage: "Missatge nou"
|
||||||
|
@ -1359,7 +1361,7 @@ _chat:
|
||||||
noInvitations: "No tens cap invitació "
|
noInvitations: "No tens cap invitació "
|
||||||
history: "Historial de converses "
|
history: "Historial de converses "
|
||||||
noHistory: "No hi ha un registre previ"
|
noHistory: "No hi ha un registre previ"
|
||||||
noRooms: "No hi ha habitacions"
|
noRooms: "No hi ha cap sala"
|
||||||
inviteUser: "Invitar usuaris"
|
inviteUser: "Invitar usuaris"
|
||||||
sentInvitations: "Enviar invitacions"
|
sentInvitations: "Enviar invitacions"
|
||||||
join: "Afegir-se "
|
join: "Afegir-se "
|
||||||
|
@ -1417,8 +1419,8 @@ _settings:
|
||||||
makeEveryTextElementsSelectable_description: "L'activació pot reduir la usabilitat en determinades ocasions."
|
makeEveryTextElementsSelectable_description: "L'activació pot reduir la usabilitat en determinades ocasions."
|
||||||
useStickyIcons: "Utilitza icones fixes"
|
useStickyIcons: "Utilitza icones fixes"
|
||||||
showNavbarSubButtons: "Mostrar sub botons a la barra de navegació "
|
showNavbarSubButtons: "Mostrar sub botons a la barra de navegació "
|
||||||
ifOn: "Quan s'encén "
|
ifOn: "Quan s'activa"
|
||||||
ifOff: "Quan s'apaga "
|
ifOff: "Quan es desactiva"
|
||||||
enableSyncThemesBetweenDevices: "Sincronitzar els temes instal·lats entre dispositius"
|
enableSyncThemesBetweenDevices: "Sincronitzar els temes instal·lats entre dispositius"
|
||||||
_chat:
|
_chat:
|
||||||
showSenderName: "Mostrar el nom del remitent"
|
showSenderName: "Mostrar el nom del remitent"
|
||||||
|
@ -1486,7 +1488,7 @@ _announcement:
|
||||||
needConfirmationToRead: "Es necessita confirmació de lectura de la notificació "
|
needConfirmationToRead: "Es necessita confirmació de lectura de la notificació "
|
||||||
needConfirmationToReadDescription: "Si s'activa es mostrarà un diàleg per confirmar la lectura d'aquesta notificació. A més aquesta notificació serà exclosa de qualsevol funcionalitat com \"Marcar tot com a llegit\"."
|
needConfirmationToReadDescription: "Si s'activa es mostrarà un diàleg per confirmar la lectura d'aquesta notificació. A més aquesta notificació serà exclosa de qualsevol funcionalitat com \"Marcar tot com a llegit\"."
|
||||||
end: "Final de la notificació "
|
end: "Final de la notificació "
|
||||||
tooManyActiveAnnouncementDescription: "Tenir massa notificacions actives pot empitjorar l'experiència de l'usuari. Considera finalitzar els anuncis que siguin antics."
|
tooManyActiveAnnouncementDescription: "Tenir massa notificacions actives pot empitjorar l'experiència de l'usuari. Considera finalitzar els avisos que siguin antics."
|
||||||
readConfirmTitle: "Marcar com llegida?"
|
readConfirmTitle: "Marcar com llegida?"
|
||||||
readConfirmText: "Això marcarà el contingut de \"{title}\" com llegit."
|
readConfirmText: "Això marcarà el contingut de \"{title}\" com llegit."
|
||||||
shouldNotBeUsedToPresentPermanentInfo: "Ja que l'ús de notificacions pot impactar l'experiència dels nous usuaris, és recomanable fer servir les notificacions amb el flux d'informació en comptes de fer-les servir en un únic bloc."
|
shouldNotBeUsedToPresentPermanentInfo: "Ja que l'ús de notificacions pot impactar l'experiència dels nous usuaris, és recomanable fer servir les notificacions amb el flux d'informació en comptes de fer-les servir en un únic bloc."
|
||||||
|
@ -1604,7 +1606,7 @@ _accountMigration:
|
||||||
moveTo: "Migrar aquest compte a un altre"
|
moveTo: "Migrar aquest compte a un altre"
|
||||||
moveToLabel: "Compte al qual es vol migrar:"
|
moveToLabel: "Compte al qual es vol migrar:"
|
||||||
moveCannotBeUndone: "Les migracions dels comptes no es poden desfer."
|
moveCannotBeUndone: "Les migracions dels comptes no es poden desfer."
|
||||||
moveAccountDescription: "Això migrarà la teva compte a un altre diferent.\n ・Els seguidors d'aquest compte és passaran al compte nou de forma automàtica\n ・Es deixaran de seguir a tots els usuaris que es segueixen actualment en aquest compte\n ・No es poden crear notes noves, etc. en aquest compte\n\nSi bé la migració de seguidors es automàtica, has de preparar alguns pasos manualment per migrar la llista d'usuaris que segueixes. Per fer això has d'exportar els seguidors que després importaraes al compte nou mitjançant el menú de configuració. El mateix procediment s'ha de seguir per less teves llistes i els teus usuaris silenciats i bloquejats.\n\n(Aquesta explicació s'aplica a Misskey v13.12.0 i posteriors. Altres aplicacions, com Mastodon, poden funcionar diferent.)"
|
moveAccountDescription: "Això migrarà el teu compte a un altre diferent.\n ・Els seguidors d'aquest compte és passaran al compte nou de forma automàtica\n ・Es deixaran de seguir a tots els usuaris que es segueixen actualment en aquest compte\n ・No es poden crear notes noves, etc. en aquest compte\n\nSi bé la migració de seguidors es automàtica, has de preparar alguns pasos manualment per migrar la llista d'usuaris que segueixes. Per fer això has d'exportar els seguidors que després importaraes al compte nou mitjançant el menú de configuració. El mateix procediment s'ha de seguir per less teves llistes i els teus usuaris silenciats i bloquejats.\n\n(Aquesta explicació s'aplica a Misskey v13.12.0 i posteriors. Altres aplicacions, com Mastodon, poden funcionar diferent.)"
|
||||||
moveAccountHowTo: "Per fer la migració, primer has de crear un àlies per aquest compte al compte al qual vols migrar.\nDesprés de crear l'àlies, introdueix el compte al qual vols migrar amb el format següent: @nomusuari@servidor.exemple.com"
|
moveAccountHowTo: "Per fer la migració, primer has de crear un àlies per aquest compte al compte al qual vols migrar.\nDesprés de crear l'àlies, introdueix el compte al qual vols migrar amb el format següent: @nomusuari@servidor.exemple.com"
|
||||||
startMigration: "Migrar"
|
startMigration: "Migrar"
|
||||||
migrationConfirm: "Vols migrar aquest compte a {account}? Una vegada comenci la migració no es podrà parar O fer marxa enrere i no podràs tornar a fer servir aquest compte mai més."
|
migrationConfirm: "Vols migrar aquest compte a {account}? Una vegada comenci la migració no es podrà parar O fer marxa enrere i no podràs tornar a fer servir aquest compte mai més."
|
||||||
|
@ -1913,6 +1915,7 @@ _role:
|
||||||
canManageCustomEmojis: "Gestiona els emojis personalitzats"
|
canManageCustomEmojis: "Gestiona els emojis personalitzats"
|
||||||
canManageAvatarDecorations: "Gestiona les decoracions dels avatars "
|
canManageAvatarDecorations: "Gestiona les decoracions dels avatars "
|
||||||
driveCapacity: "Capacitat del disc"
|
driveCapacity: "Capacitat del disc"
|
||||||
|
maxFileSize: "Mida màxima de l'arxiu que es pot carregar"
|
||||||
alwaysMarkNsfw: "Marca sempre els fitxers com a sensibles"
|
alwaysMarkNsfw: "Marca sempre els fitxers com a sensibles"
|
||||||
canUpdateBioMedia: "Permet l'edició d'una icona o un bàner"
|
canUpdateBioMedia: "Permet l'edició d'una icona o un bàner"
|
||||||
pinMax: "Nombre màxim de notes fixades"
|
pinMax: "Nombre màxim de notes fixades"
|
||||||
|
@ -1925,7 +1928,7 @@ _role:
|
||||||
userEachUserListsMax: "Nombre màxim d'usuaris dintre d'una llista d'usuaris "
|
userEachUserListsMax: "Nombre màxim d'usuaris dintre d'una llista d'usuaris "
|
||||||
rateLimitFactor: "Limitador"
|
rateLimitFactor: "Limitador"
|
||||||
descriptionOfRateLimitFactor: "Límits baixos són menys restrictius, límits alts són més restrictius."
|
descriptionOfRateLimitFactor: "Límits baixos són menys restrictius, límits alts són més restrictius."
|
||||||
canHideAds: "Pot amagar els anuncis"
|
canHideAds: "Pot amagar la publicitat"
|
||||||
canSearchNotes: "Pot cercar notes"
|
canSearchNotes: "Pot cercar notes"
|
||||||
canUseTranslator: "Pot fer servir el traductor"
|
canUseTranslator: "Pot fer servir el traductor"
|
||||||
avatarDecorationLimit: "Nombre màxim de decoracions que es poden aplicar els avatars"
|
avatarDecorationLimit: "Nombre màxim de decoracions que es poden aplicar els avatars"
|
||||||
|
@ -1990,8 +1993,8 @@ _ad:
|
||||||
reduceFrequencyOfThisAd: "Mostrar menys aquest anunci"
|
reduceFrequencyOfThisAd: "Mostrar menys aquest anunci"
|
||||||
hide: "No mostrar mai"
|
hide: "No mostrar mai"
|
||||||
timezoneinfo: "El dia de la setmana ve determinat del fus horari del servidor."
|
timezoneinfo: "El dia de la setmana ve determinat del fus horari del servidor."
|
||||||
adsSettings: "Configuració d'anuncis "
|
adsSettings: "Configurar la publicitat"
|
||||||
notesPerOneAd: "Interval d'emplaçament d'anuncis en temps real (Notes per anuncis)"
|
notesPerOneAd: "Interval d'emplaçament publicitari en temps real (Notes per anuncis)"
|
||||||
setZeroToDisable: "Ajusta aquest valor a 0 per deshabilitar l'actualització d'anuncis en temps real"
|
setZeroToDisable: "Ajusta aquest valor a 0 per deshabilitar l'actualització d'anuncis en temps real"
|
||||||
adsTooClose: "L'interval actual pot fer que l'experiència de l'usuari sigui dolenta perquè l'interval és molt baix."
|
adsTooClose: "L'interval actual pot fer que l'experiència de l'usuari sigui dolenta perquè l'interval és molt baix."
|
||||||
_forgotPassword:
|
_forgotPassword:
|
||||||
|
@ -2368,6 +2371,7 @@ _widgets:
|
||||||
chooseList: "Tria una llista"
|
chooseList: "Tria una llista"
|
||||||
clicker: "Clicker"
|
clicker: "Clicker"
|
||||||
birthdayFollowings: "Usuaris que fan l'aniversari avui"
|
birthdayFollowings: "Usuaris que fan l'aniversari avui"
|
||||||
|
chat: "Xat"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "Amagar"
|
hide: "Amagar"
|
||||||
show: "Carregar més"
|
show: "Carregar més"
|
||||||
|
@ -2634,6 +2638,7 @@ _deck:
|
||||||
mentions: "Mencions"
|
mentions: "Mencions"
|
||||||
direct: "Publicacions directes"
|
direct: "Publicacions directes"
|
||||||
roleTimeline: "Línia de temps dels rols"
|
roleTimeline: "Línia de temps dels rols"
|
||||||
|
chat: "Xat"
|
||||||
_dialog:
|
_dialog:
|
||||||
charactersExceeded: "Has arribat al màxim de caràcters! Actualment és {current} de {max}"
|
charactersExceeded: "Has arribat al màxim de caràcters! Actualment és {current} de {max}"
|
||||||
charactersBelow: "Ets per sota del mínim de caràcters! Actualment és {current} de {min}"
|
charactersBelow: "Ets per sota del mínim de caràcters! Actualment és {current} de {min}"
|
||||||
|
|
|
@ -979,6 +979,7 @@ document: "Dokumentation"
|
||||||
numberOfPageCache: "Seitencachegröße"
|
numberOfPageCache: "Seitencachegröße"
|
||||||
numberOfPageCacheDescription: "Das Erhöhen dieses Caches führt zu einer angenehmerern Benutzererfahrung, aber erhöht Last und Arbeitsspeicherauslastung auf dem Nutzergerät."
|
numberOfPageCacheDescription: "Das Erhöhen dieses Caches führt zu einer angenehmerern Benutzererfahrung, aber erhöht Last und Arbeitsspeicherauslastung auf dem Nutzergerät."
|
||||||
logoutConfirm: "Wirklich abmelden?"
|
logoutConfirm: "Wirklich abmelden?"
|
||||||
|
logoutWillClearClientData: "Beim Abmelden werden die Konfigurationsdaten des Clients aus dem Browser gelöscht. Um sicherzustellen, dass die Konfigurationsdaten beim erneuten Einloggen wiederhergestellt werden können, aktivieren Sie bitte die automatische Sicherung der Konfiguration."
|
||||||
lastActiveDate: "Zuletzt verwendet am"
|
lastActiveDate: "Zuletzt verwendet am"
|
||||||
statusbar: "Statusleiste"
|
statusbar: "Statusleiste"
|
||||||
pleaseSelect: "Wähle eine Option"
|
pleaseSelect: "Wähle eine Option"
|
||||||
|
@ -1344,6 +1345,7 @@ embed: "Einbetten"
|
||||||
settingsMigrating: "Ihre Einstellungen werden gerade migriert, Bitte warten Sie einen Moment... (Sie können die Einstellungen später auch manuell migrieren, indem Sie zu Einstellungen → Sonstiges → Alte Einstellungen migrieren gehen)"
|
settingsMigrating: "Ihre Einstellungen werden gerade migriert, Bitte warten Sie einen Moment... (Sie können die Einstellungen später auch manuell migrieren, indem Sie zu Einstellungen → Sonstiges → Alte Einstellungen migrieren gehen)"
|
||||||
readonly: "Nur Lesezugriff"
|
readonly: "Nur Lesezugriff"
|
||||||
goToDeck: "Zurück zum Deck"
|
goToDeck: "Zurück zum Deck"
|
||||||
|
federationJobs: "Föderation Jobs"
|
||||||
_chat:
|
_chat:
|
||||||
noMessagesYet: "Noch keine Nachrichten"
|
noMessagesYet: "Noch keine Nachrichten"
|
||||||
newMessage: "Neue Nachricht"
|
newMessage: "Neue Nachricht"
|
||||||
|
@ -1913,6 +1915,7 @@ _role:
|
||||||
canManageCustomEmojis: "Benutzerdefinierte Emojis verwalten"
|
canManageCustomEmojis: "Benutzerdefinierte Emojis verwalten"
|
||||||
canManageAvatarDecorations: "Profilbilddekorationen verwalten"
|
canManageAvatarDecorations: "Profilbilddekorationen verwalten"
|
||||||
driveCapacity: "Drive-Kapazität"
|
driveCapacity: "Drive-Kapazität"
|
||||||
|
maxFileSize: "Maximale Dateigröße, die hochgeladen werden kann"
|
||||||
alwaysMarkNsfw: "Dateien immer als NSFW markieren"
|
alwaysMarkNsfw: "Dateien immer als NSFW markieren"
|
||||||
canUpdateBioMedia: "Kann ein Profil- oder ein Bannerbild bearbeiten"
|
canUpdateBioMedia: "Kann ein Profil- oder ein Bannerbild bearbeiten"
|
||||||
pinMax: "Maximale Anzahl an angehefteten Notizen"
|
pinMax: "Maximale Anzahl an angehefteten Notizen"
|
||||||
|
@ -2368,6 +2371,7 @@ _widgets:
|
||||||
chooseList: "Liste auswählen"
|
chooseList: "Liste auswählen"
|
||||||
clicker: "Klickzähler"
|
clicker: "Klickzähler"
|
||||||
birthdayFollowings: "Nutzer, die heute Geburtstag haben"
|
birthdayFollowings: "Nutzer, die heute Geburtstag haben"
|
||||||
|
chat: "Chat"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "Inhalt verbergen"
|
hide: "Inhalt verbergen"
|
||||||
show: "Inhalt anzeigen"
|
show: "Inhalt anzeigen"
|
||||||
|
@ -2634,6 +2638,7 @@ _deck:
|
||||||
mentions: "Erwähnungen"
|
mentions: "Erwähnungen"
|
||||||
direct: "Direktnachrichten"
|
direct: "Direktnachrichten"
|
||||||
roleTimeline: "Rollenchronik"
|
roleTimeline: "Rollenchronik"
|
||||||
|
chat: "Chat"
|
||||||
_dialog:
|
_dialog:
|
||||||
charactersExceeded: "Maximallänge überschritten! Momentan {current} von {max}"
|
charactersExceeded: "Maximallänge überschritten! Momentan {current} von {max}"
|
||||||
charactersBelow: "Minimallänge unterschritten! Momentan {current} von {min}"
|
charactersBelow: "Minimallänge unterschritten! Momentan {current} von {min}"
|
||||||
|
|
|
@ -978,7 +978,8 @@ deleteAccount: "Delete account"
|
||||||
document: "Documentation"
|
document: "Documentation"
|
||||||
numberOfPageCache: "Number of cached pages"
|
numberOfPageCache: "Number of cached pages"
|
||||||
numberOfPageCacheDescription: "Increasing this number will improve convenience for but cause more load as more memory usage on the user's device."
|
numberOfPageCacheDescription: "Increasing this number will improve convenience for but cause more load as more memory usage on the user's device."
|
||||||
logoutConfirm: "Really log out?"
|
logoutConfirm: "Are you sure you want to log out?"
|
||||||
|
logoutWillClearClientData: "Logging out will erase the settings of the client from the browser. In order to be able to restore the settings upon logging in again, you must enable automatic backup of your settings."
|
||||||
lastActiveDate: "Last used at"
|
lastActiveDate: "Last used at"
|
||||||
statusbar: "Status bar"
|
statusbar: "Status bar"
|
||||||
pleaseSelect: "Select an option"
|
pleaseSelect: "Select an option"
|
||||||
|
@ -1271,7 +1272,7 @@ notUsePleaseLeaveBlank: "Leave blank if not used"
|
||||||
useTotp: "Enter the One-Time Password"
|
useTotp: "Enter the One-Time Password"
|
||||||
useBackupCode: "Use the backup codes"
|
useBackupCode: "Use the backup codes"
|
||||||
launchApp: "Launch the app"
|
launchApp: "Launch the app"
|
||||||
useNativeUIForVideoAudioPlayer: "Use UI of browser when play video and audio"
|
useNativeUIForVideoAudioPlayer: "Use UI of browser when play video and audio\n"
|
||||||
keepOriginalFilename: "Keep original file name"
|
keepOriginalFilename: "Keep original file name"
|
||||||
keepOriginalFilenameDescription: "If you turn off this setting, files names will be replaced with random string automatically when you upload files."
|
keepOriginalFilenameDescription: "If you turn off this setting, files names will be replaced with random string automatically when you upload files."
|
||||||
noDescription: "There is no explanation"
|
noDescription: "There is no explanation"
|
||||||
|
@ -1344,6 +1345,7 @@ embed: "Embed"
|
||||||
settingsMigrating: "Settings are being migrated, please wait a moment... (You can also migrate manually later by going to Settings→Others→Migrate old settings)"
|
settingsMigrating: "Settings are being migrated, please wait a moment... (You can also migrate manually later by going to Settings→Others→Migrate old settings)"
|
||||||
readonly: "Read only"
|
readonly: "Read only"
|
||||||
goToDeck: "Return to Deck"
|
goToDeck: "Return to Deck"
|
||||||
|
federationJobs: "Federation Jobs"
|
||||||
_chat:
|
_chat:
|
||||||
noMessagesYet: "No messages yet"
|
noMessagesYet: "No messages yet"
|
||||||
newMessage: "New message"
|
newMessage: "New message"
|
||||||
|
@ -1913,6 +1915,7 @@ _role:
|
||||||
canManageCustomEmojis: "Can manage custom emojis"
|
canManageCustomEmojis: "Can manage custom emojis"
|
||||||
canManageAvatarDecorations: "Manage avatar decorations"
|
canManageAvatarDecorations: "Manage avatar decorations"
|
||||||
driveCapacity: "Drive capacity"
|
driveCapacity: "Drive capacity"
|
||||||
|
maxFileSize: "Upload-able max file size"
|
||||||
alwaysMarkNsfw: "Always mark files as NSFW"
|
alwaysMarkNsfw: "Always mark files as NSFW"
|
||||||
canUpdateBioMedia: "Can edit an icon or a banner image"
|
canUpdateBioMedia: "Can edit an icon or a banner image"
|
||||||
pinMax: "Maximum number of pinned notes"
|
pinMax: "Maximum number of pinned notes"
|
||||||
|
@ -2368,6 +2371,7 @@ _widgets:
|
||||||
chooseList: "Select a list"
|
chooseList: "Select a list"
|
||||||
clicker: "Clicker"
|
clicker: "Clicker"
|
||||||
birthdayFollowings: "Today's Birthdays"
|
birthdayFollowings: "Today's Birthdays"
|
||||||
|
chat: "Chat"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "Hide"
|
hide: "Hide"
|
||||||
show: "Show content"
|
show: "Show content"
|
||||||
|
@ -2634,6 +2638,7 @@ _deck:
|
||||||
mentions: "Mentions"
|
mentions: "Mentions"
|
||||||
direct: "Direct notes"
|
direct: "Direct notes"
|
||||||
roleTimeline: "Role Timeline"
|
roleTimeline: "Role Timeline"
|
||||||
|
chat: "Chat"
|
||||||
_dialog:
|
_dialog:
|
||||||
charactersExceeded: "You've exceeded the maximum character limit! Currently at {current} of {max}."
|
charactersExceeded: "You've exceeded the maximum character limit! Currently at {current} of {max}."
|
||||||
charactersBelow: "You're below the minimum character limit! Currently at {current} of {min}."
|
charactersBelow: "You're below the minimum character limit! Currently at {current} of {min}."
|
||||||
|
@ -2913,7 +2918,7 @@ _customEmojisManager:
|
||||||
confirmDeleteEmojisDescription: "Delete checked {count} Emoji(s). Are you sure to continue?"
|
confirmDeleteEmojisDescription: "Delete checked {count} Emoji(s). Are you sure to continue?"
|
||||||
confirmResetDescription: ""
|
confirmResetDescription: ""
|
||||||
confirmMovePageDesciption: "Changes have been made to the Emojis on this page.\nIf you leave the page without saving, all changes made on this page will be discarded."
|
confirmMovePageDesciption: "Changes have been made to the Emojis on this page.\nIf you leave the page without saving, all changes made on this page will be discarded."
|
||||||
dialogSelectRoleTitle: "Search by roll set in Emojis"
|
dialogSelectRoleTitle: "Search by role set in Emojis"
|
||||||
_register:
|
_register:
|
||||||
uploadSettingTitle: "Upload settings"
|
uploadSettingTitle: "Upload settings"
|
||||||
uploadSettingDescription: "On this screen, you can configure the behavior when uploading Emojis."
|
uploadSettingDescription: "On this screen, you can configure the behavior when uploading Emojis."
|
||||||
|
|
|
@ -301,6 +301,7 @@ uploadFromUrlMayTakeTime: "Subir el fichero puede tardar un tiempo."
|
||||||
explore: "Explorar"
|
explore: "Explorar"
|
||||||
messageRead: "Ya leído"
|
messageRead: "Ya leído"
|
||||||
noMoreHistory: "El historial se ha acabado"
|
noMoreHistory: "El historial se ha acabado"
|
||||||
|
startChat: "Nuevo Chat"
|
||||||
nUsersRead: "Leído por {n} personas"
|
nUsersRead: "Leído por {n} personas"
|
||||||
agreeTo: "De acuerdo con {0}"
|
agreeTo: "De acuerdo con {0}"
|
||||||
agree: "De acuerdo."
|
agree: "De acuerdo."
|
||||||
|
@ -423,6 +424,7 @@ antennaExcludeBots: "Excluir bots"
|
||||||
antennaKeywordsDescription: "Separar con espacios es una declaración AND, separar con una linea nueva es una declaración OR"
|
antennaKeywordsDescription: "Separar con espacios es una declaración AND, separar con una linea nueva es una declaración OR"
|
||||||
notifyAntenna: "Notificar nueva nota"
|
notifyAntenna: "Notificar nueva nota"
|
||||||
withFileAntenna: "Sólo notas con archivos adjuntados"
|
withFileAntenna: "Sólo notas con archivos adjuntados"
|
||||||
|
excludeNotesInSensitiveChannel: "Excluir notas en canales sensibles"
|
||||||
enableServiceworker: "Activar ServiceWorker"
|
enableServiceworker: "Activar ServiceWorker"
|
||||||
antennaUsersDescription: "Elegir nombres de usuarios separados por una linea nueva"
|
antennaUsersDescription: "Elegir nombres de usuarios separados por una linea nueva"
|
||||||
caseSensitive: "Distinguir mayúsculas de minúsculas"
|
caseSensitive: "Distinguir mayúsculas de minúsculas"
|
||||||
|
@ -694,6 +696,7 @@ userSaysSomethingAbout: "{name} dijo algo sobre {word}"
|
||||||
makeActive: "Activar"
|
makeActive: "Activar"
|
||||||
display: "Apariencia"
|
display: "Apariencia"
|
||||||
copy: "Copiar"
|
copy: "Copiar"
|
||||||
|
copiedToClipboard: "Texto copiado al portapapeles"
|
||||||
metrics: "Métricas"
|
metrics: "Métricas"
|
||||||
overview: "Resumen"
|
overview: "Resumen"
|
||||||
logs: "Registros"
|
logs: "Registros"
|
||||||
|
@ -976,6 +979,7 @@ document: "Documento"
|
||||||
numberOfPageCache: "Cantidad de páginas cacheadas"
|
numberOfPageCache: "Cantidad de páginas cacheadas"
|
||||||
numberOfPageCacheDescription: "Al aumentar el número mejora la conveniencia pero tambien puede aumentar la carga y la memoria a usarse"
|
numberOfPageCacheDescription: "Al aumentar el número mejora la conveniencia pero tambien puede aumentar la carga y la memoria a usarse"
|
||||||
logoutConfirm: "¿Cerrar sesión?"
|
logoutConfirm: "¿Cerrar sesión?"
|
||||||
|
logoutWillClearClientData: "Al cerrar la sesión, la información de configuración del cliente se borra del navegador. Para garantizar que la información de configuración se pueda restaurar al volver a iniciar sesión, active la copia de seguridad automática de la configuración."
|
||||||
lastActiveDate: "Utilizado por última vez el"
|
lastActiveDate: "Utilizado por última vez el"
|
||||||
statusbar: "Barra de estado"
|
statusbar: "Barra de estado"
|
||||||
pleaseSelect: "Selecciona una opción"
|
pleaseSelect: "Selecciona una opción"
|
||||||
|
@ -1071,7 +1075,7 @@ reactionAcceptance: "Aceptación de reacciones"
|
||||||
likeOnly: "Sólo 'me gusta'"
|
likeOnly: "Sólo 'me gusta'"
|
||||||
likeOnlyForRemote: "Sólo reacciones de instancias remotas"
|
likeOnlyForRemote: "Sólo reacciones de instancias remotas"
|
||||||
nonSensitiveOnly: "Solo no sensible"
|
nonSensitiveOnly: "Solo no sensible"
|
||||||
nonSensitiveOnlyForLocalLikeOnlyForRemote: "Sólo no contenido sensible (sólo me gusta en remote)"
|
nonSensitiveOnlyForLocalLikeOnlyForRemote: "Sólo no contenido sensible (sólo me gusta en remoto)"
|
||||||
rolesAssignedToMe: "Roles asignados a mí"
|
rolesAssignedToMe: "Roles asignados a mí"
|
||||||
resetPasswordConfirm: "¿Realmente quieres cambiar la contraseña?"
|
resetPasswordConfirm: "¿Realmente quieres cambiar la contraseña?"
|
||||||
sensitiveWords: "Palabras sensibles"
|
sensitiveWords: "Palabras sensibles"
|
||||||
|
@ -1293,19 +1297,93 @@ passkeyVerificationFailed: "La verificación de la clave de acceso ha fallado."
|
||||||
passkeyVerificationSucceededButPasswordlessLoginDisabled: "La verificación de la clave de acceso ha sido satisfactoria pero se ha deshabilitado el inicio de sesión sin contraseña."
|
passkeyVerificationSucceededButPasswordlessLoginDisabled: "La verificación de la clave de acceso ha sido satisfactoria pero se ha deshabilitado el inicio de sesión sin contraseña."
|
||||||
messageToFollower: "Mensaje a seguidores"
|
messageToFollower: "Mensaje a seguidores"
|
||||||
target: "Para"
|
target: "Para"
|
||||||
|
testCaptchaWarning: "Esta función está pensada para probar CAPTCHAs.<strong>No utilizar en un entorno de producción.</strong>"
|
||||||
|
prohibitedWordsForNameOfUser: "Palabras prohibidas para nombres de usuario"
|
||||||
|
prohibitedWordsForNameOfUserDescription: "Si alguna de las cadenas de esta lista está incluida en el nombre del usuario, el nombre será denegado. Los usuarios con privilegios de moderador no se ven afectados por esta restricción."
|
||||||
|
yourNameContainsProhibitedWords: "Tu nombre contiene palabras prohibidas"
|
||||||
|
yourNameContainsProhibitedWordsDescription: "Si deseas usar este nombre, por favor contacta con tu administrador/a de tu servidor"
|
||||||
|
thisContentsAreMarkedAsSigninRequiredByAuthor: " Establecido por el autor: requiere iniciar sesión para ver"
|
||||||
|
lockdown: "Bloqueo"
|
||||||
|
pleaseSelectAccount: "Seleccione una cuenta, por favor."
|
||||||
|
availableRoles: "Roles disponibles "
|
||||||
|
acknowledgeNotesAndEnable: "Activar después de comprender las precauciones"
|
||||||
federationSpecified: "Este servidor opera en una federación de listas blancas. No puede interactuar con otros servidores que no sean los especificados por el administrador."
|
federationSpecified: "Este servidor opera en una federación de listas blancas. No puede interactuar con otros servidores que no sean los especificados por el administrador."
|
||||||
federationDisabled: "La federación está desactivada en este servidor. No puede interactuar con usuarios de otros servidores"
|
federationDisabled: "La federación está desactivada en este servidor. No puede interactuar con usuarios de otros servidores"
|
||||||
|
confirmOnReact: "Confirmar la reacción"
|
||||||
|
reactAreYouSure: "¿Quieres añadir una reacción «{emoji}»?"
|
||||||
|
markAsSensitiveConfirm: "¿Desea establecer este medio multimedia(Imagen,vídeo...) como sensible?"
|
||||||
|
unmarkAsSensitiveConfirm: "¿Desea eliminar la designación de sensible para este adjunto?"
|
||||||
preferences: "Preferencias"
|
preferences: "Preferencias"
|
||||||
|
accessibility: "Accesibilidad"
|
||||||
|
preferencesProfile: "Configuración del perfil"
|
||||||
|
copyPreferenceId: "Copiar ID de la configuración"
|
||||||
|
resetToDefaultValue: "Revertir a valor predeterminado"
|
||||||
|
overrideByAccount: "Anulado por la cuenta"
|
||||||
|
untitled: "Sin título"
|
||||||
|
noName: "No hay nombre."
|
||||||
|
skip: "Saltar"
|
||||||
|
restore: "Restaurar"
|
||||||
|
syncBetweenDevices: "Sincronizar entre dispositivos"
|
||||||
|
preferenceSyncConflictTitle: "Los valores configurados existen en el servidor."
|
||||||
|
preferenceSyncConflictText: "Los ajustes de sincronización activados guardarán sus valores en el servidor. Sin embargo, hay valores existentes en el servidor. ¿Qué conjunto de valores desea sobrescribir?"
|
||||||
|
emojiPalette: "Paleta emoji"
|
||||||
postForm: "Formulario"
|
postForm: "Formulario"
|
||||||
information: "Información"
|
information: "Información"
|
||||||
|
chat: "Chat"
|
||||||
|
migrateOldSettings: "Migrar la configuración anterior"
|
||||||
|
right: "Derecha"
|
||||||
|
bottom: "Abajo"
|
||||||
|
top: "Arriba"
|
||||||
|
embed: "Insertar"
|
||||||
|
settingsMigrating: "La configuración está siendo migrada, por favor espera un momento... (También puedes migrar manualmente más tarde yendo a Ajustes otros migrar configuración antigua"
|
||||||
|
readonly: "Solo Lectura"
|
||||||
_chat:
|
_chat:
|
||||||
|
noMessagesYet: "Aún no hay mensajes"
|
||||||
|
newMessage: "Mensajes nuevos"
|
||||||
|
individualChat: "Chat individual"
|
||||||
|
individualChat_description: "Mantén una conversación privada con otra persona."
|
||||||
invitations: "Invitar"
|
invitations: "Invitar"
|
||||||
noHistory: "No hay datos en el historial"
|
noHistory: "No hay datos en el historial"
|
||||||
members: "Miembros"
|
members: "Miembros"
|
||||||
home: "Inicio"
|
home: "Inicio"
|
||||||
send: "Enviar"
|
send: "Enviar"
|
||||||
|
chatNotAvailableInOtherAccount: "La función de chat está desactivada para el otro usuario."
|
||||||
|
cannotChatWithTheUser: "No se puede iniciar un chat con este usuario"
|
||||||
|
cannotChatWithTheUser_description: "El chat no está disponible o la otra parte no ha habilitado el chat."
|
||||||
|
chatWithThisUser: "Chatear"
|
||||||
|
thisUserAllowsChatOnlyFromFollowers: "Este usuario sólo acepta chats de seguidores."
|
||||||
|
thisUserAllowsChatOnlyFromFollowing: "Este usuario sólo acepta chats de los usuarios a los que sigue."
|
||||||
|
thisUserAllowsChatOnlyFromMutualFollowing: "Este usuario sólo acepta chats de usuarios que son seguidores mutuos."
|
||||||
|
thisUserNotAllowedChatAnyone: "Este usuario no acepta chats de nadie."
|
||||||
|
chatAllowedUsers: "A quién permitir chatear."
|
||||||
|
chatAllowedUsers_note: "Puedes chatear con cualquier persona a la que hayas enviado un mensaje de chat, independientemente de esta configuración."
|
||||||
|
_chatAllowedUsers:
|
||||||
|
everyone: "Todos"
|
||||||
|
followers: "Sólo sus propios seguidores."
|
||||||
|
following: "Solo usuarios que sigues"
|
||||||
|
mutual: "Solo seguidores mutuos"
|
||||||
|
none: "Nadie"
|
||||||
|
_emojiPalette:
|
||||||
|
palettes: "Paleta\n"
|
||||||
_settings:
|
_settings:
|
||||||
|
api: "API"
|
||||||
webhook: "Webhook"
|
webhook: "Webhook"
|
||||||
|
makeEveryTextElementsSelectable_description: "Activar esta opción puede reducir la usabilidad en algunas situaciones."
|
||||||
|
useStickyIcons: "Hacer que los iconos te sigan cuando desplaces"
|
||||||
|
showNavbarSubButtons: "Mostrar los sub-botones en la barra de navegación."
|
||||||
|
ifOn: "Si está activado"
|
||||||
|
enableSyncThemesBetweenDevices: "Sincronizar los temas instalados entre dispositivos."
|
||||||
|
_chat:
|
||||||
|
showSenderName: "Mostrar el nombre del remitente"
|
||||||
|
sendOnEnter: "Intro para enviar"
|
||||||
|
_preferencesProfile:
|
||||||
|
profileName: "Nombre de perfil"
|
||||||
|
profileNameDescription: "Establece un nombre que identifique al dispositivo"
|
||||||
|
profileNameDescription2: "Por ejemplo: \"PC Principal\",\"Teléfono\""
|
||||||
|
_preferencesBackup:
|
||||||
|
autoBackup: "Respaldo automático"
|
||||||
|
restoreFromBackup: "Restaurar desde copia de seguridad"
|
||||||
|
noBackupsFoundTitle: "No se encontró una copia de seguridad"
|
||||||
_accountSettings:
|
_accountSettings:
|
||||||
requireSigninToViewContents: "Se requiere iniciar sesión para ver el contenido"
|
requireSigninToViewContents: "Se requiere iniciar sesión para ver el contenido"
|
||||||
requireSigninToViewContentsDescription1: "Requiere iniciar sesión para ver todas las notas y otros contenidos que hayas creado. Se espera que esto evite que los rastreadores recopilen información."
|
requireSigninToViewContentsDescription1: "Requiere iniciar sesión para ver todas las notas y otros contenidos que hayas creado. Se espera que esto evite que los rastreadores recopilen información."
|
||||||
|
@ -2196,6 +2274,7 @@ _widgets:
|
||||||
chooseList: "Seleccione una lista"
|
chooseList: "Seleccione una lista"
|
||||||
clicker: "Cliqueador"
|
clicker: "Cliqueador"
|
||||||
birthdayFollowings: "Hoy cumplen años"
|
birthdayFollowings: "Hoy cumplen años"
|
||||||
|
chat: "Chat"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "Ocultar"
|
hide: "Ocultar"
|
||||||
show: "Ver más"
|
show: "Ver más"
|
||||||
|
@ -2443,6 +2522,7 @@ _deck:
|
||||||
mentions: "Menciones"
|
mentions: "Menciones"
|
||||||
direct: "Notas directas"
|
direct: "Notas directas"
|
||||||
roleTimeline: "Linea de tiempo del rol"
|
roleTimeline: "Linea de tiempo del rol"
|
||||||
|
chat: "Chat"
|
||||||
_dialog:
|
_dialog:
|
||||||
charactersExceeded: "¡Has excedido el límite de caracteres! Actualmente {current} de {max}."
|
charactersExceeded: "¡Has excedido el límite de caracteres! Actualmente {current} de {max}."
|
||||||
charactersBelow: "¡Estás por debajo del límite de caracteres! Actualmente {current} de {min}."
|
charactersBelow: "¡Estás por debajo del límite de caracteres! Actualmente {current} de {min}."
|
||||||
|
|
|
@ -3934,6 +3934,10 @@ export interface Locale extends ILocale {
|
||||||
* ログアウトしますか?
|
* ログアウトしますか?
|
||||||
*/
|
*/
|
||||||
"logoutConfirm": string;
|
"logoutConfirm": string;
|
||||||
|
/**
|
||||||
|
* ログアウトするとクライアントの設定情報がブラウザから消去されます。再ログイン時に設定情報を復元できるようにするためには、設定の自動バックアップを有効にしてください。
|
||||||
|
*/
|
||||||
|
"logoutWillClearClientData": string;
|
||||||
/**
|
/**
|
||||||
* 最終利用日時
|
* 最終利用日時
|
||||||
*/
|
*/
|
||||||
|
@ -5394,6 +5398,10 @@ export interface Locale extends ILocale {
|
||||||
* デッキへ戻る
|
* デッキへ戻る
|
||||||
*/
|
*/
|
||||||
"goToDeck": string;
|
"goToDeck": string;
|
||||||
|
/**
|
||||||
|
* 連合ジョブ
|
||||||
|
*/
|
||||||
|
"federationJobs": string;
|
||||||
"_chat": {
|
"_chat": {
|
||||||
/**
|
/**
|
||||||
* まだメッセージはありません
|
* まだメッセージはありません
|
||||||
|
@ -7456,6 +7464,10 @@ export interface Locale extends ILocale {
|
||||||
* ドライブ容量
|
* ドライブ容量
|
||||||
*/
|
*/
|
||||||
"driveCapacity": string;
|
"driveCapacity": string;
|
||||||
|
/**
|
||||||
|
* アップロード可能な最大ファイルサイズ
|
||||||
|
*/
|
||||||
|
"maxFileSize": string;
|
||||||
/**
|
/**
|
||||||
* ファイルにNSFWを常に付与
|
* ファイルにNSFWを常に付与
|
||||||
*/
|
*/
|
||||||
|
@ -9203,6 +9215,10 @@ export interface Locale extends ILocale {
|
||||||
* 今日誕生日のユーザー
|
* 今日誕生日のユーザー
|
||||||
*/
|
*/
|
||||||
"birthdayFollowings": string;
|
"birthdayFollowings": string;
|
||||||
|
/**
|
||||||
|
* チャット
|
||||||
|
*/
|
||||||
|
"chat": string;
|
||||||
};
|
};
|
||||||
"_cw": {
|
"_cw": {
|
||||||
/**
|
/**
|
||||||
|
@ -10226,6 +10242,10 @@ export interface Locale extends ILocale {
|
||||||
* ロールタイムライン
|
* ロールタイムライン
|
||||||
*/
|
*/
|
||||||
"roleTimeline": string;
|
"roleTimeline": string;
|
||||||
|
/**
|
||||||
|
* チャット
|
||||||
|
*/
|
||||||
|
"chat": string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
"_dialog": {
|
"_dialog": {
|
||||||
|
|
|
@ -382,7 +382,7 @@ disconnectService: "Disconnetti"
|
||||||
enableLocalTimeline: "Abilita la timeline locale"
|
enableLocalTimeline: "Abilita la timeline locale"
|
||||||
enableGlobalTimeline: "Abilita la timeline federata"
|
enableGlobalTimeline: "Abilita la timeline federata"
|
||||||
disablingTimelinesInfo: "Anche disabilitandole, gli Amministratori e i Moderatori potranno comunque accedervi."
|
disablingTimelinesInfo: "Anche disabilitandole, gli Amministratori e i Moderatori potranno comunque accedervi."
|
||||||
registration: "Iscriviti"
|
registration: "Registrazione"
|
||||||
invite: "Invita"
|
invite: "Invita"
|
||||||
driveCapacityPerLocalAccount: "Capienza del Drive per profilo locale"
|
driveCapacityPerLocalAccount: "Capienza del Drive per profilo locale"
|
||||||
driveCapacityPerRemoteAccount: "Capienza del Drive per profilo remoto"
|
driveCapacityPerRemoteAccount: "Capienza del Drive per profilo remoto"
|
||||||
|
@ -424,6 +424,7 @@ antennaExcludeBots: "Escludere i Bot"
|
||||||
antennaKeywordsDescription: "Sparando con uno spazio indichi la condizione E (and). Separando con un a capo, indichi la condizione O (or)."
|
antennaKeywordsDescription: "Sparando con uno spazio indichi la condizione E (and). Separando con un a capo, indichi la condizione O (or)."
|
||||||
notifyAntenna: "Invia notifiche delle nuove note"
|
notifyAntenna: "Invia notifiche delle nuove note"
|
||||||
withFileAntenna: "Solo note con file in allegato"
|
withFileAntenna: "Solo note con file in allegato"
|
||||||
|
excludeNotesInSensitiveChannel: "Escludere le Note dai canali espliciti"
|
||||||
enableServiceworker: "Abilita ServiceWorker"
|
enableServiceworker: "Abilita ServiceWorker"
|
||||||
antennaUsersDescription: "Elenca un nome utente per riga"
|
antennaUsersDescription: "Elenca un nome utente per riga"
|
||||||
caseSensitive: "Sensibile alla distinzione tra maiuscole e minuscole"
|
caseSensitive: "Sensibile alla distinzione tra maiuscole e minuscole"
|
||||||
|
@ -727,7 +728,7 @@ reporterOrigin: "Segnalazione da"
|
||||||
send: "Inviare"
|
send: "Inviare"
|
||||||
openInNewTab: "Apri in una nuova scheda"
|
openInNewTab: "Apri in una nuova scheda"
|
||||||
openInSideView: "Apri in vista laterale"
|
openInSideView: "Apri in vista laterale"
|
||||||
defaultNavigationBehaviour: "Navigazione preimpostata"
|
defaultNavigationBehaviour: "Tipo di navigazione predefinita"
|
||||||
editTheseSettingsMayBreakAccount: "Modificare queste impostazioni può danneggiare il profilo"
|
editTheseSettingsMayBreakAccount: "Modificare queste impostazioni può danneggiare il profilo"
|
||||||
instanceTicker: "Informazioni sull'istanza da cui vengono le note"
|
instanceTicker: "Informazioni sull'istanza da cui vengono le note"
|
||||||
waitingFor: "Aspettando {x}"
|
waitingFor: "Aspettando {x}"
|
||||||
|
@ -866,7 +867,7 @@ noBotProtectionWarning: "Non è stata impostata alcuna protezione dai Bot"
|
||||||
configure: "Imposta"
|
configure: "Imposta"
|
||||||
postToGallery: "Pubblicare nella galleria"
|
postToGallery: "Pubblicare nella galleria"
|
||||||
postToHashtag: "Pubblica a questo hashtag"
|
postToHashtag: "Pubblica a questo hashtag"
|
||||||
gallery: "Galleria"
|
gallery: "Gallerie"
|
||||||
recentPosts: "Pubblicazioni recenti"
|
recentPosts: "Pubblicazioni recenti"
|
||||||
popularPosts: "Le più visualizzate"
|
popularPosts: "Le più visualizzate"
|
||||||
shareWithNote: "Condividere in nota"
|
shareWithNote: "Condividere in nota"
|
||||||
|
@ -978,6 +979,7 @@ document: "Documentazione"
|
||||||
numberOfPageCache: "Numero di pagine cache"
|
numberOfPageCache: "Numero di pagine cache"
|
||||||
numberOfPageCacheDescription: "Aumenta l'usabilità, ma aumenta anche il carico e l'utilizzo della memoria."
|
numberOfPageCacheDescription: "Aumenta l'usabilità, ma aumenta anche il carico e l'utilizzo della memoria."
|
||||||
logoutConfirm: "Vuoi davvero uscire da Misskey? "
|
logoutConfirm: "Vuoi davvero uscire da Misskey? "
|
||||||
|
logoutWillClearClientData: "All'uscita, la configurazione del client viene rimossa dal browser. Per ripristinarla quando si effettua nuovamente l'accesso, abilitare il backup automatico."
|
||||||
lastActiveDate: "Data dell'ultimo utilizzo"
|
lastActiveDate: "Data dell'ultimo utilizzo"
|
||||||
statusbar: "Barra di stato"
|
statusbar: "Barra di stato"
|
||||||
pleaseSelect: "Scegli un'opzione"
|
pleaseSelect: "Scegli un'opzione"
|
||||||
|
@ -1340,6 +1342,9 @@ right: "Destra"
|
||||||
bottom: "Sotto"
|
bottom: "Sotto"
|
||||||
top: "Sopra"
|
top: "Sopra"
|
||||||
embed: "Incorporare"
|
embed: "Incorporare"
|
||||||
|
settingsMigrating: "Migrazione delle impostazioni. Attendere prego ... (Puoi anche migrare manualmente in un secondo momento, nel menu: Impostazioni → Altro → Migrazione delle impostazioni)"
|
||||||
|
readonly: "Sola lettura"
|
||||||
|
goToDeck: "Torna al Deck"
|
||||||
_chat:
|
_chat:
|
||||||
noMessagesYet: "Ancora nessun messaggio"
|
noMessagesYet: "Ancora nessun messaggio"
|
||||||
newMessage: "Nuovo messaggio"
|
newMessage: "Nuovo messaggio"
|
||||||
|
@ -1369,6 +1374,7 @@ _chat:
|
||||||
muteThisRoom: "Silenzia stanza"
|
muteThisRoom: "Silenzia stanza"
|
||||||
deleteRoom: "Elimina stanza"
|
deleteRoom: "Elimina stanza"
|
||||||
chatNotAvailableForThisAccountOrServer: "Questo server, o questo profilo ha disabilitato la chat."
|
chatNotAvailableForThisAccountOrServer: "Questo server, o questo profilo ha disabilitato la chat."
|
||||||
|
chatIsReadOnlyForThisAccountOrServer: "Le chat, su questo server o su questo profilo, sono di sola lettura. Impossibile scrivere in chat o creare e partecipare a stanze."
|
||||||
chatNotAvailableInOtherAccount: "La chat non è disponibile nel profilo dell'altra persona."
|
chatNotAvailableInOtherAccount: "La chat non è disponibile nel profilo dell'altra persona."
|
||||||
cannotChatWithTheUser: "Impossibile chattare con questa persona"
|
cannotChatWithTheUser: "Impossibile chattare con questa persona"
|
||||||
cannotChatWithTheUser_description: "La chat potrebbe non essere disponibile, oppure l'altra persona potrebbe non esserlo."
|
cannotChatWithTheUser_description: "La chat potrebbe non essere disponibile, oppure l'altra persona potrebbe non esserlo."
|
||||||
|
@ -1929,6 +1935,7 @@ _role:
|
||||||
canImportFollowing: "Può importare Following"
|
canImportFollowing: "Può importare Following"
|
||||||
canImportMuting: "Può importare Silenziati"
|
canImportMuting: "Può importare Silenziati"
|
||||||
canImportUserLists: "Può importare liste di Profili"
|
canImportUserLists: "Può importare liste di Profili"
|
||||||
|
chatAvailability: "Chat consentita"
|
||||||
_condition:
|
_condition:
|
||||||
roleAssignedTo: "Assegnato a ruoli manualmente"
|
roleAssignedTo: "Assegnato a ruoli manualmente"
|
||||||
isLocal: "Profilo locale"
|
isLocal: "Profilo locale"
|
||||||
|
@ -2362,6 +2369,7 @@ _widgets:
|
||||||
chooseList: "Seleziona una lista"
|
chooseList: "Seleziona una lista"
|
||||||
clicker: "Cliccheria"
|
clicker: "Cliccheria"
|
||||||
birthdayFollowings: "Compleanni del giorno"
|
birthdayFollowings: "Compleanni del giorno"
|
||||||
|
chat: "Chat"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "Nascondere"
|
hide: "Nascondere"
|
||||||
show: "Continua la lettura..."
|
show: "Continua la lettura..."
|
||||||
|
@ -2594,8 +2602,8 @@ _notification:
|
||||||
renote: "Rinota"
|
renote: "Rinota"
|
||||||
_deck:
|
_deck:
|
||||||
alwaysShowMainColumn: "Mostra sempre la colonna principale"
|
alwaysShowMainColumn: "Mostra sempre la colonna principale"
|
||||||
columnAlign: "Allineare colonne"
|
columnAlign: "Allineamento delle colonne"
|
||||||
columnGap: "Margine tra le colonne"
|
columnGap: "Spessore del margine tra colonne"
|
||||||
deckMenuPosition: "Posizione del menu Deck"
|
deckMenuPosition: "Posizione del menu Deck"
|
||||||
navbarPosition: "Posizione barra di navigazione"
|
navbarPosition: "Posizione barra di navigazione"
|
||||||
addColumn: "Aggiungi colonna"
|
addColumn: "Aggiungi colonna"
|
||||||
|
@ -2624,10 +2632,11 @@ _deck:
|
||||||
tl: "Timeline"
|
tl: "Timeline"
|
||||||
antenna: "Antenne"
|
antenna: "Antenne"
|
||||||
list: "Liste"
|
list: "Liste"
|
||||||
channel: "Canale"
|
channel: "Canali"
|
||||||
mentions: "Menzioni"
|
mentions: "Menzioni"
|
||||||
direct: "Note Dirette"
|
direct: "Note Dirette"
|
||||||
roleTimeline: "Timeline Ruolo"
|
roleTimeline: "Timeline Ruolo"
|
||||||
|
chat: "Chat"
|
||||||
_dialog:
|
_dialog:
|
||||||
charactersExceeded: "Hai superato il limite di {max} caratteri! ({current})"
|
charactersExceeded: "Hai superato il limite di {max} caratteri! ({current})"
|
||||||
charactersBelow: "Sei al di sotto del minimo di {min} caratteri! ({current})"
|
charactersBelow: "Sei al di sotto del minimo di {min} caratteri! ({current})"
|
||||||
|
|
|
@ -979,6 +979,7 @@ document: "ドキュメント"
|
||||||
numberOfPageCache: "ページキャッシュ数"
|
numberOfPageCache: "ページキャッシュ数"
|
||||||
numberOfPageCacheDescription: "多くすると利便性が向上しますが、負荷とメモリ使用量が増えます。"
|
numberOfPageCacheDescription: "多くすると利便性が向上しますが、負荷とメモリ使用量が増えます。"
|
||||||
logoutConfirm: "ログアウトしますか?"
|
logoutConfirm: "ログアウトしますか?"
|
||||||
|
logoutWillClearClientData: "ログアウトするとクライアントの設定情報がブラウザから消去されます。再ログイン時に設定情報を復元できるようにするためには、設定の自動バックアップを有効にしてください。"
|
||||||
lastActiveDate: "最終利用日時"
|
lastActiveDate: "最終利用日時"
|
||||||
statusbar: "ステータスバー"
|
statusbar: "ステータスバー"
|
||||||
pleaseSelect: "選択してください"
|
pleaseSelect: "選択してください"
|
||||||
|
@ -1344,6 +1345,7 @@ embed: "埋め込み"
|
||||||
settingsMigrating: "設定を移行しています。しばらくお待ちください... (後ほど、設定→その他→旧設定情報を移行 で手動で移行することもできます)"
|
settingsMigrating: "設定を移行しています。しばらくお待ちください... (後ほど、設定→その他→旧設定情報を移行 で手動で移行することもできます)"
|
||||||
readonly: "読み取り専用"
|
readonly: "読み取り専用"
|
||||||
goToDeck: "デッキへ戻る"
|
goToDeck: "デッキへ戻る"
|
||||||
|
federationJobs: "連合ジョブ"
|
||||||
|
|
||||||
_chat:
|
_chat:
|
||||||
noMessagesYet: "まだメッセージはありません"
|
noMessagesYet: "まだメッセージはありません"
|
||||||
|
@ -1932,6 +1934,7 @@ _role:
|
||||||
canManageCustomEmojis: "カスタム絵文字の管理"
|
canManageCustomEmojis: "カスタム絵文字の管理"
|
||||||
canManageAvatarDecorations: "アバターデコレーションの管理"
|
canManageAvatarDecorations: "アバターデコレーションの管理"
|
||||||
driveCapacity: "ドライブ容量"
|
driveCapacity: "ドライブ容量"
|
||||||
|
maxFileSize: "アップロード可能な最大ファイルサイズ"
|
||||||
alwaysMarkNsfw: "ファイルにNSFWを常に付与"
|
alwaysMarkNsfw: "ファイルにNSFWを常に付与"
|
||||||
canUpdateBioMedia: "アイコンとバナーの更新を許可"
|
canUpdateBioMedia: "アイコンとバナーの更新を許可"
|
||||||
pinMax: "ノートのピン留めの最大数"
|
pinMax: "ノートのピン留めの最大数"
|
||||||
|
@ -2420,6 +2423,7 @@ _widgets:
|
||||||
chooseList: "リストを選択"
|
chooseList: "リストを選択"
|
||||||
clicker: "クリッカー"
|
clicker: "クリッカー"
|
||||||
birthdayFollowings: "今日誕生日のユーザー"
|
birthdayFollowings: "今日誕生日のユーザー"
|
||||||
|
chat: "チャット"
|
||||||
|
|
||||||
_cw:
|
_cw:
|
||||||
hide: "隠す"
|
hide: "隠す"
|
||||||
|
@ -2704,6 +2708,7 @@ _deck:
|
||||||
mentions: "あなた宛て"
|
mentions: "あなた宛て"
|
||||||
direct: "ダイレクト"
|
direct: "ダイレクト"
|
||||||
roleTimeline: "ロールタイムライン"
|
roleTimeline: "ロールタイムライン"
|
||||||
|
chat: "チャット"
|
||||||
|
|
||||||
_dialog:
|
_dialog:
|
||||||
charactersExceeded: "最大文字数を超えています! 現在 {current} / 制限 {max}"
|
charactersExceeded: "最大文字数を超えています! 現在 {current} / 制限 {max}"
|
||||||
|
|
|
@ -489,7 +489,7 @@ next: "다음"
|
||||||
retype: "다시 입력"
|
retype: "다시 입력"
|
||||||
noteOf: "{user}의 노트"
|
noteOf: "{user}의 노트"
|
||||||
quoteAttached: "인용함"
|
quoteAttached: "인용함"
|
||||||
quoteQuestion: "인용해서 작성하시겠습니까?"
|
quoteQuestion: "인용해서 첨부하시겠습니까?"
|
||||||
attachAsFileQuestion: "붙여넣으려는 글이 너무 깁니다. 텍스트 파일로 첨부하시겠습니까?"
|
attachAsFileQuestion: "붙여넣으려는 글이 너무 깁니다. 텍스트 파일로 첨부하시겠습니까?"
|
||||||
onlyOneFileCanBeAttached: "메시지에 첨부할 수 있는 파일은 하나까지입니다"
|
onlyOneFileCanBeAttached: "메시지에 첨부할 수 있는 파일은 하나까지입니다"
|
||||||
signinRequired: "진행하기 전에 로그인을 해 주세요"
|
signinRequired: "진행하기 전에 로그인을 해 주세요"
|
||||||
|
@ -571,8 +571,8 @@ objectStorageSetPublicRead: "업로드할 때 'public-read'를 설정하기"
|
||||||
s3ForcePathStyleDesc: "s3ForcePathStyle을 활성화하면, 버킷 이름을 URL의 호스트명이 아닌 경로의 일부로써 취급합니다. 셀프 호스트 Minio와 같은 서비스를 사용할 경우 활성화해야 할 수 있습니다."
|
s3ForcePathStyleDesc: "s3ForcePathStyle을 활성화하면, 버킷 이름을 URL의 호스트명이 아닌 경로의 일부로써 취급합니다. 셀프 호스트 Minio와 같은 서비스를 사용할 경우 활성화해야 할 수 있습니다."
|
||||||
serverLogs: "서버 로그"
|
serverLogs: "서버 로그"
|
||||||
deleteAll: "모두 삭제"
|
deleteAll: "모두 삭제"
|
||||||
showFixedPostForm: "타임라인 상단에 글 작성란을 표시"
|
showFixedPostForm: "타임라인 상단에 글 입력란을 표시"
|
||||||
showFixedPostFormInChannel: "채널 타임라인 상단에 글 작성란을 표시"
|
showFixedPostFormInChannel: "채널 타임라인 상단에 글 입력란을 표시"
|
||||||
withRepliesByDefaultForNewlyFollowed: "팔로우 할 때 기본적으로 답글을 타임라인에 나오게 하기"
|
withRepliesByDefaultForNewlyFollowed: "팔로우 할 때 기본적으로 답글을 타임라인에 나오게 하기"
|
||||||
newNoteRecived: "새 노트가 있습니다"
|
newNoteRecived: "새 노트가 있습니다"
|
||||||
sounds: "소리"
|
sounds: "소리"
|
||||||
|
@ -720,7 +720,7 @@ abuseReports: "신고"
|
||||||
reportAbuse: "신고"
|
reportAbuse: "신고"
|
||||||
reportAbuseRenote: "리노트 신고하기"
|
reportAbuseRenote: "리노트 신고하기"
|
||||||
reportAbuseOf: "{name} 신고하기"
|
reportAbuseOf: "{name} 신고하기"
|
||||||
fillAbuseReportDescription: "신고하려는 이유를 자세히 알려주세요. 특정 게시물을 신고할 때에는 게시물의 URL도 포함해 주세요."
|
fillAbuseReportDescription: "신고 사유를 자세히 기재해 주세요. 대상 노트나 페이지 등이 있는 경우에는 해당 URL도 기재해 주세요."
|
||||||
abuseReported: "신고를 보냈습니다. 신고해 주셔서 감사합니다."
|
abuseReported: "신고를 보냈습니다. 신고해 주셔서 감사합니다."
|
||||||
reporter: "신고자"
|
reporter: "신고자"
|
||||||
reporteeOrigin: "피신고자"
|
reporteeOrigin: "피신고자"
|
||||||
|
@ -825,7 +825,7 @@ editCode: "코드 수정"
|
||||||
apply: "적용"
|
apply: "적용"
|
||||||
receiveAnnouncementFromInstance: "이 서버의 알림을 이메일로 수신할게요"
|
receiveAnnouncementFromInstance: "이 서버의 알림을 이메일로 수신할게요"
|
||||||
emailNotification: "메일 알림"
|
emailNotification: "메일 알림"
|
||||||
publish: "게시"
|
publish: "공개"
|
||||||
inChannelSearch: "채널에서 검색"
|
inChannelSearch: "채널에서 검색"
|
||||||
useReactionPickerForContextMenu: "우클릭하여 리액션 선택기 열기"
|
useReactionPickerForContextMenu: "우클릭하여 리액션 선택기 열기"
|
||||||
typingUsers: "{users}님이 입력 중"
|
typingUsers: "{users}님이 입력 중"
|
||||||
|
@ -979,6 +979,7 @@ document: "문서"
|
||||||
numberOfPageCache: "페이지 캐시 수"
|
numberOfPageCache: "페이지 캐시 수"
|
||||||
numberOfPageCacheDescription: "숫자가 클 수록 편리성이 높아지지만, 시스템 자원과 메모리를 더 많이 사용합니다."
|
numberOfPageCacheDescription: "숫자가 클 수록 편리성이 높아지지만, 시스템 자원과 메모리를 더 많이 사용합니다."
|
||||||
logoutConfirm: "로그아웃 하시겠습니까?"
|
logoutConfirm: "로그아웃 하시겠습니까?"
|
||||||
|
logoutWillClearClientData: "로그아웃하면 클라이언트의 설정 데이터가 브라우저에서 지워지게 됩니다. 다시 로그인할 때 설정 데이터를 복원할 수 있도록 하려면 설정 자동 백업을 활성화하세요."
|
||||||
lastActiveDate: "마지막 이용"
|
lastActiveDate: "마지막 이용"
|
||||||
statusbar: "상태바"
|
statusbar: "상태바"
|
||||||
pleaseSelect: "선택해 주세요"
|
pleaseSelect: "선택해 주세요"
|
||||||
|
@ -1081,7 +1082,7 @@ sensitiveWords: "민감한 단어"
|
||||||
sensitiveWordsDescription: "설정한 단어가 포함된 노트의 공개 범위를 '홈'으로 강제합니다. 개행으로 구분하여 여러 개를 지정할 수 있습니다."
|
sensitiveWordsDescription: "설정한 단어가 포함된 노트의 공개 범위를 '홈'으로 강제합니다. 개행으로 구분하여 여러 개를 지정할 수 있습니다."
|
||||||
sensitiveWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
|
sensitiveWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
|
||||||
prohibitedWords: "금지 단어"
|
prohibitedWords: "금지 단어"
|
||||||
prohibitedWordsDescription: "설정된 단어가 포함되는 노트를 작성하려고 하면, 오류가 발생하도록 합니다. 줄바꿈으로 구분지어 복수 설정할 수 있습니다."
|
prohibitedWordsDescription: "설정된 단어가 포함되는 노트를 게시하려고 하면, 오류가 발생하도록 합니다. 줄바꿈으로 구분지어 복수 설정할 수 있습니다."
|
||||||
prohibitedWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
|
prohibitedWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
|
||||||
hiddenTags: "숨긴 해시태그"
|
hiddenTags: "숨긴 해시태그"
|
||||||
hiddenTagsDescription: "설정한 태그를 트렌드에 표시하지 않도록 합니다. 줄 바꿈으로 하나씩 나눠서 설정할 수 있습니다."
|
hiddenTagsDescription: "설정한 태그를 트렌드에 표시하지 않도록 합니다. 줄 바꿈으로 하나씩 나눠서 설정할 수 있습니다."
|
||||||
|
@ -1344,6 +1345,7 @@ embed: "임베드"
|
||||||
settingsMigrating: "설정을 이전하는 중입니다. 잠시 기다려주십시오... (나중에 '환경설정 → 기타 → 기존 설정 정보를 이전'에서 수동으로 이전할 수도 있습니다)"
|
settingsMigrating: "설정을 이전하는 중입니다. 잠시 기다려주십시오... (나중에 '환경설정 → 기타 → 기존 설정 정보를 이전'에서 수동으로 이전할 수도 있습니다)"
|
||||||
readonly: "읽기 전용"
|
readonly: "읽기 전용"
|
||||||
goToDeck: "덱으로 돌아가기"
|
goToDeck: "덱으로 돌아가기"
|
||||||
|
federationJobs: "연합 작업"
|
||||||
_chat:
|
_chat:
|
||||||
noMessagesYet: "아직 메시지가 없습니다"
|
noMessagesYet: "아직 메시지가 없습니다"
|
||||||
newMessage: "새로운 메시지"
|
newMessage: "새로운 메시지"
|
||||||
|
@ -1373,7 +1375,7 @@ _chat:
|
||||||
muteThisRoom: "이 룸을 뮤트"
|
muteThisRoom: "이 룸을 뮤트"
|
||||||
deleteRoom: "룸을 삭제"
|
deleteRoom: "룸을 삭제"
|
||||||
chatNotAvailableForThisAccountOrServer: "이 서버 또는 이 계정에서 채팅이 활성화되어 있지 않습니다."
|
chatNotAvailableForThisAccountOrServer: "이 서버 또는 이 계정에서 채팅이 활성화되어 있지 않습니다."
|
||||||
chatIsReadOnlyForThisAccountOrServer: "이 서버 또는 이 계정에서 채팅은 읽기 전용입니다. 새로 쓰거나 채팅방을 만들거나 참가할 수 없습니다."
|
chatIsReadOnlyForThisAccountOrServer: "이 서버 또는 이 계정에서 채팅은 읽기 전용입니다. 새로 쓰거나 채팅 룸을 만들거나 참가할 수 없습니다."
|
||||||
chatNotAvailableInOtherAccount: "상대방 계정에서 채팅 기능을 사용할 수 없는 상태입니다."
|
chatNotAvailableInOtherAccount: "상대방 계정에서 채팅 기능을 사용할 수 없는 상태입니다."
|
||||||
cannotChatWithTheUser: "이 유저와 채팅을 시작할 수 없습니다"
|
cannotChatWithTheUser: "이 유저와 채팅을 시작할 수 없습니다"
|
||||||
cannotChatWithTheUser_description: "채팅을 사용할 수 없는 상태이거나 상대방이 채팅을 열지 않은 상태입니다."
|
cannotChatWithTheUser_description: "채팅을 사용할 수 없는 상태이거나 상대방이 채팅을 열지 않은 상태입니다."
|
||||||
|
@ -1542,7 +1544,7 @@ _initialTutorial:
|
||||||
description3: "이 외에도, '리스트 타임라인'이나 '채널 타임라인' 등이 있습니다. 자세한 사항은 {link}에서 확인하실 수 있습니다."
|
description3: "이 외에도, '리스트 타임라인'이나 '채널 타임라인' 등이 있습니다. 자세한 사항은 {link}에서 확인하실 수 있습니다."
|
||||||
_postNote:
|
_postNote:
|
||||||
title: "노트 게시 설정"
|
title: "노트 게시 설정"
|
||||||
description1: "Misskey에 노트를 쓸 때에는 다양한 옵션을 설정할 수 있습니다. 노트를 작성하는 화면은 이렇게 생겼습니다."
|
description1: "Misskey에 노트를 게시할 때에는 다양한 옵션 설정이 가능합니다. 노트를 게시할 때 쓰이는 '글 입력란'은 이렇게 생겼습니다."
|
||||||
_visibility:
|
_visibility:
|
||||||
description: "노트를 볼 수 있는 사람을 제한할 수 있습니다."
|
description: "노트를 볼 수 있는 사람을 제한할 수 있습니다."
|
||||||
public: "모든 유저에게 공개합니다."
|
public: "모든 유저에게 공개합니다."
|
||||||
|
@ -1562,7 +1564,7 @@ _initialTutorial:
|
||||||
_howToMakeAttachmentsSensitive:
|
_howToMakeAttachmentsSensitive:
|
||||||
title: "첨부 파일을 열람주의로 설정하려면?"
|
title: "첨부 파일을 열람주의로 설정하려면?"
|
||||||
description: "서버의 가이드라인에 따라 필요한 이미지, 또는 그대로 노출되기에 부적절한 미디어는 '열람 주의'를 설정해 주세요."
|
description: "서버의 가이드라인에 따라 필요한 이미지, 또는 그대로 노출되기에 부적절한 미디어는 '열람 주의'를 설정해 주세요."
|
||||||
tryThisFile: "이 작성 창에 첨부된 이미지를 열람 주의로 설정해 보세요!"
|
tryThisFile: "이 입력란에 첨부된 이미지를 열람 주의로 설정해 보세요!"
|
||||||
_exampleNote:
|
_exampleNote:
|
||||||
note: "낫또 뚜껑 뜯다가 실수했다…"
|
note: "낫또 뚜껑 뜯다가 실수했다…"
|
||||||
method: "첨부 파일을 열람 주의로 설정하려면, 해당 파일을 클릭하여 메뉴를 열고, '열람주의로 설정'을 클릭합니다."
|
method: "첨부 파일을 열람 주의로 설정하려면, 해당 파일을 클릭하여 메뉴를 열고, '열람주의로 설정'을 클릭합니다."
|
||||||
|
@ -1816,7 +1818,7 @@ _achievements:
|
||||||
description: "드라이브 폴더에 스스로를 넣게 했다"
|
description: "드라이브 폴더에 스스로를 넣게 했다"
|
||||||
_reactWithoutRead:
|
_reactWithoutRead:
|
||||||
title: "읽고 답하긴 하시는 건가요?"
|
title: "읽고 답하긴 하시는 건가요?"
|
||||||
description: "100자가 넘는 노트를 작성한 지 3초 안에 리액션했다"
|
description: "100자가 넘는 노트를 게시한 지 3초 안에 리액션했다"
|
||||||
_clickedClickHere:
|
_clickedClickHere:
|
||||||
title: "여길 눌러보세요"
|
title: "여길 눌러보세요"
|
||||||
description: "여기를 눌렀다"
|
description: "여기를 눌렀다"
|
||||||
|
@ -1845,7 +1847,7 @@ _achievements:
|
||||||
_cookieClicked:
|
_cookieClicked:
|
||||||
title: "쿠키를 클릭하는 게임"
|
title: "쿠키를 클릭하는 게임"
|
||||||
description: "쿠키를 클릭했다"
|
description: "쿠키를 클릭했다"
|
||||||
flavor: "소프트웨어 착각하지 않았어?"
|
flavor: "소프트웨어 착각하지 않으셨나요?"
|
||||||
_brainDiver:
|
_brainDiver:
|
||||||
title: "Brain Diver"
|
title: "Brain Diver"
|
||||||
description: "Brain Diver로의 링크를 첨부했다"
|
description: "Brain Diver로의 링크를 첨부했다"
|
||||||
|
@ -2368,6 +2370,7 @@ _widgets:
|
||||||
chooseList: "리스트 선택"
|
chooseList: "리스트 선택"
|
||||||
clicker: "클리커"
|
clicker: "클리커"
|
||||||
birthdayFollowings: "오늘이 생일인 유저"
|
birthdayFollowings: "오늘이 생일인 유저"
|
||||||
|
chat: "채팅"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "숨기기"
|
hide: "숨기기"
|
||||||
show: "더 보기"
|
show: "더 보기"
|
||||||
|
@ -2634,6 +2637,7 @@ _deck:
|
||||||
mentions: "받은 멘션"
|
mentions: "받은 멘션"
|
||||||
direct: "다이렉트"
|
direct: "다이렉트"
|
||||||
roleTimeline: "역할 타임라인"
|
roleTimeline: "역할 타임라인"
|
||||||
|
chat: "채팅"
|
||||||
_dialog:
|
_dialog:
|
||||||
charactersExceeded: "최대 글자수를 초과하였습니다! 현재 {current} / 최대 {max}"
|
charactersExceeded: "최대 글자수를 초과하였습니다! 현재 {current} / 최대 {max}"
|
||||||
charactersBelow: "최소 글자수 미만입니다! 현재 {current} / 최소 {min}"
|
charactersBelow: "최소 글자수 미만입니다! 현재 {current} / 최소 {min}"
|
||||||
|
|
|
@ -118,6 +118,8 @@ renotedToX: "Renoted naar {name}"
|
||||||
cantRenote: "Dit bericht kan niet worden herdeeld"
|
cantRenote: "Dit bericht kan niet worden herdeeld"
|
||||||
cantReRenote: "Een herdeling kan niet worden herdeeld"
|
cantReRenote: "Een herdeling kan niet worden herdeeld"
|
||||||
quote: "Quote"
|
quote: "Quote"
|
||||||
|
inChannelRenote: "Alleen-kanaal Renote"
|
||||||
|
inChannelQuote: "Alleen-kanaal Citaat"
|
||||||
renoteToChannel: "Renote naar kanaal"
|
renoteToChannel: "Renote naar kanaal"
|
||||||
renoteToOtherChannel: "Renote naar ander kanaal"
|
renoteToOtherChannel: "Renote naar ander kanaal"
|
||||||
pinnedNote: "Vastgemaakte notitie"
|
pinnedNote: "Vastgemaakte notitie"
|
||||||
|
@ -143,6 +145,8 @@ unmarkAsSensitive: "Geen NSFW"
|
||||||
enterFileName: "Invoeren bestandsnaam"
|
enterFileName: "Invoeren bestandsnaam"
|
||||||
mute: "Dempen"
|
mute: "Dempen"
|
||||||
unmute: "Stop dempen"
|
unmute: "Stop dempen"
|
||||||
|
renoteMute: "Renotes dempen"
|
||||||
|
renoteUnmute: "Dempen Renotes opheffen"
|
||||||
block: "Blokkeren"
|
block: "Blokkeren"
|
||||||
unblock: "Deblokkeren"
|
unblock: "Deblokkeren"
|
||||||
suspend: "Opschorten"
|
suspend: "Opschorten"
|
||||||
|
@ -152,7 +156,10 @@ unblockConfirm: "Ben je zeker dat je deze account wil blokkeren?"
|
||||||
suspendConfirm: "Ben je zeker dat je deze account wil suspenderen?"
|
suspendConfirm: "Ben je zeker dat je deze account wil suspenderen?"
|
||||||
unsuspendConfirm: "Ben je zeker dat je deze account wil opnieuw aanstellen?"
|
unsuspendConfirm: "Ben je zeker dat je deze account wil opnieuw aanstellen?"
|
||||||
selectList: "Kies een lijst."
|
selectList: "Kies een lijst."
|
||||||
|
editList: "Lijst bewerken"
|
||||||
|
selectChannel: "Kanaal selecteren"
|
||||||
selectAntenna: "Kies een antenne"
|
selectAntenna: "Kies een antenne"
|
||||||
|
editAntenna: "Antenne bewerken"
|
||||||
createAntenna: "Antenne aanmaken"
|
createAntenna: "Antenne aanmaken"
|
||||||
selectWidget: "Kies een widget"
|
selectWidget: "Kies een widget"
|
||||||
editWidgets: "Bewerk widgets"
|
editWidgets: "Bewerk widgets"
|
||||||
|
@ -166,6 +173,9 @@ addEmoji: "Toevoegen emoji"
|
||||||
settingGuide: "Aanbevolen instellingen"
|
settingGuide: "Aanbevolen instellingen"
|
||||||
cacheRemoteFiles: "Externe bestanden cachen"
|
cacheRemoteFiles: "Externe bestanden cachen"
|
||||||
cacheRemoteFilesDescription: "Als deze instelling uitgeschakeld is worden bestanden altijd direct van remote servers geladen. Hiermee wordt opslagruimte bespaard, maar doordat er geen thumbnails worden gegenereerd, zal netwerkverkeer toenemen."
|
cacheRemoteFilesDescription: "Als deze instelling uitgeschakeld is worden bestanden altijd direct van remote servers geladen. Hiermee wordt opslagruimte bespaard, maar doordat er geen thumbnails worden gegenereerd, zal netwerkverkeer toenemen."
|
||||||
|
youCanCleanRemoteFilesCache: "Klik op de 🗑️ knop in de bestandsbeheerweergave om de cache te wissen."
|
||||||
|
cacheRemoteSensitiveFiles: "Gevoelige bestanden van externe instances in de cache bewaren"
|
||||||
|
cacheRemoteSensitiveFilesDescription: "Als deze instelling is uitgeschakeld, worden gevoelige bestanden op afstand direct vanuit de instantie op afstand geladen zonder caching."
|
||||||
flagAsBot: "Markeer dit account als een robot."
|
flagAsBot: "Markeer dit account als een robot."
|
||||||
flagAsBotDescription: "Als dit account van een programma wordt beheerd, zet deze vlag aan. Het aanzetten helpt andere ontwikkelaars om bijvoorbeeld onbedoelde feedback loops te doorbreken of om Misskey meer geschikt te maken."
|
flagAsBotDescription: "Als dit account van een programma wordt beheerd, zet deze vlag aan. Het aanzetten helpt andere ontwikkelaars om bijvoorbeeld onbedoelde feedback loops te doorbreken of om Misskey meer geschikt te maken."
|
||||||
flagAsCat: "Markeer dit account als een kat."
|
flagAsCat: "Markeer dit account als een kat."
|
||||||
|
@ -174,6 +184,7 @@ flagShowTimelineReplies: "Toon antwoorden op de tijdlijn."
|
||||||
flagShowTimelineRepliesDescription: "Als je dit vlag aanzet, toont de tijdlijn ook antwoorden op andere en niet alleen jouw eigen notities."
|
flagShowTimelineRepliesDescription: "Als je dit vlag aanzet, toont de tijdlijn ook antwoorden op andere en niet alleen jouw eigen notities."
|
||||||
autoAcceptFollowed: "Accepteer verzoeken om jezelf te volgen vanzelf als je de verzoeker al volgt."
|
autoAcceptFollowed: "Accepteer verzoeken om jezelf te volgen vanzelf als je de verzoeker al volgt."
|
||||||
addAccount: "Account toevoegen"
|
addAccount: "Account toevoegen"
|
||||||
|
reloadAccountsList: "Accountlijst opnieuw laden"
|
||||||
loginFailed: "Aanmelding mislukt."
|
loginFailed: "Aanmelding mislukt."
|
||||||
showOnRemote: "Toon op de externe instantie."
|
showOnRemote: "Toon op de externe instantie."
|
||||||
continueOnRemote: "Verder op remote server"
|
continueOnRemote: "Verder op remote server"
|
||||||
|
@ -205,6 +216,7 @@ perHour: "Per uur"
|
||||||
perDay: "Per dag"
|
perDay: "Per dag"
|
||||||
stopActivityDelivery: "Stop met versturen activiteiten"
|
stopActivityDelivery: "Stop met versturen activiteiten"
|
||||||
blockThisInstance: "Blokkeer deze server"
|
blockThisInstance: "Blokkeer deze server"
|
||||||
|
silenceThisInstance: "Instantie dempen"
|
||||||
mediaSilenceThisInstance: "Media van deze server dempen"
|
mediaSilenceThisInstance: "Media van deze server dempen"
|
||||||
operations: "Verwerkingen"
|
operations: "Verwerkingen"
|
||||||
software: "Software"
|
software: "Software"
|
||||||
|
@ -225,6 +237,7 @@ clearCachedFiles: "Cache opschonen"
|
||||||
clearCachedFilesConfirm: "Weet je zeker dat je alle externe bestanden in de cache wilt verwijderen?"
|
clearCachedFilesConfirm: "Weet je zeker dat je alle externe bestanden in de cache wilt verwijderen?"
|
||||||
blockedInstances: "Geblokkeerde servers"
|
blockedInstances: "Geblokkeerde servers"
|
||||||
blockedInstancesDescription: "Maak een lijst van de servers die moeten worden geblokkeerd, gescheiden door regeleinden. Geblokkeerde servers kunnen niet meer communiceren met deze server."
|
blockedInstancesDescription: "Maak een lijst van de servers die moeten worden geblokkeerd, gescheiden door regeleinden. Geblokkeerde servers kunnen niet meer communiceren met deze server."
|
||||||
|
silencedInstances: "Gedempte instanties"
|
||||||
silencedInstancesDescription: "Geef de hostnamen van de servers die je wil dempen op, elk op hun eigen regel. Alle accounts die bij de opgegeven servers horen worden als gedempt behandeld, kunnen alleen maar volgverzoeken maken, en kunnen lokale accounts niet vermelden als ze niet gevolgd worden. Geblokkeerde servers worden hier niet door beïnvloed."
|
silencedInstancesDescription: "Geef de hostnamen van de servers die je wil dempen op, elk op hun eigen regel. Alle accounts die bij de opgegeven servers horen worden als gedempt behandeld, kunnen alleen maar volgverzoeken maken, en kunnen lokale accounts niet vermelden als ze niet gevolgd worden. Geblokkeerde servers worden hier niet door beïnvloed."
|
||||||
mediaSilencedInstances: "Media-gedempte servers"
|
mediaSilencedInstances: "Media-gedempte servers"
|
||||||
mediaSilencedInstancesDescription: "Geef de hostnamen van de servers die je wil media-dempen op, elk op hun eigen regel. Alle accounts die bij de opgegeven servers horen worden als gedempt behandeld, en kunnen geen eigen emojis gebruiken. Geblokkeerde servers worden hier niet door beïnvloed."
|
mediaSilencedInstancesDescription: "Geef de hostnamen van de servers die je wil media-dempen op, elk op hun eigen regel. Alle accounts die bij de opgegeven servers horen worden als gedempt behandeld, en kunnen geen eigen emojis gebruiken. Geblokkeerde servers worden hier niet door beïnvloed."
|
||||||
|
@ -291,6 +304,10 @@ noMoreHistory: "Er is geen verdere geschiedenis"
|
||||||
startChat: "Chat starten"
|
startChat: "Chat starten"
|
||||||
nUsersRead: "gelezen door {n}"
|
nUsersRead: "gelezen door {n}"
|
||||||
agreeTo: "Ik stem in met {0}"
|
agreeTo: "Ik stem in met {0}"
|
||||||
|
agree: "Akkoord"
|
||||||
|
agreeBelow: "Ik ga akkoord met de volgende"
|
||||||
|
basicNotesBeforeCreateAccount: "Belangrijke informatie"
|
||||||
|
termsOfService: "Gebruiksvoorwaarden"
|
||||||
start: "Aan de slag"
|
start: "Aan de slag"
|
||||||
home: "Startpagina"
|
home: "Startpagina"
|
||||||
remoteUserCaution: "Aangezien deze gebruiker van een externe server afkomstig is, kan de weergegeven informatie onvolledig zijn."
|
remoteUserCaution: "Aangezien deze gebruiker van een externe server afkomstig is, kan de weergegeven informatie onvolledig zijn."
|
||||||
|
@ -336,6 +353,7 @@ copyUrl: "URL kopiëren"
|
||||||
rename: "Hernoemen"
|
rename: "Hernoemen"
|
||||||
avatar: "Avatar"
|
avatar: "Avatar"
|
||||||
banner: "Banner"
|
banner: "Banner"
|
||||||
|
displayOfSensitiveMedia: "Weergave van gevoelige media"
|
||||||
whenServerDisconnected: "Wanneer de verbinding met de server wordt onderbroken"
|
whenServerDisconnected: "Wanneer de verbinding met de server wordt onderbroken"
|
||||||
disconnectedFromServer: "Verbinding met de server onderbroken."
|
disconnectedFromServer: "Verbinding met de server onderbroken."
|
||||||
reload: "Verversen"
|
reload: "Verversen"
|
||||||
|
@ -373,7 +391,10 @@ bannerUrl: "Banner URL"
|
||||||
backgroundImageUrl: "URL afbeelding"
|
backgroundImageUrl: "URL afbeelding"
|
||||||
basicInfo: "Basisinformatie"
|
basicInfo: "Basisinformatie"
|
||||||
pinnedUsers: "Vastgeprikte gebruikers"
|
pinnedUsers: "Vastgeprikte gebruikers"
|
||||||
|
pinnedUsersDescription: "Een lijst met gebruikersnamen, gescheiden door regeleinden, die moet worden vastgemaakt in het tabblad “Verkennen”"
|
||||||
pinnedPages: "Vastgeprikte pagina's"
|
pinnedPages: "Vastgeprikte pagina's"
|
||||||
|
pinnedPagesDescription: "Voer de paden in van de Pagina's die je aan de bovenste pagina van deze instantie wilt vastmaken, gescheiden door regeleinden."
|
||||||
|
pinnedClipId: "ID van de clip die moet worden vastgepind"
|
||||||
pinnedNotes: "Vastgemaakte notitie"
|
pinnedNotes: "Vastgemaakte notitie"
|
||||||
hcaptcha: "hCaptcha"
|
hcaptcha: "hCaptcha"
|
||||||
enableHcaptcha: "Inschakelen hCaptcha"
|
enableHcaptcha: "Inschakelen hCaptcha"
|
||||||
|
@ -392,6 +413,7 @@ turnstile: "Tourniquet"
|
||||||
enableTurnstile: "Inschakelen tourniquet"
|
enableTurnstile: "Inschakelen tourniquet"
|
||||||
turnstileSiteKey: "Site sleutel"
|
turnstileSiteKey: "Site sleutel"
|
||||||
turnstileSecretKey: "Geheime sleutel"
|
turnstileSecretKey: "Geheime sleutel"
|
||||||
|
avoidMultiCaptchaConfirm: "Het gebruik van meerdere Captcha-systemen kan interferentie tussen deze systemen veroorzaken. Wil je de andere Captcha-systemen die momenteel actief zijn uitschakelen? Als je wilt dat ze ingeschakeld blijven, druk dan op annuleren."
|
||||||
antennas: "Antennes"
|
antennas: "Antennes"
|
||||||
manageAntennas: "Antennes beheren"
|
manageAntennas: "Antennes beheren"
|
||||||
name: "Naam"
|
name: "Naam"
|
||||||
|
@ -399,6 +421,13 @@ antennaSource: "Bron antenne"
|
||||||
antennaKeywords: "Sleutelwoorden"
|
antennaKeywords: "Sleutelwoorden"
|
||||||
antennaExcludeKeywords: "Blokkeerwoorden"
|
antennaExcludeKeywords: "Blokkeerwoorden"
|
||||||
antennaExcludeBots: "Bot-accounts uitsluiten"
|
antennaExcludeBots: "Bot-accounts uitsluiten"
|
||||||
|
antennaKeywordsDescription: "Scheid met spaties voor een EN-voorwaarde of met regeleinden voor een OF-voorwaarde."
|
||||||
|
notifyAntenna: "Houd een notificatie bij nieuwe notities"
|
||||||
|
withFileAntenna: "Alleen notities met bestanden"
|
||||||
|
excludeNotesInSensitiveChannel: "Sluit notities uit van gevoelige kanalen"
|
||||||
|
enableServiceworker: "Activeer pushmeldingen in de browser"
|
||||||
|
antennaUsersDescription: "Lijst één gebruikersnaam per regel"
|
||||||
|
caseSensitive: "Hoofdlettergevoelig"
|
||||||
withReplies: "Antwoorden toevoegen"
|
withReplies: "Antwoorden toevoegen"
|
||||||
connectedTo: "De volgende accounts zijn verbonden"
|
connectedTo: "De volgende accounts zijn verbonden"
|
||||||
notesAndReplies: "Berichten en reacties"
|
notesAndReplies: "Berichten en reacties"
|
||||||
|
@ -419,18 +448,30 @@ about: "Over"
|
||||||
aboutMisskey: "Over Misskey"
|
aboutMisskey: "Over Misskey"
|
||||||
administrator: "Beheerder"
|
administrator: "Beheerder"
|
||||||
token: "Token"
|
token: "Token"
|
||||||
|
2fa: "Twee factor authenticatie"
|
||||||
|
setupOf2fa: "Tweefactorauthenticatie instellen"
|
||||||
|
totp: "Verificatie-App"
|
||||||
|
totpDescription: "Log in via de verificatie-app met het eenmalige wachtwoord"
|
||||||
moderator: "Moderator"
|
moderator: "Moderator"
|
||||||
moderation: "Moderatie"
|
moderation: "Moderatie"
|
||||||
|
moderationNote: "Moderatienotitie"
|
||||||
|
moderationNoteDescription: "Voer hier notities in. Deze zijn alleen zichtbaar voor de moderators."
|
||||||
|
addModerationNote: "Moderatienotitie toevoegen"
|
||||||
|
moderationLogs: "Moderatieprotocollen"
|
||||||
nUsersMentioned: "Vermeld door {n} gebruikers"
|
nUsersMentioned: "Vermeld door {n} gebruikers"
|
||||||
|
securityKeyAndPasskey: "Beveiligings- en pasjessleutels"
|
||||||
securityKey: "Beveiligingssleutel"
|
securityKey: "Beveiligingssleutel"
|
||||||
lastUsed: "Laatst gebruikt"
|
lastUsed: "Laatst gebruikt"
|
||||||
|
lastUsedAt: "Laatst gebruikt: {t}"
|
||||||
unregister: "Uitschrijven"
|
unregister: "Uitschrijven"
|
||||||
passwordLessLogin: "Inloggen zonder wachtwoord"
|
passwordLessLogin: "Inloggen zonder wachtwoord"
|
||||||
|
passwordLessLoginDescription: "Maakt aanmelden zonder wachtwoord mogelijk met een beveiligingstoken of -wachtsleutel"
|
||||||
resetPassword: "Wachtwoord terugzetten"
|
resetPassword: "Wachtwoord terugzetten"
|
||||||
newPasswordIs: "Het nieuwe wachtwoord is „{password}”."
|
newPasswordIs: "Het nieuwe wachtwoord is „{password}”."
|
||||||
reduceUiAnimation: "Verminder beweging in de UI"
|
reduceUiAnimation: "Verminder beweging in de UI"
|
||||||
share: "Delen"
|
share: "Delen"
|
||||||
notFound: "Niet gevonden"
|
notFound: "Niet gevonden"
|
||||||
|
notFoundDescription: "Er is geen pagina gevonden onder deze URL."
|
||||||
uploadFolder: "Standaardmap voor uploaden"
|
uploadFolder: "Standaardmap voor uploaden"
|
||||||
markAsReadAllNotifications: "Markeer alle meldingen als gelezen"
|
markAsReadAllNotifications: "Markeer alle meldingen als gelezen"
|
||||||
markAsReadAllUnreadNotes: "Markeer alle berichten als gelezen"
|
markAsReadAllUnreadNotes: "Markeer alle berichten als gelezen"
|
||||||
|
@ -449,13 +490,53 @@ retype: "Opnieuw invoeren"
|
||||||
noteOf: "Notitie van {user}"
|
noteOf: "Notitie van {user}"
|
||||||
quoteAttached: "Citaat"
|
quoteAttached: "Citaat"
|
||||||
quoteQuestion: "Toevoegen als citaat?"
|
quoteQuestion: "Toevoegen als citaat?"
|
||||||
|
attachAsFileQuestion: "De tekst op het klembord is te lang. Wilt u het als een tekstbestand bijvoegen?"
|
||||||
|
onlyOneFileCanBeAttached: "Per bericht kan slechts één bestand worden bijgevoegd"
|
||||||
|
signinRequired: "Gelieve te registreren of in te loggen om verder te gaan"
|
||||||
signinOrContinueOnRemote: "Ga naar je eigen instantie of registreer je/log in op deze server om door te gaan."
|
signinOrContinueOnRemote: "Ga naar je eigen instantie of registreer je/log in op deze server om door te gaan."
|
||||||
invitations: "Uitnodigen"
|
invitations: "Uitnodigen"
|
||||||
|
invitationCode: "Uitnodigingscode"
|
||||||
|
checking: "Wordt gecheckt ..."
|
||||||
|
available: "Beschikbaar"
|
||||||
|
unavailable: "Onbeschikbaar"
|
||||||
|
usernameInvalidFormat: "Je kunt kleine letters, hoofdletters, cijfers en onderstrepingstekens gebruiken."
|
||||||
|
tooShort: "Te kort"
|
||||||
|
tooLong: "Te lang"
|
||||||
|
weakPassword: "Zwak wachtwoord"
|
||||||
|
normalPassword: "Redelijke wachtwoord"
|
||||||
|
strongPassword: "Sterk wachtwoord"
|
||||||
|
passwordMatched: "Lucifers"
|
||||||
|
passwordNotMatched: "Komt niet overeen"
|
||||||
|
signinWith: "Aanmelden met {x}"
|
||||||
|
signinFailed: "Inloggen mislukt. Controleer gebruikersnaam en wachtwoord."
|
||||||
|
or: "Of"
|
||||||
|
language: "Taal"
|
||||||
|
uiLanguage: "Taal van gebruikersinterface"
|
||||||
|
aboutX: "Over {x}"
|
||||||
|
emojiStyle: "Emoji-stijl"
|
||||||
|
native: "Inheems"
|
||||||
menuStyle: "Menustijl"
|
menuStyle: "Menustijl"
|
||||||
style: "Stijl"
|
style: "Stijl"
|
||||||
drawer: "Lade"
|
drawer: "Lade"
|
||||||
popup: "Pop-up"
|
popup: "Pop-up"
|
||||||
|
showNoteActionsOnlyHover: "Toon notitiemenu alleen bij muisaanwijzer"
|
||||||
showReactionsCount: "Zie het aantal reacties op notities"
|
showReactionsCount: "Zie het aantal reacties op notities"
|
||||||
|
noHistory: "Geen geschiedenis gevonden"
|
||||||
|
signinHistory: "Inloggeschiedenis"
|
||||||
|
enableAdvancedMfm: "Uitgebreide MFM activeren"
|
||||||
|
enableAnimatedMfm: "Geanimeerde MFM activeren"
|
||||||
|
doing: "In uitvoering..."
|
||||||
|
category: "Categorie"
|
||||||
|
tags: "Aliassen"
|
||||||
|
docSource: "Broncode van dit document"
|
||||||
|
createAccount: "Gebruikersaccount maken"
|
||||||
|
existingAccount: "Bestaand gebruikersaccount"
|
||||||
|
regenerate: "Regenereer"
|
||||||
|
fontSize: "Lettergrootte"
|
||||||
|
mediaListWithOneImageAppearance: "Hoogte van medialijsten met slechts één afbeelding"
|
||||||
|
limitTo: "Beperken tot {x}"
|
||||||
|
noFollowRequests: "Je hebt geen lopende volgverzoeken"
|
||||||
|
openImageInNewTab: "Afbeeldingen in nieuw tabblad openen"
|
||||||
dashboard: "Overzicht"
|
dashboard: "Overzicht"
|
||||||
local: "Lokaal"
|
local: "Lokaal"
|
||||||
remote: "Remote"
|
remote: "Remote"
|
||||||
|
@ -470,38 +551,388 @@ promote: "Promoot"
|
||||||
numberOfDays: "Aantal dagen"
|
numberOfDays: "Aantal dagen"
|
||||||
hideThisNote: "Verberg deze notitie"
|
hideThisNote: "Verberg deze notitie"
|
||||||
showFeaturedNotesInTimeline: "Laat featured notities in tijdlijn zien"
|
showFeaturedNotesInTimeline: "Laat featured notities in tijdlijn zien"
|
||||||
|
objectStorage: "Object Storage"
|
||||||
|
useObjectStorage: "Object Storage gebruiken"
|
||||||
|
objectStorageBaseUrl: "Basis-URL"
|
||||||
|
objectStorageBaseUrlDesc: "De URL die wordt gebruikt als referentie. Als je een CDN of proxy gebruikt, voer dan de URL daarvan in. Gebruik voor S3 ‘https://<bucket>.s3.amazonaws.com’. Gebruik voor GCS of vergelijkbaar ‘https://storage.googleapis.com/<bucket>’."
|
||||||
|
objectStorageBucket: "Bucket"
|
||||||
|
objectStorageBucketDesc: "Geef de bucketnaam op die bij je provider wordt gebruikt."
|
||||||
|
objectStoragePrefix: "Prefix"
|
||||||
|
objectStoragePrefixDesc: "Bestanden worden opgeslagen in de mappen onder deze prefix."
|
||||||
|
objectStorageEndpoint: "Endpoint"
|
||||||
|
objectStorageEndpointDesc: "Laat dit leeg als je AWS S3 gebruikt, anders geef je het eindpunt op als ‘<host>’ of ‘<host>:<port>’, afhankelijk van de service die je gebruikt."
|
||||||
|
objectStorageRegion: "Region"
|
||||||
|
objectStorageRegionDesc: "Voer een regio in zoals “xx-east-1”. Als je provider geen onderscheid maakt tussen regio's, voer dan “us-east-1” in. Laat leeg als je AWS-configuratiebestanden of omgevingsvariabelen gebruikt."
|
||||||
|
objectStorageUseSSL: "SSL gebruiken"
|
||||||
|
objectStorageUseSSLDesc: "Deactiveer dit als u geen HTTPS gebruikt voor API-verbindingen"
|
||||||
|
objectStorageUseProxy: "Verbinden via proxy"
|
||||||
|
objectStorageUseProxyDesc: "Deactiveer dit als u geen proxy wilt gebruiken voor verbindingen met de API"
|
||||||
|
objectStorageSetPublicRead: "Instellen op “public-read” op upload"
|
||||||
|
s3ForcePathStyleDesc: "Als s3ForcePathStyle is geactiveerd, moet de bucketnaam niet worden opgegeven in de hostnaam van de URL, maar in het pad van de URL. Deze optie moet mogelijk worden geactiveerd als services zoals een zelfbediende Minio-instantie worden gebruikt."
|
||||||
|
serverLogs: "Serverprotocollen"
|
||||||
|
deleteAll: "Alles verwijderen"
|
||||||
|
showFixedPostForm: "Het postingformulier bovenaan de tijdbalk weergeven"
|
||||||
|
showFixedPostFormInChannel: "Het postingformulier bovenaan de tijdbalk weergeven (Kanalen)"
|
||||||
|
withRepliesByDefaultForNewlyFollowed: "Toon replies van nieuw gevolgde gebruikers standaard in de tijdlijn"
|
||||||
|
newNoteRecived: "Er zijn nieuwe notities"
|
||||||
|
sounds: "Geluiden"
|
||||||
sound: "Geluid"
|
sound: "Geluid"
|
||||||
|
listen: "Luisteren"
|
||||||
|
none: "Niets"
|
||||||
|
showInPage: "Weergeven in een pagina"
|
||||||
|
popout: "Pop-Up"
|
||||||
|
volume: "Volume"
|
||||||
|
masterVolume: "Hoofdvolume"
|
||||||
notUseSound: "Geluid uitschakelen"
|
notUseSound: "Geluid uitschakelen"
|
||||||
useSoundOnlyWhenActive: "Geluid alleen inschakelen wanneer Misskey actief is"
|
useSoundOnlyWhenActive: "Geluid alleen inschakelen wanneer Misskey actief is"
|
||||||
|
details: "Details"
|
||||||
|
renoteDetails: "Renote Details"
|
||||||
|
chooseEmoji: "Emoji selecteren"
|
||||||
|
unableToProcess: "De operatie kan niet worden voltooid."
|
||||||
|
recentUsed: "Recent gebruikt"
|
||||||
|
install: "Installeren"
|
||||||
|
uninstall: "Deinstalleren"
|
||||||
|
installedApps: "Geautoriseerde toepassingen"
|
||||||
|
nothing: "Niets te zien hier"
|
||||||
|
installedDate: "Geautoriseerd at"
|
||||||
|
lastUsedDate: "Laatst gebruikt at"
|
||||||
|
state: "Status"
|
||||||
|
sort: "Sorteren"
|
||||||
|
ascendingOrder: "Oplopende volgorde"
|
||||||
|
descendingOrder: "Aflopende volgorde"
|
||||||
|
scratchpad: "Testomgeving"
|
||||||
|
scratchpadDescription: "De testomgeving biedt een gebied voor AiScript experimenten. Daar kunt u AiScript schrijven en uitvoeren en de effecten ervan op Misskey controleren."
|
||||||
uiInspector: "UI-inspecteur"
|
uiInspector: "UI-inspecteur"
|
||||||
|
uiInspectorDescription: "De lijst met servers van UI-componenten kan worden bekeken in de cache. De UI-component wordt gegenereerd door de functie Ui:C:"
|
||||||
|
output: "Uitvoer"
|
||||||
|
script: "Script"
|
||||||
|
disablePagesScript: "AiScript uitschakelen op pagina's"
|
||||||
|
updateRemoteUser: "Gebruikersinformatie bijwerken"
|
||||||
unsetUserAvatar: "Avatar verwijderen"
|
unsetUserAvatar: "Avatar verwijderen"
|
||||||
unsetUserAvatarConfirm: "Weet je zeker dat je je avatar wil verwijderen?"
|
unsetUserAvatarConfirm: "Weet je zeker dat je je avatar wil verwijderen?"
|
||||||
unsetUserBanner: "Banner verwijderen"
|
unsetUserBanner: "Banner verwijderen"
|
||||||
unsetUserBannerConfirm: "Weet je zeker dat je je banner wil verwijderen?"
|
unsetUserBannerConfirm: "Weet je zeker dat je je banner wil verwijderen?"
|
||||||
|
deleteAllFiles: "Alle bestanden verwijderen"
|
||||||
|
deleteAllFilesConfirm: "Wil je echt alle bestanden verwijderen?"
|
||||||
|
removeAllFollowing: "Ontvolg alle gevolgde gebruikers"
|
||||||
|
removeAllFollowingDescription: "Door dit uit te voeren worden alle accounts van {host} ontvolgd. Voer dit uit als de instantie bijvoorbeeld niet meer bestaat."
|
||||||
|
userSuspended: "Deze gebruiker is geschorst."
|
||||||
|
userSilenced: "Deze gebruiker is instantiebreed gedempt."
|
||||||
|
yourAccountSuspendedTitle: "Deze account is geschorst"
|
||||||
|
yourAccountSuspendedDescription: "Dit gebruikersaccount is geschorst omdat het de gebruiksvoorwaarden van deze server heeft geschonden. Neem contact op met de operator voor meer informatie. Maak geen nieuwe gebruikersaccount aan."
|
||||||
|
tokenRevoked: "Ongeldig token"
|
||||||
|
tokenRevokedDescription: "Het token is verlopen. Log opnieuw in."
|
||||||
|
accountDeleted: "Het gebruikersaccount is verwijderd"
|
||||||
|
accountDeletedDescription: "Deze account is verwijderd."
|
||||||
|
menu: "Menu"
|
||||||
|
divider: "Scheider"
|
||||||
|
addItem: "Element toevoegen"
|
||||||
|
rearrange: "Sorteren"
|
||||||
|
relays: "Relays"
|
||||||
|
addRelay: "Relay toevoegen"
|
||||||
|
inboxUrl: "Inbox-URL"
|
||||||
|
addedRelays: "Toegevoegd Relays"
|
||||||
|
serviceworkerInfo: "Moet worden geactiveerd voor pushmeldingen."
|
||||||
|
deletedNote: "Verwijderde notitie"
|
||||||
|
invisibleNote: "Privé notitie"
|
||||||
|
enableInfiniteScroll: "Automatisch meer laden"
|
||||||
|
visibility: "Zichtbaarheid"
|
||||||
|
poll: "Peiling"
|
||||||
|
useCw: "Inhoudswaarschuwing gebruiken"
|
||||||
|
enablePlayer: "Videospeler openen"
|
||||||
|
disablePlayer: "Videospeler sluiten"
|
||||||
expandTweet: "Notitie uitklappen"
|
expandTweet: "Notitie uitklappen"
|
||||||
|
themeEditor: "Thema-editor"
|
||||||
|
description: "Beschrijving"
|
||||||
|
describeFile: "Beschrijving toevoegen"
|
||||||
|
enterFileDescription: "Beschrijving invoeren"
|
||||||
|
author: "Auteur"
|
||||||
|
leaveConfirm: "Er zijn niet-opgeslagen wijzigingen. Wil je ze verwijderen?"
|
||||||
|
manage: "Beheer"
|
||||||
|
plugins: "Plugins"
|
||||||
|
preferencesBackups: "Instellingen Back-ups"
|
||||||
|
deck: "Dek"
|
||||||
|
undeck: "Dek verlaten"
|
||||||
|
useBlurEffectForModal: "Vervagingseffect gebruiken voor modals"
|
||||||
|
useFullReactionPicker: "Volledige reaktieselectier gebruiken"
|
||||||
|
width: "Breedte"
|
||||||
|
height: "Hoogte"
|
||||||
|
large: "Groot"
|
||||||
|
medium: "Medium"
|
||||||
|
small: "Klein"
|
||||||
|
generateAccessToken: "Toegangstoken genereren"
|
||||||
|
permission: "Machtigingen"
|
||||||
adminPermission: "Administratorrechten"
|
adminPermission: "Administratorrechten"
|
||||||
|
enableAll: "Alle activeren"
|
||||||
|
disableAll: "Alle deactiveren"
|
||||||
|
tokenRequested: "Toegang verlenen tot het gebruikersaccount"
|
||||||
|
pluginTokenRequestedDescription: "Deze plugin kan de hier geconfigureerde autorisaties gebruiken."
|
||||||
|
notificationType: "Type melding"
|
||||||
|
edit: "Bewerken"
|
||||||
|
emailServer: "Email-Server"
|
||||||
|
enableEmail: "Email distributie inschakelen"
|
||||||
|
emailConfigInfo: "Wordt gebruikt om je email te bevestigen tijdens het aanmelden of als je je wachtwoord bent vergeten"
|
||||||
|
email: "Email"
|
||||||
|
emailAddress: "Email adres"
|
||||||
|
smtpConfig: "SMTP-server configuratie"
|
||||||
smtpHost: "Server"
|
smtpHost: "Server"
|
||||||
|
smtpPort: "Poort"
|
||||||
smtpUser: "Gebruikersnaam"
|
smtpUser: "Gebruikersnaam"
|
||||||
smtpPass: "Wachtwoord"
|
smtpPass: "Wachtwoord"
|
||||||
|
emptyToDisableSmtpAuth: "Laat gebruikersnaam en wachtwoord leeg om SMTP-authenticatie uit te schakelen."
|
||||||
|
smtpSecure: "Impliciet SSL/TLS gebruiken voor SMTP-verbindingen"
|
||||||
|
smtpSecureInfo: "Schakel dit uit bij gebruik van STARTTLS"
|
||||||
|
testEmail: "Emailversand testen"
|
||||||
|
wordMute: "Woord dempen"
|
||||||
wordMuteDescription: "Minimaliseert notities die het gespecificeerde woord of zin bevatten. Geminimaliseerde notities kunnen worden weergegeven door er op te klikken."
|
wordMuteDescription: "Minimaliseert notities die het gespecificeerde woord of zin bevatten. Geminimaliseerde notities kunnen worden weergegeven door er op te klikken."
|
||||||
hardWordMute: "Harde woorddemping"
|
hardWordMute: "Harde woorddemping"
|
||||||
showMutedWord: "Gedempte woorden weergeven"
|
showMutedWord: "Gedempte woorden weergeven"
|
||||||
hardWordMuteDescription: "Verbert notities die het gespecificeerde woord of zin bevatten. In tegenstelling tot woorddemping wordt de notitie volledig verborgen."
|
hardWordMuteDescription: "Verbert notities die het gespecificeerde woord of zin bevatten. In tegenstelling tot woorddemping wordt de notitie volledig verborgen."
|
||||||
|
regexpError: "Fout in reguliere expressie"
|
||||||
|
regexpErrorDescription: "Er is een fout opgetreden in de reguliere expressie op regel {line} van uw {tab} woord dempen:"
|
||||||
|
instanceMute: "Instantie dempers"
|
||||||
|
userSaysSomething: "{name} zei iets"
|
||||||
userSaysSomethingAbout: "{name} zei iets over '{word}'"
|
userSaysSomethingAbout: "{name} zei iets over '{word}'"
|
||||||
|
makeActive: "Activeren"
|
||||||
|
display: "Weergave"
|
||||||
|
copy: "Kopiëren"
|
||||||
copiedToClipboard: "Naar het klembord gekopieerd"
|
copiedToClipboard: "Naar het klembord gekopieerd"
|
||||||
|
metrics: "Metrieken"
|
||||||
|
overview: "Overzicht"
|
||||||
|
logs: "Protocollen"
|
||||||
|
delayed: "Vertraagd"
|
||||||
|
database: "Database"
|
||||||
|
channel: "Kanalen"
|
||||||
|
create: "Creëer"
|
||||||
|
notificationSetting: "Instellingen meldingen"
|
||||||
|
notificationSettingDesc: "Selecteer het type meldingen dat moet worden weergegeven."
|
||||||
|
useGlobalSetting: "Globale instelling gebruiken"
|
||||||
|
useGlobalSettingDesc: "Als deze optie is ingeschakeld, worden de meldingsinstellingen van je account gebruikt. Als deze optie uitgeschakeld is, kunnen individuele configuraties worden gemaakt."
|
||||||
|
other: "Ander"
|
||||||
|
regenerateLoginToken: "Login token opnieuw genereren"
|
||||||
|
regenerateLoginTokenDescription: "Regenereren van het token dat intern wordt gebruikt om in te loggen. Dit is normaal gezien niet nodig. Alle apparaten worden afgemeld tijdens het regenereren."
|
||||||
theKeywordWhenSearchingForCustomEmoji: "Dit is het keyword dat gebruikt wordt bij het zoeken naar eigen emojis."
|
theKeywordWhenSearchingForCustomEmoji: "Dit is het keyword dat gebruikt wordt bij het zoeken naar eigen emojis."
|
||||||
|
setMultipleBySeparatingWithSpace: "Scheid elementen met een spatie om meerdere instellingen te configureren."
|
||||||
|
fileIdOrUrl: "Bestands-ID of URL"
|
||||||
|
behavior: "Gedrag"
|
||||||
|
sample: "Voorbeeld"
|
||||||
|
abuseReports: "Meldt"
|
||||||
|
reportAbuse: "Meld"
|
||||||
|
reportAbuseRenote: "Meld renote"
|
||||||
|
reportAbuseOf: "Meld {name}"
|
||||||
fillAbuseReportDescription: "Vul s.v.p. de details in over deze melding. Geef, als het over een specifieke notitie gaat, ook de URL op."
|
fillAbuseReportDescription: "Vul s.v.p. de details in over deze melding. Geef, als het over een specifieke notitie gaat, ook de URL op."
|
||||||
|
abuseReported: "Uw rapport is verzonden. Hartelijk dank."
|
||||||
|
reporter: "Verslaggever"
|
||||||
|
reporteeOrigin: "Oorsprong van de gemelde persoon"
|
||||||
|
reporterOrigin: "Verslaggever Oorsprong"
|
||||||
|
send: "Stuur"
|
||||||
|
openInNewTab: "In nieuw tabblad openen"
|
||||||
|
openInSideView: "In zijaanzicht openen"
|
||||||
|
defaultNavigationBehaviour: "Standaard navigatie gedrag"
|
||||||
|
editTheseSettingsMayBreakAccount: "Het wijzigen van deze instellingen kan je account beschadigen."
|
||||||
|
instanceTicker: "Instantie-informatie van notities"
|
||||||
|
waitingFor: "Wachten op {x}"
|
||||||
|
random: "Willekeurig"
|
||||||
|
system: "Systeem"
|
||||||
|
switchUi: "UI omschakelen"
|
||||||
|
desktop: "Desktop"
|
||||||
|
clip: "Clip aanmaken"
|
||||||
|
createNew: "Nieuwe aanmaken"
|
||||||
|
optional: "Optioneel"
|
||||||
|
createNewClip: "Nieuwe clip aanmaken"
|
||||||
|
unclip: "Van clip verwijderen"
|
||||||
|
confirmToUnclipAlreadyClippedNote: "Deze notitie is al toegevoegd aan de clip “{name}”. Wil je deze uit deze clip verwijderen?"
|
||||||
|
public: "Openbare"
|
||||||
|
private: "Privé"
|
||||||
|
i18nInfo: "Misskey wordt in veel verschillende talen vertaald door vrijwilligers. Je kunt helpen op {link}"
|
||||||
|
manageAccessTokens: "Toegangstokens beheren"
|
||||||
|
accountInfo: "Informatie over gebruikersaccount"
|
||||||
|
notesCount: "Aantal notities"
|
||||||
|
repliesCount: "Aantal verzonden replies"
|
||||||
|
renotesCount: "Aantal verzonden renotes"
|
||||||
|
repliedCount: "Aantal ontvangen replies"
|
||||||
|
renotedCount: "Aantal ontvangen renotes"
|
||||||
|
followingCount: "Aantal gevolgde accounts"
|
||||||
|
followersCount: "Aantal volgers"
|
||||||
|
sentReactionsCount: "Aantal verzonden reacties"
|
||||||
|
receivedReactionsCount: "Aantal ontvangen reacties"
|
||||||
|
pollVotesCount: "Aantal verzonden peiling stemmen"
|
||||||
|
pollVotedCount: "Aantal ontvangen peiling stemmen"
|
||||||
|
yes: "Ja"
|
||||||
|
no: "Nee"
|
||||||
|
driveFilesCount: "Aantal bestanden in station"
|
||||||
|
driveUsage: "Schijfruimtegebruik"
|
||||||
|
noCrawle: "Crawler-indexering verwerpen"
|
||||||
|
noCrawleDescription: "Vraag zoekmachines om je eigen profielpagina, notities, pagina's, enz. niet te indexeren."
|
||||||
|
lockedAccountInfo: "Tenzij je de zichtbaarheid van je notities instelt op “Alleen volgers”, zijn je notities zichtbaar voor iedereen, zelfs als je vereist dat volgers handmatig worden goedgekeurd."
|
||||||
|
alwaysMarkSensitive: "Markeer media standaard als gevoelig"
|
||||||
|
loadRawImages: "Toon altijd originele afbeeldingen in plaats van miniaturen"
|
||||||
|
disableShowingAnimatedImages: "Speel geen geanimeerde afbeeldingen af"
|
||||||
|
highlightSensitiveMedia: "Markeer gevoelige media"
|
||||||
|
verificationEmailSent: "Er is een bevestigingsmail naar uw e-mailadres verzonden. Ga naar de link in de e-mail om het verificatieproces te voltooien."
|
||||||
|
notSet: "Niet geconfigureerd"
|
||||||
|
emailVerified: "Emailadres bevestigd"
|
||||||
|
noteFavoritesCount: "Aantal notities gemarkeerd als favoriet"
|
||||||
|
pageLikesCount: "Aantal gelikete pagina's"
|
||||||
|
pageLikedCount: "Aantal ontvangen pagina-likes"
|
||||||
|
contact: "Contact"
|
||||||
|
useSystemFont: "Het standaardlettertype van het systeem gebruiken"
|
||||||
|
clips: "Clips"
|
||||||
|
experimentalFeatures: "Experimentele functionaliteiten"
|
||||||
|
experimental: "Experimentele"
|
||||||
|
thisIsExperimentalFeature: "Dit is een experimentele functie. De functionaliteit kan worden gewijzigd en werkt mogelijk niet zoals bedoeld."
|
||||||
|
developer: "Ontwikkelaar"
|
||||||
|
makeExplorable: "Gebruikersaccount zichtbaar maken in “Verkennen”"
|
||||||
|
makeExplorableDescription: "Als deze optie is uitgeschakeld, is uw gebruikersaccount niet zichtbaar in het gedeelte “Verkennen”."
|
||||||
|
showGapBetweenNotesInTimeline: "Een gat tussen noten op de tijdlijn weergeven"
|
||||||
|
duplicate: "Dupliceren"
|
||||||
|
left: "Links"
|
||||||
|
center: "Center"
|
||||||
|
wide: "Breed"
|
||||||
|
narrow: "Smal"
|
||||||
reloadToApplySetting: "Deze instelling gaat pas in nadat de pagina herladen is. Nu herladen?"
|
reloadToApplySetting: "Deze instelling gaat pas in nadat de pagina herladen is. Nu herladen?"
|
||||||
|
needReloadToApply: "Deze instelling wordt van kracht nadat de pagina is vernieuwd."
|
||||||
|
showTitlebar: "Titelbalk weergeven"
|
||||||
clearCache: "Cache opschonen"
|
clearCache: "Cache opschonen"
|
||||||
|
onlineUsersCount: "{n} Gebruikers zijn online"
|
||||||
|
nUsers: "{n} Gebruikers"
|
||||||
|
nNotes: "{n} Notities"
|
||||||
|
sendErrorReports: "Foutrapporten sturen"
|
||||||
|
sendErrorReportsDescription: "Als u deze optie inschakelt, wordt gedetailleerde foutinformatie met Misskey gedeeld wanneer zich een probleem voordoet. Dit helpt de kwaliteit van Misskey te verbeteren.\nDit omvat informatie zoals de versie van uw OS, welke browser u gebruikt, uw activiteit in Misskey, enz."
|
||||||
|
myTheme: "Mijn thema"
|
||||||
|
backgroundColor: "Achtergrondkleur"
|
||||||
|
accentColor: "Accentkleur"
|
||||||
|
textColor: "Tekstkleur"
|
||||||
|
saveAs: "Opslaan als…"
|
||||||
|
advanced: "Geavanceerd"
|
||||||
|
advancedSettings: "Geavanceerde instellingen"
|
||||||
|
value: "Waarde"
|
||||||
|
createdAt: "Aangemaakt at"
|
||||||
|
updatedAt: "Laatst gewijzigd at"
|
||||||
|
saveConfirm: "Wijzigingen opslaan?"
|
||||||
|
deleteConfirm: "Echt verwijderen?"
|
||||||
|
invalidValue: "Ongeldige waarde."
|
||||||
|
registry: "Registry"
|
||||||
|
closeAccount: "Gebruikersaccount sluiten"
|
||||||
|
currentVersion: "Huidige versie"
|
||||||
|
latestVersion: "Nieuwste versie"
|
||||||
|
youAreRunningUpToDateClient: "Je gebruikt de nieuwste versie van je client."
|
||||||
|
newVersionOfClientAvailable: "Er is een nieuwere versie van je client beschikbaar."
|
||||||
|
usageAmount: "Gebruik"
|
||||||
|
capacity: "Capaciteit"
|
||||||
|
inUse: "Gebruikt"
|
||||||
|
editCode: "Code bewerken"
|
||||||
|
apply: "Toepassen"
|
||||||
|
receiveAnnouncementFromInstance: "Meldingen ontvangen van deze instantie"
|
||||||
|
emailNotification: "E-mailmeldingen"
|
||||||
|
publish: "Publiceren"
|
||||||
|
inChannelSearch: "In kanaal zoeken"
|
||||||
|
useReactionPickerForContextMenu: "Open reactieselectie door rechts te klikken"
|
||||||
|
typingUsers: "{users} is/zijn aan het schrijven..."
|
||||||
|
jumpToSpecifiedDate: "Naar een specifieke datum springen"
|
||||||
|
showingPastTimeline: "Momenteel wordt een oude tijdlijn weergeven"
|
||||||
|
clear: "Terugkeren"
|
||||||
|
markAllAsRead: "Alles als gelezen markeren"
|
||||||
|
goBack: "Terug"
|
||||||
|
unlikeConfirm: "Wil je echt je like verwijderen?"
|
||||||
|
fullView: "Volledig zicht"
|
||||||
|
quitFullView: "Volledig zicht verlaten"
|
||||||
|
addDescription: "Beschrijving toevoegen"
|
||||||
|
userPagePinTip: "Je kunt hier notities tonen door “Vastmaken aan profiel” te selecteren in het menu van de individuele notities."
|
||||||
|
notSpecifiedMentionWarning: "Deze notitie bevat verwijzingen naar gebruikers die niet zijn geselecteerd als ontvangers"
|
||||||
info: "Over"
|
info: "Over"
|
||||||
|
userInfo: "Gebruikersinformatie"
|
||||||
|
unknown: "Onbekend"
|
||||||
|
onlineStatus: "Online status"
|
||||||
|
hideOnlineStatus: "Online status verbergen"
|
||||||
|
hideOnlineStatusDescription: "Het verbergen van je online status vermindert het nut van functies zoals zoeken."
|
||||||
|
online: "Online"
|
||||||
|
active: "Actief"
|
||||||
|
offline: "Offline"
|
||||||
|
notRecommended: "Niet aanbevolen"
|
||||||
|
botProtection: "Beveiliging tegen bots"
|
||||||
|
instanceBlocking: "Geblokkeerde/gedempte Instanties"
|
||||||
|
selectAccount: "Gebruikersaccount selecteren"
|
||||||
|
switchAccount: "Account wisselen"
|
||||||
|
enabled: "Ingeschakeld"
|
||||||
|
disabled: "Uitgeschakeld"
|
||||||
|
quickAction: "Snelle acties"
|
||||||
user: "Gebruikers"
|
user: "Gebruikers"
|
||||||
|
administration: "Beheer"
|
||||||
|
accounts: "Gebruikersaccounts"
|
||||||
|
switch: "Wissel"
|
||||||
|
noMaintainerInformationWarning: "Operatorinformatie is niet geconfigureerd."
|
||||||
noInquiryUrlWarning: "Contact-URL niet opgegeven"
|
noInquiryUrlWarning: "Contact-URL niet opgegeven"
|
||||||
|
noBotProtectionWarning: "Bescherming tegen bots is niet geconfigureerd."
|
||||||
|
configure: "Configureer"
|
||||||
|
postToGallery: "Nieuw galerijbericht maken"
|
||||||
|
postToHashtag: "Post naar deze hashtag"
|
||||||
|
gallery: "Galerij"
|
||||||
|
recentPosts: "Recente berichten"
|
||||||
|
popularPosts: "Populair berichten"
|
||||||
|
shareWithNote: "Delen met notitie"
|
||||||
|
ads: "Advertenties"
|
||||||
|
expiration: "Deadline"
|
||||||
|
startingperiod: "Start"
|
||||||
|
memo: "Memo"
|
||||||
|
priority: "Prioriteit"
|
||||||
|
high: "Hoge"
|
||||||
|
middle: "Medium"
|
||||||
|
low: "Lage"
|
||||||
|
emailNotConfiguredWarning: "E-mailadres niet ingesteld."
|
||||||
|
ratio: "Verhouding"
|
||||||
|
previewNoteText: "Show voorproefje"
|
||||||
|
customCss: "Aangepaste CSS"
|
||||||
|
customCssWarn: "Gebruik deze instelling alleen als je weet wat het doet. Ongeldige invoer kan ertoe leiden dat de client niet meer normaal functioneert."
|
||||||
|
global: "Globaal"
|
||||||
|
squareAvatars: "Toon profielfoto's as vierkant"
|
||||||
|
sent: "Verzonden"
|
||||||
|
received: "Ontvangen"
|
||||||
|
searchResult: "Zoekresultaten"
|
||||||
|
hashtags: "Hashtags"
|
||||||
|
troubleshooting: "Probleemoplossing"
|
||||||
|
useBlurEffect: "Vervagingseffecten in de UI gebruike"
|
||||||
|
learnMore: "Meer leren"
|
||||||
|
misskeyUpdated: "Misskey is bijgewerkt!"
|
||||||
|
whatIsNew: "Wijzigingen tonen"
|
||||||
|
translate: "Vertalen"
|
||||||
|
translatedFrom: "Vertaald uit {x}"
|
||||||
|
accountDeletionInProgress: "De verwijdering van je gebruikersaccount wordt momenteel verwerkt."
|
||||||
|
usernameInfo: "Een naam die kan worden gebruikt om je gebruikersaccount op deze server te identificeren. Je kunt het alfabet (a~z, A~Z), cijfers (0~9) of underscores (_) gebruiken. Gebruikersnamen kunnen later niet worden gewijzigd."
|
||||||
|
aiChanMode: "Ai Mode"
|
||||||
|
devMode: "Ontwikkelaar modus"
|
||||||
|
keepCw: "Inhoudswaarschuwingen behouden"
|
||||||
|
pubSub: "Pub/Sub Gebruikersaccounts"
|
||||||
|
lastCommunication: "Laatste communicatie"
|
||||||
|
resolved: "Opgelost"
|
||||||
|
unresolved: "Onopgelost"
|
||||||
|
breakFollow: "Volger verwijderen"
|
||||||
|
breakFollowConfirm: "Deze volger echt weghalen?"
|
||||||
|
itsOn: "Ingeschakeld"
|
||||||
|
itsOff: "Uitgeschakeld"
|
||||||
|
on: "Op"
|
||||||
|
off: "Uit"
|
||||||
|
emailRequiredForSignup: "Vereist e-mailadres voor aanmelding"
|
||||||
|
unread: "Ongelezen"
|
||||||
|
filter: "Filter"
|
||||||
|
controlPanel: "Controlepaneel"
|
||||||
|
manageAccounts: "Gebruikersaccounts beheren"
|
||||||
|
makeReactionsPublic: "Reactiegeschiedenis publiceren"
|
||||||
|
makeReactionsPublicDescription: "Hierdoor wordt de lijst met al je eerdere reacties openbaar."
|
||||||
|
classic: "Classic"
|
||||||
muteThread: "Discussies dempen "
|
muteThread: "Discussies dempen "
|
||||||
unmuteThread: "Dempen van discussie ongedaan maken"
|
unmuteThread: "Dempen van discussie ongedaan maken"
|
||||||
followingVisibility: "Zichtbaarheid van gevolgden"
|
followingVisibility: "Zichtbaarheid van gevolgden"
|
||||||
followersVisibility: "Zichtbaarheid van volgers"
|
followersVisibility: "Zichtbaarheid van volgers"
|
||||||
|
continueThread: "Bekijk draad voortzetting"
|
||||||
|
deleteAccountConfirm: "Je gebruikersaccount wordt onherroepelijk verwijderd. Wil je nog steeds doorgaan?"
|
||||||
|
incorrectPassword: "Onjuist wachtwoord."
|
||||||
incorrectTotp: "Het eenmalige wachtwoord is incorrect of verlopen"
|
incorrectTotp: "Het eenmalige wachtwoord is incorrect of verlopen"
|
||||||
|
voteConfirm: "Bevestig je je stem op “{choice}”?"
|
||||||
hide: "Verbergen"
|
hide: "Verbergen"
|
||||||
|
useDrawerReactionPickerForMobile: "Toon reactiekiezer als lade op mobiel"
|
||||||
|
welcomeBackWithName: "Welkom terug, {name}"
|
||||||
|
clickToFinishEmailVerification: "Druk op [{ok}] om de e-mailbevestiging af te ronden."
|
||||||
searchByGoogle: "Zoeken"
|
searchByGoogle: "Zoeken"
|
||||||
threeMonths: "3 maanden"
|
threeMonths: "3 maanden"
|
||||||
oneYear: "1 jaar"
|
oneYear: "1 jaar"
|
||||||
|
@ -509,6 +940,7 @@ threeDays: "3 dagen"
|
||||||
cropImage: "Afbeelding bijsnijden"
|
cropImage: "Afbeelding bijsnijden"
|
||||||
cropImageAsk: "Bijsnijdengevraagd"
|
cropImageAsk: "Bijsnijdengevraagd"
|
||||||
file: "Bestanden"
|
file: "Bestanden"
|
||||||
|
account: "Gebruikersaccounts"
|
||||||
pushNotification: "Pushberichten"
|
pushNotification: "Pushberichten"
|
||||||
subscribePushNotification: "Push meldingen inschakelen"
|
subscribePushNotification: "Push meldingen inschakelen"
|
||||||
unsubscribePushNotification: "Pushberichten uitschakelen"
|
unsubscribePushNotification: "Pushberichten uitschakelen"
|
||||||
|
@ -516,6 +948,7 @@ pushNotificationAlreadySubscribed: "Pushberichtrn al ingeschakeld"
|
||||||
windowMaximize: "Maximaliseren"
|
windowMaximize: "Maximaliseren"
|
||||||
windowRestore: "Herstellen"
|
windowRestore: "Herstellen"
|
||||||
loggedInAsBot: "Momenteel als bot ingelogd"
|
loggedInAsBot: "Momenteel als bot ingelogd"
|
||||||
|
show: "Weergave"
|
||||||
correspondingSourceIsAvailable: "De bijbehorende broncode is beschikbaar bij {anchor}"
|
correspondingSourceIsAvailable: "De bijbehorende broncode is beschikbaar bij {anchor}"
|
||||||
invalidParamErrorDescription: "De aanvraagparameters zijn ongeldig. Dit komt meestal door een bug, maar kan ook omdat de invoer te lang is of iets dergelijks."
|
invalidParamErrorDescription: "De aanvraagparameters zijn ongeldig. Dit komt meestal door een bug, maar kan ook omdat de invoer te lang is of iets dergelijks."
|
||||||
collapseRenotes: "Renotes die je al gezien hebt, inklappen"
|
collapseRenotes: "Renotes die je al gezien hebt, inklappen"
|
||||||
|
@ -534,26 +967,40 @@ lookupConfirm: "Weet je zeker dat je dit wil opzoeken?"
|
||||||
openTagPageConfirm: "Wil je deze hashtagpagina openen?"
|
openTagPageConfirm: "Wil je deze hashtagpagina openen?"
|
||||||
specifyHost: "Specificeer host"
|
specifyHost: "Specificeer host"
|
||||||
icon: "Avatar"
|
icon: "Avatar"
|
||||||
replies: "Antwoord"
|
replies: "Antwoorden"
|
||||||
renotes: "Herdelen"
|
renotes: "Herdelen"
|
||||||
followingOrFollower: "Gevolgd of volger"
|
followingOrFollower: "Gevolgd of volger"
|
||||||
confirmShowRepliesAll: "Dit is een onomkeerbare operatie. Weet je zeker dat reacties op anderen van iedereen die je volgt, wil weergeven in je tijdlijn?"
|
confirmShowRepliesAll: "Dit is een onomkeerbare operatie. Weet je zeker dat reacties op anderen van iedereen die je volgt, wil weergeven in je tijdlijn?"
|
||||||
information: "Over"
|
information: "Over"
|
||||||
_chat:
|
_chat:
|
||||||
invitations: "Uitnodigen"
|
invitations: "Uitnodigen"
|
||||||
|
noHistory: "Geen geschiedenis gevonden"
|
||||||
members: "Leden"
|
members: "Leden"
|
||||||
home: "Startpagina"
|
home: "Startpagina"
|
||||||
|
send: "Stuur"
|
||||||
_delivery:
|
_delivery:
|
||||||
stop: "Opgeschort"
|
stop: "Opgeschort"
|
||||||
_type:
|
_type:
|
||||||
none: "Publiceren"
|
none: "Publiceren"
|
||||||
|
_role:
|
||||||
|
priority: "Prioriteit"
|
||||||
|
_priority:
|
||||||
|
low: "Lage"
|
||||||
|
middle: "Medium"
|
||||||
|
high: "Hoge"
|
||||||
|
_ffVisibility:
|
||||||
|
public: "Publiceren"
|
||||||
|
_ad:
|
||||||
|
back: "Terug"
|
||||||
_email:
|
_email:
|
||||||
_follow:
|
_follow:
|
||||||
title: "volgde jou"
|
title: "volgde jou"
|
||||||
_theme:
|
_theme:
|
||||||
|
description: "Beschrijving"
|
||||||
keys:
|
keys:
|
||||||
mention: "Vermelding"
|
mention: "Vermelding"
|
||||||
renote: "Herdelen"
|
renote: "Herdelen"
|
||||||
|
divider: "Scheider"
|
||||||
_sfx:
|
_sfx:
|
||||||
note: "Notities"
|
note: "Notities"
|
||||||
notification: "Meldingen"
|
notification: "Meldingen"
|
||||||
|
@ -578,6 +1025,7 @@ _profile:
|
||||||
name: "Naam"
|
name: "Naam"
|
||||||
username: "Gebruikersnaam"
|
username: "Gebruikersnaam"
|
||||||
_exportOrImport:
|
_exportOrImport:
|
||||||
|
clips: "Clip aanmaken"
|
||||||
followingList: "Volgend"
|
followingList: "Volgend"
|
||||||
muteList: "Dempen"
|
muteList: "Dempen"
|
||||||
blockingList: "Blokkeren"
|
blockingList: "Blokkeren"
|
||||||
|
@ -588,6 +1036,9 @@ _charts:
|
||||||
federation: "Federatie"
|
federation: "Federatie"
|
||||||
_timelines:
|
_timelines:
|
||||||
home: "Startpagina"
|
home: "Startpagina"
|
||||||
|
_play:
|
||||||
|
script: "Script"
|
||||||
|
summary: "Beschrijving"
|
||||||
_pages:
|
_pages:
|
||||||
blocks:
|
blocks:
|
||||||
image: "Afbeeldingen"
|
image: "Afbeeldingen"
|
||||||
|
@ -610,9 +1061,15 @@ _deck:
|
||||||
tl: "Tijdlijn"
|
tl: "Tijdlijn"
|
||||||
antenna: "Antennes"
|
antenna: "Antennes"
|
||||||
list: "Lijsten"
|
list: "Lijsten"
|
||||||
|
channel: "Kanalen"
|
||||||
mentions: "Vermeldingen"
|
mentions: "Vermeldingen"
|
||||||
_webhookSettings:
|
_webhookSettings:
|
||||||
name: "Naam"
|
name: "Naam"
|
||||||
|
active: "Ingeschakeld"
|
||||||
|
_abuseReport:
|
||||||
|
_notificationRecipient:
|
||||||
|
_recipientType:
|
||||||
|
mail: "Email"
|
||||||
_moderationLogTypes:
|
_moderationLogTypes:
|
||||||
suspend: "Opschorten"
|
suspend: "Opschorten"
|
||||||
resetPassword: "Wachtwoord terugzetten"
|
resetPassword: "Wachtwoord terugzetten"
|
||||||
|
|
|
@ -5,6 +5,7 @@ introMisskey: "Bem-vindo! O Misskey é um serviço de microblog descentralizado
|
||||||
poweredByMisskeyDescription: "{name} é uma instância da plataforma de código aberto <b>Misskey</b>."
|
poweredByMisskeyDescription: "{name} é uma instância da plataforma de código aberto <b>Misskey</b>."
|
||||||
monthAndDay: "{day}/{month}"
|
monthAndDay: "{day}/{month}"
|
||||||
search: "Pesquisar"
|
search: "Pesquisar"
|
||||||
|
reset: "Redefinir"
|
||||||
notifications: "Notificações"
|
notifications: "Notificações"
|
||||||
username: "Nome de usuário"
|
username: "Nome de usuário"
|
||||||
password: "Senha"
|
password: "Senha"
|
||||||
|
@ -48,6 +49,7 @@ pin: "Fixar no perfil"
|
||||||
unpin: "Desafixar do perfil"
|
unpin: "Desafixar do perfil"
|
||||||
copyContent: "Copiar conteúdos"
|
copyContent: "Copiar conteúdos"
|
||||||
copyLink: "Copiar link"
|
copyLink: "Copiar link"
|
||||||
|
copyRemoteLink: "Copiar endereço remoto"
|
||||||
copyLinkRenote: "Copiar o link da repostagem"
|
copyLinkRenote: "Copiar o link da repostagem"
|
||||||
delete: "Excluir"
|
delete: "Excluir"
|
||||||
deleteAndEdit: "Excluir e editar"
|
deleteAndEdit: "Excluir e editar"
|
||||||
|
@ -299,6 +301,7 @@ uploadFromUrlMayTakeTime: "Pode levar algum tempo para que o upload seja conclu
|
||||||
explore: "Explorar"
|
explore: "Explorar"
|
||||||
messageRead: "Lida"
|
messageRead: "Lida"
|
||||||
noMoreHistory: "Não existe histórico anterior"
|
noMoreHistory: "Não existe histórico anterior"
|
||||||
|
startChat: "Iniciar conversa"
|
||||||
nUsersRead: "{n} pessoas leram"
|
nUsersRead: "{n} pessoas leram"
|
||||||
agreeTo: "Eu concordo com {0}"
|
agreeTo: "Eu concordo com {0}"
|
||||||
agree: "Concordar"
|
agree: "Concordar"
|
||||||
|
@ -421,6 +424,7 @@ antennaExcludeBots: "Ignorar contas de bot"
|
||||||
antennaKeywordsDescription: "Se você separá-lo com um espaço, será uma especificação AND, e se você separá-lo com uma quebra de linha, será uma especificação OR."
|
antennaKeywordsDescription: "Se você separá-lo com um espaço, será uma especificação AND, e se você separá-lo com uma quebra de linha, será uma especificação OR."
|
||||||
notifyAntenna: "Notificar novas notas"
|
notifyAntenna: "Notificar novas notas"
|
||||||
withFileAntenna: "Apenas notas com arquivos anexados"
|
withFileAntenna: "Apenas notas com arquivos anexados"
|
||||||
|
excludeNotesInSensitiveChannel: "Excluir notas de canais sensíveis"
|
||||||
enableServiceworker: "Ative as notificações push para o seu navegador"
|
enableServiceworker: "Ative as notificações push para o seu navegador"
|
||||||
antennaUsersDescription: "Especificar nomes de utilizador separados por quebras de linha"
|
antennaUsersDescription: "Especificar nomes de utilizador separados por quebras de linha"
|
||||||
caseSensitive: "Maiúsculas e minúsculas"
|
caseSensitive: "Maiúsculas e minúsculas"
|
||||||
|
@ -680,14 +684,19 @@ smtpSecure: "Use SSL/TLS implícito para conexões SMTP"
|
||||||
smtpSecureInfo: "Desative esta opção ao utilizar STARTTLS."
|
smtpSecureInfo: "Desative esta opção ao utilizar STARTTLS."
|
||||||
testEmail: "Testar envio de e-mail"
|
testEmail: "Testar envio de e-mail"
|
||||||
wordMute: "Silenciar palavras"
|
wordMute: "Silenciar palavras"
|
||||||
|
wordMuteDescription: "Minimizar notas que contêm a palavra ou frase especificada. Notas minimizadas são exibidas ao clicá-las."
|
||||||
hardWordMute: "Silenciar palavras (esconder posts)"
|
hardWordMute: "Silenciar palavras (esconder posts)"
|
||||||
|
showMutedWord: "Exibir palavras silenciadas"
|
||||||
|
hardWordMuteDescription: "Esconder notas que contêm a palavra ou frase especificada. Diferente do silenciamento de palavras, a nota será completamente escondida."
|
||||||
regexpError: "Erro na expressão regular"
|
regexpError: "Erro na expressão regular"
|
||||||
regexpErrorDescription: "Ocorreu um erro na expressão regular na linha {line} da palavra mutada {tab}:"
|
regexpErrorDescription: "Ocorreu um erro na expressão regular na linha {line} da palavra mutada {tab}:"
|
||||||
instanceMute: "Instâncias silenciadas"
|
instanceMute: "Instâncias silenciadas"
|
||||||
userSaysSomething: "{name} disse algo"
|
userSaysSomething: "{name} disse algo"
|
||||||
|
userSaysSomethingAbout: "{name} disse algo sobre \"{word}\""
|
||||||
makeActive: "Ativar"
|
makeActive: "Ativar"
|
||||||
display: "Visualizar"
|
display: "Visualizar"
|
||||||
copy: "Copiar"
|
copy: "Copiar"
|
||||||
|
copiedToClipboard: "Copiado à área de transferência"
|
||||||
metrics: "Métricas"
|
metrics: "Métricas"
|
||||||
overview: "Visão geral"
|
overview: "Visão geral"
|
||||||
logs: "Logs"
|
logs: "Logs"
|
||||||
|
@ -970,6 +979,7 @@ document: "Documentação"
|
||||||
numberOfPageCache: "Número de cache de página"
|
numberOfPageCache: "Número de cache de página"
|
||||||
numberOfPageCacheDescription: "Aumentar isso melhora a conveniência, mas também resulta em maior carga e uso de memória."
|
numberOfPageCacheDescription: "Aumentar isso melhora a conveniência, mas também resulta em maior carga e uso de memória."
|
||||||
logoutConfirm: "Gostaria de encerrar a sessão?"
|
logoutConfirm: "Gostaria de encerrar a sessão?"
|
||||||
|
logoutWillClearClientData: "Sair irá remover as configurações do cliente do navegador. Para redefinir as configurações ao entrar, você deve habilitar o backup automático de configurações."
|
||||||
lastActiveDate: "Última data de uso"
|
lastActiveDate: "Última data de uso"
|
||||||
statusbar: "Barra de status"
|
statusbar: "Barra de status"
|
||||||
pleaseSelect: "Por favor, selecione."
|
pleaseSelect: "Por favor, selecione."
|
||||||
|
@ -1297,16 +1307,137 @@ lockdown: "Lockdown"
|
||||||
pleaseSelectAccount: "Selecione uma conta"
|
pleaseSelectAccount: "Selecione uma conta"
|
||||||
availableRoles: "Cargos disponíveis"
|
availableRoles: "Cargos disponíveis"
|
||||||
acknowledgeNotesAndEnable: "Ative após compreender as precauções."
|
acknowledgeNotesAndEnable: "Ative após compreender as precauções."
|
||||||
|
federationSpecified: "Esse servidor opera com uma lista branca de federação. Interagir com servidores diferentes daqueles designados pela administração não é permitido."
|
||||||
|
federationDisabled: "Federação está desabilitada nesse servidor. Você não pode interagir com usuários de outros servidores."
|
||||||
|
confirmOnReact: "Confirmar ao reagir"
|
||||||
|
reactAreYouSure: "Você deseja adicionar uma reação \"{emoji}\"?"
|
||||||
|
markAsSensitiveConfirm: "Você deseja definir essa mídia como sensível?"
|
||||||
|
unmarkAsSensitiveConfirm: "Você deseja remover a definição dessa mídia como sensível?"
|
||||||
|
preferences: "Preferências"
|
||||||
|
accessibility: "Acessibilidade"
|
||||||
|
preferencesProfile: "Perfil de preferências"
|
||||||
|
copyPreferenceId: "Copiar ID de preferências"
|
||||||
|
resetToDefaultValue: "Reverter ao padrão"
|
||||||
|
overrideByAccount: "Sobrescrever pela conta"
|
||||||
|
untitled: "Sem título"
|
||||||
|
noName: "Sem nome"
|
||||||
|
skip: "Pular"
|
||||||
|
restore: "Redefinir"
|
||||||
|
syncBetweenDevices: "Sincronizar entre dispositivos"
|
||||||
|
preferenceSyncConflictTitle: "O valor configurado já existe no servidor."
|
||||||
|
preferenceSyncConflictText: "As preferências com a sincronização ativada irão salvar os seus valores no servidor. Porém, já existem valores no servidor. Qual conjunto de valores você deseja sobrescrever?"
|
||||||
|
preferenceSyncConflictChoiceServer: "Valor configurado no servidor"
|
||||||
|
preferenceSyncConflictChoiceDevice: "Valor configurado no dispositivo"
|
||||||
|
preferenceSyncConflictChoiceCancel: "Cancelar a habilitação de sincronização"
|
||||||
|
paste: "Colar"
|
||||||
|
emojiPalette: "Paleta de emojis"
|
||||||
postForm: "Campo de postagem"
|
postForm: "Campo de postagem"
|
||||||
|
textCount: "Contagem de caracteres"
|
||||||
information: "Informações"
|
information: "Informações"
|
||||||
|
chat: "Conversas"
|
||||||
|
migrateOldSettings: "Migrar configurações antigas de cliente"
|
||||||
|
migrateOldSettings_description: "Isso deve ser feito automaticamente. Caso o processo de migração tenha falhado, você pode acioná-lo manualmente. As informações atuais de migração serão substituídas."
|
||||||
|
compress: "Comprimir"
|
||||||
|
right: "Direita"
|
||||||
|
bottom: "Inferior"
|
||||||
|
top: "Superior"
|
||||||
|
embed: "Embed"
|
||||||
|
settingsMigrating: "Configurações estão sendo migradas, aguarde... (Você pode migrar manualmente em Configurações→Outros→Migrar configurações antigas de cliente)"
|
||||||
|
readonly: "Ler apenas"
|
||||||
|
goToDeck: "Voltar ao Deck"
|
||||||
|
federationJobs: "Tarefas de Federação"
|
||||||
_chat:
|
_chat:
|
||||||
|
noMessagesYet: "Ainda não há mensagens"
|
||||||
|
newMessage: "Nova mensagem"
|
||||||
|
individualChat: "Conversa Particular"
|
||||||
|
individualChat_description: "Ter uma conversa particular com outra pessoa."
|
||||||
|
roomChat: "Conversa de Grupo"
|
||||||
|
roomChat_description: "Uma sala de conversas com várias pessoas. Você pode adicionar pessoas que não permitem conversas privadas se elas aceitarem o convite."
|
||||||
|
createRoom: "Criar Sala"
|
||||||
|
inviteUserToChat: "Convide usuários para começar a conversar"
|
||||||
|
yourRooms: "Salas criadas"
|
||||||
|
joiningRooms: "Salas ingressadas"
|
||||||
invitations: "Convidar"
|
invitations: "Convidar"
|
||||||
|
noInvitations: "Sem convites"
|
||||||
|
history: "Histórico"
|
||||||
noHistory: "Ainda não há histórico"
|
noHistory: "Ainda não há histórico"
|
||||||
|
noRooms: "Nenhuma sala encontrada"
|
||||||
|
inviteUser: "Convidar Usuários"
|
||||||
|
sentInvitations: "Convites Enviados"
|
||||||
|
join: "Entrar"
|
||||||
|
ignore: "Ignorar"
|
||||||
|
leave: "Deixar sala"
|
||||||
members: "Membros"
|
members: "Membros"
|
||||||
|
searchMessages: "Pesquisar mensagens"
|
||||||
home: "Início"
|
home: "Início"
|
||||||
send: "Enviar"
|
send: "Enviar"
|
||||||
|
newline: "Nova linha"
|
||||||
|
muteThisRoom: "Silenciar sala"
|
||||||
|
deleteRoom: "Excluir sala"
|
||||||
|
chatNotAvailableForThisAccountOrServer: "Conversas não estão habilitadas nesse servidor ou para essa conta."
|
||||||
|
chatIsReadOnlyForThisAccountOrServer: "Conversas são apenas para leitura nesse servidor ou para essa conta. Não é possível escrever novas mensagens ou criar/ingressar novas conversas."
|
||||||
|
chatNotAvailableInOtherAccount: "A função de conversas está desabilitadas para o outro usuário."
|
||||||
|
cannotChatWithTheUser: "Não é possível conversar com esse usuário."
|
||||||
|
cannotChatWithTheUser_description: "Conversas estão indisponíveis ou o outro usuário não as habilitou."
|
||||||
|
chatWithThisUser: "Conversar com usuário"
|
||||||
|
thisUserAllowsChatOnlyFromFollowers: "Esse usuário aceita conversar apenas com seguidores."
|
||||||
|
thisUserAllowsChatOnlyFromFollowing: "Esse usuário aceita conversar apenas com quem segue."
|
||||||
|
thisUserAllowsChatOnlyFromMutualFollowing: "Esse usuário aceita conversar apenas com seguidores mútuos."
|
||||||
|
thisUserNotAllowedChatAnyone: "Esse usuário não aceita conversar com ninguém."
|
||||||
|
chatAllowedUsers: "Com quem permitir conversas"
|
||||||
|
chatAllowedUsers_note: "Você pode conversar com qualquer um com quem tenha iniciado uma conversa independente dessa configuração."
|
||||||
|
_chatAllowedUsers:
|
||||||
|
everyone: "Todos"
|
||||||
|
followers: "Seus seguidores"
|
||||||
|
following: "Quem você segue"
|
||||||
|
mutual: "Seguidores mútuos"
|
||||||
|
none: "Ninguém"
|
||||||
|
_emojiPalette:
|
||||||
|
palettes: "Paleta"
|
||||||
|
enableSyncBetweenDevicesForPalettes: "Sincronizar paleta entre dispositivos"
|
||||||
|
paletteForMain: "Paleta principal"
|
||||||
|
paletteForReaction: "Paleta de reações"
|
||||||
_settings:
|
_settings:
|
||||||
|
driveBanner: "Você consegue administrar e configurar o drive, conferir o seu uso e configurar as opções de envio de arquivos."
|
||||||
|
pluginBanner: "Você pode ampliar as funções do cliente com plugins. Você pode instalar plugins, configurar e administrar individualmente."
|
||||||
|
notificationsBanner: "Você pode configurar os tipos e intervalo das notificações do servidor, além de notificações push."
|
||||||
|
api: "API"
|
||||||
webhook: "Webhook"
|
webhook: "Webhook"
|
||||||
|
serviceConnection: "Integração de serviço"
|
||||||
|
serviceConnectionBanner: "Administre e configure tokens de acesso e webhooks para interagir com aplicações e serviços externos."
|
||||||
|
accountData: "Dados da conta"
|
||||||
|
accountDataBanner: "Exportar e importar dados da conta."
|
||||||
|
muteAndBlockBanner: "Você pode configurar meios para esconder conteúdo e restringir ações de certos usuários."
|
||||||
|
accessibilityBanner: "Você pode personalizar o visual e comportamento do cliente, além de configurar modos de otimizar o uso."
|
||||||
|
privacyBanner: "Você pode configurar a privacidade da conta por meio da visibilidade do conteúdo, capacidade de descoberta e aprovação manual de seguidores."
|
||||||
|
securityBanner: "Você pode configurar a segurança da conta em ajustes como senha, meios de entrada, aplicativos de autenticação e chaves de acesso."
|
||||||
|
preferencesBanner: "Você pode configurar o comportamento geral do cliente segundo as suas preferências."
|
||||||
|
appearanceBanner: "Você pode configurar a aparência do cliente e ajustes de tela segundo as suas preferências."
|
||||||
|
soundsBanner: "Você pode configurar a reprodução de sons no cliente."
|
||||||
|
timelineAndNote: "Notas e linha do tempo"
|
||||||
|
makeEveryTextElementsSelectable: "Tornar todos os elementos de texto selecionáveis"
|
||||||
|
makeEveryTextElementsSelectable_description: "Habilitar isso pode reduzir a usabilidade em algumas situações"
|
||||||
|
useStickyIcons: "Fazer ícones acompanharem a rolagem da tela"
|
||||||
|
showNavbarSubButtons: "Mostrar sub-botões na barra de navegação"
|
||||||
|
ifOn: "Quando ligado"
|
||||||
|
ifOff: "Quando desligado"
|
||||||
|
enableSyncThemesBetweenDevices: "Sincronizar temas instalados entre dispositivos"
|
||||||
|
_chat:
|
||||||
|
showSenderName: "Exibir nome de usuário do remetente"
|
||||||
|
sendOnEnter: "Pressionar Enter para enviar"
|
||||||
|
_preferencesProfile:
|
||||||
|
profileName: "Nome do perfil"
|
||||||
|
profileNameDescription: "Defina o nome que identifica esse dispositivo."
|
||||||
|
profileNameDescription2: "Exemplo: \"Computador Principal\", \"Celular\""
|
||||||
|
_preferencesBackup:
|
||||||
|
autoBackup: "Backup automático"
|
||||||
|
restoreFromBackup: "Restaurar backup"
|
||||||
|
noBackupsFoundTitle: "Nenhum backup encontrado"
|
||||||
|
noBackupsFoundDescription: "Nenhum backup automático foi encontrado. Se você salvou um arquivo de backup manualmente, você pode importá-lo e restaurá-lo."
|
||||||
|
selectBackupToRestore: "Selecionar um backup para restaurar"
|
||||||
|
youNeedToNameYourProfileToEnableAutoBackup: "Um nome de perfil deve ser definido para habilitar o backup automático."
|
||||||
|
autoPreferencesBackupIsNotEnabledForThisDevice: "Backup automático de configurações não está habilitado no dispositivo."
|
||||||
|
backupFound: "Backup de configurações encontrado"
|
||||||
_accountSettings:
|
_accountSettings:
|
||||||
requireSigninToViewContents: "Exigir cadastro para ver o conteúdo"
|
requireSigninToViewContents: "Exigir cadastro para ver o conteúdo"
|
||||||
requireSigninToViewContentsDescription1: "Exigir cadastro para ver todas as notas e outro conteúdo que você criou. Isso previne 'crawlers' de coletar os seus dados."
|
requireSigninToViewContentsDescription1: "Exigir cadastro para ver todas as notas e outro conteúdo que você criou. Isso previne 'crawlers' de coletar os seus dados."
|
||||||
|
@ -1317,6 +1448,7 @@ _accountSettings:
|
||||||
makeNotesHiddenBefore: "Tornar notas passadas privadas"
|
makeNotesHiddenBefore: "Tornar notas passadas privadas"
|
||||||
makeNotesHiddenBeforeDescription: "Com essa função ativada, apenas você poderá ver as notas anteriores à data e hora marcadas. Se isso for desativado, o status de publicação da nota será reestabelecido."
|
makeNotesHiddenBeforeDescription: "Com essa função ativada, apenas você poderá ver as notas anteriores à data e hora marcadas. Se isso for desativado, o status de publicação da nota será reestabelecido."
|
||||||
mayNotEffectForFederatedNotes: "Notas federadas a servidores remotos podem não ser afetadas."
|
mayNotEffectForFederatedNotes: "Notas federadas a servidores remotos podem não ser afetadas."
|
||||||
|
mayNotEffectSomeSituations: "Essas restrições são simplificadas. Elas podem não ser aplicadas em algumas situações, como ao visualizar num servidor remoto ou durante a moderação."
|
||||||
notesHavePassedSpecifiedPeriod: "Notas que duraram um tempo específico."
|
notesHavePassedSpecifiedPeriod: "Notas que duraram um tempo específico."
|
||||||
notesOlderThanSpecifiedDateAndTime: "Notas antes do tempo específico."
|
notesOlderThanSpecifiedDateAndTime: "Notas antes do tempo específico."
|
||||||
_abuseUserReport:
|
_abuseUserReport:
|
||||||
|
@ -1762,6 +1894,8 @@ _role:
|
||||||
descriptionOfIsExplorable: "Ao ativar, a lista de membros será pública na seção 'Explorar' e a linha do tempo do cargo ficará disponível."
|
descriptionOfIsExplorable: "Ao ativar, a lista de membros será pública na seção 'Explorar' e a linha do tempo do cargo ficará disponível."
|
||||||
displayOrder: "Ordenação"
|
displayOrder: "Ordenação"
|
||||||
descriptionOfDisplayOrder: "Quanto maior o número, maior a posição de destaque na interface do usuário."
|
descriptionOfDisplayOrder: "Quanto maior o número, maior a posição de destaque na interface do usuário."
|
||||||
|
preserveAssignmentOnMoveAccount: "Preservar a associação de cargos durante a migração"
|
||||||
|
preserveAssignmentOnMoveAccount_description: "Quando ligado, esse cargo será encaminhado para a conta final quando houver migração de um usuário."
|
||||||
canEditMembersByModerator: "Permitir a edição de membros deste cargo por moderadores"
|
canEditMembersByModerator: "Permitir a edição de membros deste cargo por moderadores"
|
||||||
descriptionOfCanEditMembersByModerator: "Quando ativado, os moderadores também poderão atribuir/remover usuários deste papel, além dos administradores. Quando desativado, apenas os administradores poderão fazê-lo."
|
descriptionOfCanEditMembersByModerator: "Quando ativado, os moderadores também poderão atribuir/remover usuários deste papel, além dos administradores. Quando desativado, apenas os administradores poderão fazê-lo."
|
||||||
priority: "Prioridade"
|
priority: "Prioridade"
|
||||||
|
@ -1802,6 +1936,7 @@ _role:
|
||||||
canImportFollowing: "Permitir importação de usuários seguidos"
|
canImportFollowing: "Permitir importação de usuários seguidos"
|
||||||
canImportMuting: "Permitir importação de silenciamentos"
|
canImportMuting: "Permitir importação de silenciamentos"
|
||||||
canImportUserLists: "Permitir importação de listas"
|
canImportUserLists: "Permitir importação de listas"
|
||||||
|
chatAvailability: "Permitir Conversas"
|
||||||
_condition:
|
_condition:
|
||||||
roleAssignedTo: "Atribuído a cargos manuais"
|
roleAssignedTo: "Atribuído a cargos manuais"
|
||||||
isLocal: "Usuário local"
|
isLocal: "Usuário local"
|
||||||
|
@ -1965,6 +2100,7 @@ _theme:
|
||||||
installed: "{name} foi instalado"
|
installed: "{name} foi instalado"
|
||||||
installedThemes: "Temas instalados"
|
installedThemes: "Temas instalados"
|
||||||
builtinThemes: "Temas nativos"
|
builtinThemes: "Temas nativos"
|
||||||
|
instanceTheme: "Tema do servidor"
|
||||||
alreadyInstalled: "Esse tema já foi instalado"
|
alreadyInstalled: "Esse tema já foi instalado"
|
||||||
invalid: "O formato desse tema é invalido"
|
invalid: "O formato desse tema é invalido"
|
||||||
make: "Fazer um tema"
|
make: "Fazer um tema"
|
||||||
|
@ -2027,6 +2163,7 @@ _sfx:
|
||||||
noteMy: "Própria nota"
|
noteMy: "Própria nota"
|
||||||
notification: "Notificações"
|
notification: "Notificações"
|
||||||
reaction: "Ao selecionar uma reação"
|
reaction: "Ao selecionar uma reação"
|
||||||
|
chatMessage: "Mensagens em Conversas"
|
||||||
_soundSettings:
|
_soundSettings:
|
||||||
driveFile: "Usar um arquivo de áudio do Drive."
|
driveFile: "Usar um arquivo de áudio do Drive."
|
||||||
driveFileWarn: "Selecione um arquivo de áudio do Drive."
|
driveFileWarn: "Selecione um arquivo de áudio do Drive."
|
||||||
|
@ -2174,6 +2311,7 @@ _permissions:
|
||||||
"read:federation": "Ver dados de federação"
|
"read:federation": "Ver dados de federação"
|
||||||
"write:report-abuse": "Reportar violação"
|
"write:report-abuse": "Reportar violação"
|
||||||
"write:chat": "Compor ou editar mensagens de chat"
|
"write:chat": "Compor ou editar mensagens de chat"
|
||||||
|
"read:chat": "Navegar Conversas"
|
||||||
_auth:
|
_auth:
|
||||||
shareAccessTitle: "Conceder permissões do aplicativo"
|
shareAccessTitle: "Conceder permissões do aplicativo"
|
||||||
shareAccess: "Você gostaria de autorizar \"{name}\" para acessar essa conta?"
|
shareAccess: "Você gostaria de autorizar \"{name}\" para acessar essa conta?"
|
||||||
|
@ -2232,6 +2370,7 @@ _widgets:
|
||||||
chooseList: "Selecione uma lista"
|
chooseList: "Selecione uma lista"
|
||||||
clicker: "Clicker"
|
clicker: "Clicker"
|
||||||
birthdayFollowings: "Usuários de aniversário hoje"
|
birthdayFollowings: "Usuários de aniversário hoje"
|
||||||
|
chat: "Conversas"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "Esconder"
|
hide: "Esconder"
|
||||||
show: "Carregar mais"
|
show: "Carregar mais"
|
||||||
|
@ -2422,6 +2561,7 @@ _notification:
|
||||||
newNote: "Nova nota"
|
newNote: "Nova nota"
|
||||||
unreadAntennaNote: "Antena {name}"
|
unreadAntennaNote: "Antena {name}"
|
||||||
roleAssigned: "Cargo dado"
|
roleAssigned: "Cargo dado"
|
||||||
|
chatRoomInvitationReceived: "Você foi convidado para uma conversa"
|
||||||
emptyPushNotificationMessage: "As notificações de alerta foram atualizadas"
|
emptyPushNotificationMessage: "As notificações de alerta foram atualizadas"
|
||||||
achievementEarned: "Conquista desbloqueada"
|
achievementEarned: "Conquista desbloqueada"
|
||||||
testNotification: "Notificação teste"
|
testNotification: "Notificação teste"
|
||||||
|
@ -2435,6 +2575,8 @@ _notification:
|
||||||
flushNotification: "Limpar notificações"
|
flushNotification: "Limpar notificações"
|
||||||
exportOfXCompleted: "Exportação de {x} foi concluída"
|
exportOfXCompleted: "Exportação de {x} foi concluída"
|
||||||
login: "Alguém entrou na conta"
|
login: "Alguém entrou na conta"
|
||||||
|
createToken: "Uma token de acesso foi criada"
|
||||||
|
createTokenDescription: "Se você não faz ideia, exclua o token de acesso através de \"{text}\"."
|
||||||
_types:
|
_types:
|
||||||
all: "Todas"
|
all: "Todas"
|
||||||
note: "Novas notas"
|
note: "Novas notas"
|
||||||
|
@ -2448,9 +2590,11 @@ _notification:
|
||||||
receiveFollowRequest: "Recebeu pedidos de seguidor"
|
receiveFollowRequest: "Recebeu pedidos de seguidor"
|
||||||
followRequestAccepted: "Aceitou pedidos de seguidor"
|
followRequestAccepted: "Aceitou pedidos de seguidor"
|
||||||
roleAssigned: "Cargo dado"
|
roleAssigned: "Cargo dado"
|
||||||
|
chatRoomInvitationReceived: "Convite de conversa recebido"
|
||||||
achievementEarned: "Conquista desbloqueada"
|
achievementEarned: "Conquista desbloqueada"
|
||||||
exportCompleted: "A exportação foi concluída"
|
exportCompleted: "A exportação foi concluída"
|
||||||
login: "Iniciar sessão"
|
login: "Iniciar sessão"
|
||||||
|
createToken: "Criar token de acesso"
|
||||||
test: "Notificação teste"
|
test: "Notificação teste"
|
||||||
app: "Notificações de aplicativos conectados"
|
app: "Notificações de aplicativos conectados"
|
||||||
_actions:
|
_actions:
|
||||||
|
@ -2460,6 +2604,9 @@ _notification:
|
||||||
_deck:
|
_deck:
|
||||||
alwaysShowMainColumn: "Sempre mostrar a coluna principal"
|
alwaysShowMainColumn: "Sempre mostrar a coluna principal"
|
||||||
columnAlign: "Alinhar colunas"
|
columnAlign: "Alinhar colunas"
|
||||||
|
columnGap: "Margem entre colunas"
|
||||||
|
deckMenuPosition: "Posição do menu do deck"
|
||||||
|
navbarPosition: "Posição da barra de navegação"
|
||||||
addColumn: "Adicionar coluna"
|
addColumn: "Adicionar coluna"
|
||||||
newNoteNotificationSettings: "Opções de notificação para novas notas"
|
newNoteNotificationSettings: "Opções de notificação para novas notas"
|
||||||
configureColumn: "Configurar coluna"
|
configureColumn: "Configurar coluna"
|
||||||
|
@ -2478,6 +2625,7 @@ _deck:
|
||||||
useSimpleUiForNonRootPages: "Usar UI simples para páginas navegadas"
|
useSimpleUiForNonRootPages: "Usar UI simples para páginas navegadas"
|
||||||
usedAsMinWidthWhenFlexible: "A largura mínima será usada para isso quando o \"Ajuste automático da largura\" estiver ativado"
|
usedAsMinWidthWhenFlexible: "A largura mínima será usada para isso quando o \"Ajuste automático da largura\" estiver ativado"
|
||||||
flexible: "Ajuste automático da largura"
|
flexible: "Ajuste automático da largura"
|
||||||
|
enableSyncBetweenDevicesForProfiles: "Habilitar sincronização das informações do perfil entre dispositivos"
|
||||||
_columns:
|
_columns:
|
||||||
main: "Principal"
|
main: "Principal"
|
||||||
widgets: "Widgets"
|
widgets: "Widgets"
|
||||||
|
@ -2489,6 +2637,7 @@ _deck:
|
||||||
mentions: "Menções"
|
mentions: "Menções"
|
||||||
direct: "Notas diretas"
|
direct: "Notas diretas"
|
||||||
roleTimeline: "Linha do tempo do cargo"
|
roleTimeline: "Linha do tempo do cargo"
|
||||||
|
chat: "Conversas"
|
||||||
_dialog:
|
_dialog:
|
||||||
charactersExceeded: "Você excedeu o limite de caracteres! Atualmente em {current} de {max}."
|
charactersExceeded: "Você excedeu o limite de caracteres! Atualmente em {current} de {max}."
|
||||||
charactersBelow: "Você está abaixo do limite mínimo de caracteres! Atualmente em {current} of {min}."
|
charactersBelow: "Você está abaixo do limite mínimo de caracteres! Atualmente em {current} of {min}."
|
||||||
|
@ -2585,6 +2734,8 @@ _moderationLogTypes:
|
||||||
deletePage: "Remover página"
|
deletePage: "Remover página"
|
||||||
deleteFlash: "Remover Play"
|
deleteFlash: "Remover Play"
|
||||||
deleteGalleryPost: "Remover a publicação da galeria"
|
deleteGalleryPost: "Remover a publicação da galeria"
|
||||||
|
deleteChatRoom: "Sala de Conversas Excluída"
|
||||||
|
updateProxyAccountDescription: "Atualizar descrição da conta de proxy"
|
||||||
_fileViewer:
|
_fileViewer:
|
||||||
title: "Detalhes do arquivo"
|
title: "Detalhes do arquivo"
|
||||||
type: "Tipo de arquivo"
|
type: "Tipo de arquivo"
|
||||||
|
@ -2719,6 +2870,66 @@ _contextMenu:
|
||||||
app: "Aplicativo"
|
app: "Aplicativo"
|
||||||
appWithShift: "Aplicativo com a tecla shift"
|
appWithShift: "Aplicativo com a tecla shift"
|
||||||
native: "Nativo"
|
native: "Nativo"
|
||||||
|
_gridComponent:
|
||||||
|
_error:
|
||||||
|
requiredValue: "Esse valor é necessário"
|
||||||
|
columnTypeNotSupport: "Validação de expressões regulares (RegEx) só é permitida em colunas type:text."
|
||||||
|
patternNotMatch: "Esse valor não se encaixa no padrão de {pattern}"
|
||||||
|
notUnique: "Valor deve ser único"
|
||||||
|
_roleSelectDialog:
|
||||||
|
notSelected: "Não selecionado"
|
||||||
|
_customEmojisManager:
|
||||||
|
_gridCommon:
|
||||||
|
copySelectionRows: "Copiar linhas selecionadas"
|
||||||
|
copySelectionRanges: "Copiar seleção"
|
||||||
|
deleteSelectionRows: "Excluir linhas selecionadas"
|
||||||
|
deleteSelectionRanges: "Excluir valores selecionados"
|
||||||
|
searchSettings: "Opções de busca"
|
||||||
|
searchSettingCaption: "Definir critérios detalhados de busca."
|
||||||
|
searchLimit: "Limite de busca"
|
||||||
|
sortOrder: "Ordem de classificação"
|
||||||
|
registrationLogs: "Histórico de registros"
|
||||||
|
registrationLogsCaption: "Atualizações e remoções de emoji serão gravadas no histórico. Atualizar, remover, mover a uma nova página ou recarregar limpará o histórico"
|
||||||
|
alertEmojisRegisterFailedDescription: "Não foi possível atualizar ou remover emojis. Por favor, confira o histórico de registro para mais detalhes."
|
||||||
|
_logs:
|
||||||
|
showSuccessLogSwitch: "Exibir sucessos no histórico"
|
||||||
|
failureLogNothing: "Não há registro de falhas."
|
||||||
|
logNothing: "Não há registros."
|
||||||
|
_remote:
|
||||||
|
selectionRowDetail: "Detalhes da linha selecionada"
|
||||||
|
importSelectionRows: "Importar linhas selecionadas"
|
||||||
|
importSelectionRangesRows: "Importar linhas no intervalo"
|
||||||
|
importEmojisButton: "Importar Emojis selecionados"
|
||||||
|
confirmImportEmojisTitle: "Importar Emojis"
|
||||||
|
confirmImportEmojisDescription: "Importar {count} Emoji(s) recebidos de um servidor remoto. Por favor, preste atenção na licença do Emoji. Tem certeza que deseja continuar?"
|
||||||
|
_local:
|
||||||
|
tabTitleList: "Emojis registrados"
|
||||||
|
tabTitleRegister: "Registro de Emoji"
|
||||||
|
_list:
|
||||||
|
emojisNothing: "Não há Emojis registrados."
|
||||||
|
markAsDeleteTargetRows: "Marcar linhas selecionadas para remoção"
|
||||||
|
markAsDeleteTargetRanges: "Marcar linhas no intervalo para remoção"
|
||||||
|
alertUpdateEmojisNothingDescription: "Não há Emojis atualizados."
|
||||||
|
alertDeleteEmojisNothingDescription: "Não há Emojis marcados para remoção."
|
||||||
|
confirmMovePage: "Deseja mudar de página?"
|
||||||
|
confirmChangeView: "Deseja mudar de seção?"
|
||||||
|
confirmUpdateEmojisDescription: "Atualizando {count} Emoji(s). Deseja continuar?"
|
||||||
|
confirmDeleteEmojisDescription: "Removendo {count} Emoji(s) marcado(s). Deseja continuar?"
|
||||||
|
confirmResetDescription: "Todas as mudanças serão redefinidas."
|
||||||
|
confirmMovePageDesciption: "Mudanças foram feitas nos Emojis dessa página. Se você sair sem salvar, todas serão descartadas."
|
||||||
|
dialogSelectRoleTitle: "Buscar por cargo que pode usar esse Emoji"
|
||||||
|
_register:
|
||||||
|
uploadSettingTitle: "Configurações de envio"
|
||||||
|
uploadSettingDescription: "Nessa tela, você pode configurar o comportamento ao enviar Emojis."
|
||||||
|
directoryToCategoryLabel: "Transformar as pastas em categorias"
|
||||||
|
directoryToCategoryCaption: "Quando você arrastar um diretório, converter o caminho das pastas no campo \"categoria\"."
|
||||||
|
emojiInputAreaCaption: "Selecione Emojis que você deseja registrar utilizando um dos métodos."
|
||||||
|
emojiInputAreaList1: "Arraste arquivos de imagem ou diretórios dentro desse quadro"
|
||||||
|
emojiInputAreaList2: "Clique nesse link para abrir a seleção de arquivos"
|
||||||
|
emojiInputAreaList3: "Clique nesse link para selecionar do drive"
|
||||||
|
confirmRegisterEmojisDescription: "Registrando os Emojis da lista como novos Emojis personalizados. Deseja continuar? (Para evitar sobrecarga, apenas {count} Emoji(s) podem ser registrados em uma única operação)"
|
||||||
|
confirmClearEmojisDescription: "Descartando edições e limpando Emojis da lista. Deseja continuar?"
|
||||||
|
confirmUploadEmojisDescription: "Enviando {count} arquivo(s) arrastados ao drive. Deseja continuar?"
|
||||||
_embedCodeGen:
|
_embedCodeGen:
|
||||||
title: "Personalizar código do embed"
|
title: "Personalizar código do embed"
|
||||||
header: "Exibir cabeçalho"
|
header: "Exibir cabeçalho"
|
||||||
|
@ -2758,7 +2969,36 @@ _remoteLookupErrors:
|
||||||
_noSuchObject:
|
_noSuchObject:
|
||||||
title: "Não encontrado"
|
title: "Não encontrado"
|
||||||
description: "O recurso solicitado não foi encontrado, confira o endereço."
|
description: "O recurso solicitado não foi encontrado, confira o endereço."
|
||||||
|
_captcha:
|
||||||
|
verify: "Por favor, verifique o CAPTCHA"
|
||||||
|
testSiteKeyMessage: "Você pode conferir a prévia inserindo valores de teste para o site e chaves secretas.\nVeja a página seguinte para mais detalhes."
|
||||||
|
_error:
|
||||||
|
_requestFailed:
|
||||||
|
title: "O pedido do CAPTCHA falhou"
|
||||||
|
text: "Por favor, tente novamente ou verifique as configurações."
|
||||||
|
_verificationFailed:
|
||||||
|
title: "A validação do CAPTCHA falhou"
|
||||||
|
text: "Por favor, verifique se as configurações estão corretas."
|
||||||
|
_unknown:
|
||||||
|
title: "Erro CAPTCHA"
|
||||||
|
text: "Houve um erro inexperado."
|
||||||
|
_bootErrors:
|
||||||
|
title: "Falha ao carregar"
|
||||||
|
serverError: "Se o problema persistir após esperar um momento e recarregar, contate a administração da instância com o seguinte ID de erro."
|
||||||
|
solution: "O seguinte pode resolver o problema."
|
||||||
|
solution1: "Atualize seu navegador e sistema operacional para a última versão."
|
||||||
|
solution2: "Desative o bloqueador de anúncios"
|
||||||
|
solution3: "Limpe o cache do navegador"
|
||||||
|
solution4: "Defina dom.webaudio.enabled como verdadeiro no Navegador Tor"
|
||||||
|
otherOption: "Outras opções"
|
||||||
|
otherOption1: "Excluir ajustes de cliente e cache"
|
||||||
|
otherOption2: "Iniciar o cliente simples"
|
||||||
|
otherOption3: "Iniciar ferramenta de reparo"
|
||||||
_search:
|
_search:
|
||||||
searchScopeAll: "Todos"
|
searchScopeAll: "Todos"
|
||||||
searchScopeLocal: "Local"
|
searchScopeLocal: "Local"
|
||||||
|
searchScopeServer: "Servidor específico"
|
||||||
searchScopeUser: "Usuário específico"
|
searchScopeUser: "Usuário específico"
|
||||||
|
pleaseEnterServerHost: "Insira o endereço do servidor"
|
||||||
|
pleaseSelectUser: "Selecione um usuário"
|
||||||
|
serverHostPlaceholder: "Exemplo: misskey.example.com"
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,6 +5,7 @@ introMisskey: "ยินดีต้อนรับทุกคนจ้า! Mis
|
||||||
poweredByMisskeyDescription: "{name} เป็นหนึ่งในเซิร์ฟเวอร์ของแพลตฟอร์มโอเพ่นซอร์ส <b>Misskey</b>"
|
poweredByMisskeyDescription: "{name} เป็นหนึ่งในเซิร์ฟเวอร์ของแพลตฟอร์มโอเพ่นซอร์ส <b>Misskey</b>"
|
||||||
monthAndDay: "{month}/{day}"
|
monthAndDay: "{month}/{day}"
|
||||||
search: "ค้นหา"
|
search: "ค้นหา"
|
||||||
|
reset: "รีเซ็ต"
|
||||||
notifications: "เเจ้งเตือน"
|
notifications: "เเจ้งเตือน"
|
||||||
username: "ชื่อผู้ใช้"
|
username: "ชื่อผู้ใช้"
|
||||||
password: "รหัสผ่าน"
|
password: "รหัสผ่าน"
|
||||||
|
@ -1288,8 +1289,11 @@ prohibitedWordsForNameOfUser: "คำนี้ไม่สามารถใช
|
||||||
prohibitedWordsForNameOfUserDescription: "หากมีสตริงใดๆ ในรายการนี้ปรากฏอยู่ในชื่อของผู้ใช้ ชื่อนั้นจะถูกปฏิเสธ ผู้ใช้ที่มีสิทธิ์แต่ผู้ดูแลระบบนั้นจะไม่ได้รับผลกระทบใดๆจากข้อจำกัดนี้ค่ะ"
|
prohibitedWordsForNameOfUserDescription: "หากมีสตริงใดๆ ในรายการนี้ปรากฏอยู่ในชื่อของผู้ใช้ ชื่อนั้นจะถูกปฏิเสธ ผู้ใช้ที่มีสิทธิ์แต่ผู้ดูแลระบบนั้นจะไม่ได้รับผลกระทบใดๆจากข้อจำกัดนี้ค่ะ"
|
||||||
yourNameContainsProhibitedWords: "ชื่อของคุณนั้นมีคำที่ต้องห้าม"
|
yourNameContainsProhibitedWords: "ชื่อของคุณนั้นมีคำที่ต้องห้าม"
|
||||||
yourNameContainsProhibitedWordsDescription: "ถ้าหากคุณต้องการใช้ชื่อนี้ กรุณาติดต่อผู้ดูแลระบบของเซิร์ฟเวอร์นะค่ะ"
|
yourNameContainsProhibitedWordsDescription: "ถ้าหากคุณต้องการใช้ชื่อนี้ กรุณาติดต่อผู้ดูแลระบบของเซิร์ฟเวอร์นะค่ะ"
|
||||||
|
federationDisabled: "เซิร์ฟเวอร์นี้ปิดการใช้งานการรวมกลุ่ม คุณไม่สามารถโต้ตอบกับผู้ใช้บนเซิร์ฟเวอร์อื่นได้"
|
||||||
postForm: "แบบฟอร์มการโพสต์"
|
postForm: "แบบฟอร์มการโพสต์"
|
||||||
information: "เกี่ยวกับ"
|
information: "เกี่ยวกับ"
|
||||||
|
right: "ขวา"
|
||||||
|
bottom: "ภายใต้"
|
||||||
_chat:
|
_chat:
|
||||||
invitations: "คำเชิญ"
|
invitations: "คำเชิญ"
|
||||||
noHistory: "ไม่มีประวัติ"
|
noHistory: "ไม่มีประวัติ"
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
---
|
---
|
||||||
_lang_: "Tiếng Nhật"
|
_lang_: "Tiếng Việt "
|
||||||
headlineMisskey: "Mạng xã hội liên hợp"
|
headlineMisskey: "Mạng xã hội liên hợp"
|
||||||
introMisskey: "Xin chào! Misskey là một nền tảng tiểu blog phi tập trung mã nguồn mở.\nViết \"tút\" để chia sẻ những suy nghĩ của bạn 📡\nBằng \"biểu cảm\", bạn có thể bày tỏ nhanh chóng cảm xúc của bạn với các tút 👍\nHãy khám phá một thế giới mới! 🚀"
|
introMisskey: "Xin chào! Misskey là một nền tảng tiểu blog phi tập trung mã nguồn mở.\nViết \"tút\" để chia sẻ những suy nghĩ của bạn 📡\nBằng \"biểu cảm\", bạn có thể bày tỏ nhanh chóng cảm xúc của bạn với các tút 👍\nHãy khám phá một thế giới mới! 🚀"
|
||||||
poweredByMisskeyDescription: "{name} là một trong những chủ máy của <b>Misskey</b> là nền tảng mã nguồn mở"
|
poweredByMisskeyDescription: "{name} là một trong những chủ máy của <b>Misskey</b> là nền tảng mã nguồn mở"
|
||||||
monthAndDay: "{day} tháng {month}"
|
monthAndDay: "{day} tháng {month}"
|
||||||
search: "Tìm kiếm"
|
search: "Tìm kiếm"
|
||||||
|
reset: "cài lại"
|
||||||
notifications: "Thông báo"
|
notifications: "Thông báo"
|
||||||
username: "Tên người dùng"
|
username: "Tên người dùng"
|
||||||
password: "Mật khẩu"
|
password: "Mật khẩu"
|
||||||
|
@ -48,9 +49,10 @@ pin: "Ghim"
|
||||||
unpin: "Bỏ ghim"
|
unpin: "Bỏ ghim"
|
||||||
copyContent: "Chép nội dung"
|
copyContent: "Chép nội dung"
|
||||||
copyLink: "Chép liên kết"
|
copyLink: "Chép liên kết"
|
||||||
|
copyRemoteLink: "Sao chép liên kết từ xa"
|
||||||
copyLinkRenote: "Sao chép liên kết ghi chú"
|
copyLinkRenote: "Sao chép liên kết ghi chú"
|
||||||
delete: "Xóa"
|
delete: "Xóa"
|
||||||
deleteAndEdit: "Sửa"
|
deleteAndEdit: "Xóa và soạn thảo lại"
|
||||||
deleteAndEditConfirm: "Bạn có chắc muốn sửa tút này? Những biểu cảm, lượt trả lời và đăng lại sẽ bị mất."
|
deleteAndEditConfirm: "Bạn có chắc muốn sửa tút này? Những biểu cảm, lượt trả lời và đăng lại sẽ bị mất."
|
||||||
addToList: "Thêm vào danh sách"
|
addToList: "Thêm vào danh sách"
|
||||||
addToAntenna: "Thêm vào Ăngten"
|
addToAntenna: "Thêm vào Ăngten"
|
||||||
|
@ -63,6 +65,7 @@ copyFileId: "Sao chép ID tập tin"
|
||||||
copyFolderId: "Sao chép ID thư mục"
|
copyFolderId: "Sao chép ID thư mục"
|
||||||
copyProfileUrl: "Sao chép URL hồ sơ"
|
copyProfileUrl: "Sao chép URL hồ sơ"
|
||||||
searchUser: "Tìm kiếm người dùng"
|
searchUser: "Tìm kiếm người dùng"
|
||||||
|
searchThisUsersNotes: "Tìm kiếm ghi chú của người dùng"
|
||||||
reply: "Trả lời"
|
reply: "Trả lời"
|
||||||
loadMore: "Tải thêm"
|
loadMore: "Tải thêm"
|
||||||
showMore: "Xem thêm"
|
showMore: "Xem thêm"
|
||||||
|
@ -111,6 +114,7 @@ enterEmoji: "Chèn emoji"
|
||||||
renote: "Đăng lại"
|
renote: "Đăng lại"
|
||||||
unrenote: "Hủy đăng lại"
|
unrenote: "Hủy đăng lại"
|
||||||
renoted: "Đã đăng lại."
|
renoted: "Đã đăng lại."
|
||||||
|
renotedToX: "Đã cho thuê lại {name}."
|
||||||
cantRenote: "Không thể đăng lại tút này."
|
cantRenote: "Không thể đăng lại tút này."
|
||||||
cantReRenote: "Không thể đăng lại một tút đăng lại."
|
cantReRenote: "Không thể đăng lại một tút đăng lại."
|
||||||
quote: "Trích dẫn"
|
quote: "Trích dẫn"
|
||||||
|
@ -125,6 +129,7 @@ add: "Thêm"
|
||||||
reaction: "Biểu cảm"
|
reaction: "Biểu cảm"
|
||||||
reactions: "Biểu cảm"
|
reactions: "Biểu cảm"
|
||||||
emojiPicker: "Bộ chọn biểu tượng cảm xúc"
|
emojiPicker: "Bộ chọn biểu tượng cảm xúc"
|
||||||
|
emojiPickerDisplay: "Hiển thị bộ chọn"
|
||||||
reactionSettingDescription2: "Kéo để sắp xếp, nhấn để xóa, nhấn \"+\" để thêm."
|
reactionSettingDescription2: "Kéo để sắp xếp, nhấn để xóa, nhấn \"+\" để thêm."
|
||||||
rememberNoteVisibility: "Lưu kiểu tút mặc định"
|
rememberNoteVisibility: "Lưu kiểu tút mặc định"
|
||||||
attachCancel: "Gỡ tập tin đính kèm"
|
attachCancel: "Gỡ tập tin đính kèm"
|
||||||
|
@ -175,6 +180,8 @@ addAccount: "Thêm tài khoản"
|
||||||
reloadAccountsList: "Cập nhật danh sách tài khoản"
|
reloadAccountsList: "Cập nhật danh sách tài khoản"
|
||||||
loginFailed: "Đăng nhập không thành công"
|
loginFailed: "Đăng nhập không thành công"
|
||||||
showOnRemote: "Truy cập trang của người này"
|
showOnRemote: "Truy cập trang của người này"
|
||||||
|
continueOnRemote: "Tiếp tục trên phiên bản từ xa"
|
||||||
|
chooseServerOnMisskeyHub: "Chọn một máy chủ từ Misskey Hub"
|
||||||
general: "Tổng quan"
|
general: "Tổng quan"
|
||||||
wallpaper: "Ảnh bìa"
|
wallpaper: "Ảnh bìa"
|
||||||
setWallpaper: "Đặt ảnh bìa"
|
setWallpaper: "Đặt ảnh bìa"
|
||||||
|
@ -185,6 +192,7 @@ followConfirm: "Bạn theo dõi {name}?"
|
||||||
proxyAccount: "Tài khoản proxy"
|
proxyAccount: "Tài khoản proxy"
|
||||||
proxyAccountDescription: "Tài khoản proxy là tài khoản hoạt động như một người theo dõi từ xa cho người dùng trong những điều kiện nhất định. Ví dụ: khi người dùng thêm người dùng từ xa vào danh sách, hoạt động của người dùng từ xa sẽ không được chuyển đến phiên bản nếu không có người dùng cục bộ nào theo dõi người dùng đó, vì vậy tài khoản proxy sẽ theo dõi."
|
proxyAccountDescription: "Tài khoản proxy là tài khoản hoạt động như một người theo dõi từ xa cho người dùng trong những điều kiện nhất định. Ví dụ: khi người dùng thêm người dùng từ xa vào danh sách, hoạt động của người dùng từ xa sẽ không được chuyển đến phiên bản nếu không có người dùng cục bộ nào theo dõi người dùng đó, vì vậy tài khoản proxy sẽ theo dõi."
|
||||||
host: "Host"
|
host: "Host"
|
||||||
|
selectSelf: "Chọn chính bạn"
|
||||||
selectUser: "Chọn người dùng"
|
selectUser: "Chọn người dùng"
|
||||||
recipient: "Người nhận"
|
recipient: "Người nhận"
|
||||||
annotation: "Bình luận"
|
annotation: "Bình luận"
|
||||||
|
@ -199,6 +207,7 @@ perHour: "Mỗi Giờ"
|
||||||
perDay: "Mỗi Ngày"
|
perDay: "Mỗi Ngày"
|
||||||
stopActivityDelivery: "Ngưng gửi hoạt động"
|
stopActivityDelivery: "Ngưng gửi hoạt động"
|
||||||
blockThisInstance: "Chặn máy chủ này"
|
blockThisInstance: "Chặn máy chủ này"
|
||||||
|
silenceThisInstance: "Máy chủ im lặng"
|
||||||
operations: "Vận hành"
|
operations: "Vận hành"
|
||||||
software: "Phần mềm"
|
software: "Phần mềm"
|
||||||
version: "Phiên bản"
|
version: "Phiên bản"
|
||||||
|
@ -218,6 +227,8 @@ clearCachedFiles: "Xóa bộ nhớ đệm"
|
||||||
clearCachedFilesConfirm: "Bạn có chắc muốn xóa sạch bộ nhớ đệm?"
|
clearCachedFilesConfirm: "Bạn có chắc muốn xóa sạch bộ nhớ đệm?"
|
||||||
blockedInstances: "Máy chủ đã chặn"
|
blockedInstances: "Máy chủ đã chặn"
|
||||||
blockedInstancesDescription: "Danh sách những máy chủ bạn muốn chặn. Chúng sẽ không thể giao tiếp với máy chủy này nữa."
|
blockedInstancesDescription: "Danh sách những máy chủ bạn muốn chặn. Chúng sẽ không thể giao tiếp với máy chủy này nữa."
|
||||||
|
silencedInstances: "Máy chủ im lặng"
|
||||||
|
silencedInstancesDescription: "Đặt máy chủ mà bạn muốn tắt tiếng, phân tách bằng dấu xuống dòng. Tất cả tài khoản trên máy chủ bị tắt tiếng sẽ được coi là \"bị tắt tiếng\" và mọi hành động theo dõi sẽ được coi là yêu cầu. Không có tác dụng với những trường hợp bị chặn."
|
||||||
muteAndBlock: "Ẩn và Chặn"
|
muteAndBlock: "Ẩn và Chặn"
|
||||||
mutedUsers: "Người đã ẩn"
|
mutedUsers: "Người đã ẩn"
|
||||||
blockedUsers: "Người đã chặn"
|
blockedUsers: "Người đã chặn"
|
||||||
|
@ -254,8 +265,8 @@ more: "Thêm nữa!"
|
||||||
featured: "Nổi bật"
|
featured: "Nổi bật"
|
||||||
usernameOrUserId: "Tên người dùng hoặc ID"
|
usernameOrUserId: "Tên người dùng hoặc ID"
|
||||||
noSuchUser: "Không tìm thấy người dùng"
|
noSuchUser: "Không tìm thấy người dùng"
|
||||||
lookup: "Tìm kiếm"
|
lookup: "Tra cứu"
|
||||||
announcements: "Thông báo"
|
announcements: "Thông báo máy chủ"
|
||||||
imageUrl: "URL ảnh"
|
imageUrl: "URL ảnh"
|
||||||
remove: "Xóa"
|
remove: "Xóa"
|
||||||
removed: "Đã xóa"
|
removed: "Đã xóa"
|
||||||
|
@ -426,6 +437,7 @@ totpDescription: "Nhắn mã OTP bằng ứng dụng xác thực"
|
||||||
moderator: "Kiểm duyệt viên"
|
moderator: "Kiểm duyệt viên"
|
||||||
moderation: "Kiểm duyệt"
|
moderation: "Kiểm duyệt"
|
||||||
moderationNote: "Ghi chú kiểm duyệt"
|
moderationNote: "Ghi chú kiểm duyệt"
|
||||||
|
moderationNoteDescription: "Bạn có thể điền vào những ghi chú chỉ được chia sẻ giữa những người kiểm duyệt."
|
||||||
addModerationNote: "Thêm ghi chú kiểm duyệt"
|
addModerationNote: "Thêm ghi chú kiểm duyệt"
|
||||||
moderationLogs: "Nhật kí quản trị"
|
moderationLogs: "Nhật kí quản trị"
|
||||||
nUsersMentioned: "Dùng bởi {n} người"
|
nUsersMentioned: "Dùng bởi {n} người"
|
||||||
|
@ -546,6 +558,7 @@ volume: "Âm lượng"
|
||||||
masterVolume: "Âm thanh chung"
|
masterVolume: "Âm thanh chung"
|
||||||
notUseSound: "Tắt tiếng"
|
notUseSound: "Tắt tiếng"
|
||||||
details: "Chi tiết"
|
details: "Chi tiết"
|
||||||
|
renoteDetails: "Tìm hiểu thêm về đăng lại "
|
||||||
chooseEmoji: "Chọn emoji"
|
chooseEmoji: "Chọn emoji"
|
||||||
unableToProcess: "Không thể hoàn tất hành động"
|
unableToProcess: "Không thể hoàn tất hành động"
|
||||||
recentUsed: "Sử dụng gần đây"
|
recentUsed: "Sử dụng gần đây"
|
||||||
|
@ -644,9 +657,11 @@ regexpError: "Lỗi biểu thức"
|
||||||
regexpErrorDescription: "Xảy ra lỗi biểu thức ở dòng {line} của {tab} chữ ẩn:"
|
regexpErrorDescription: "Xảy ra lỗi biểu thức ở dòng {line} của {tab} chữ ẩn:"
|
||||||
instanceMute: "Những máy chủ ẩn"
|
instanceMute: "Những máy chủ ẩn"
|
||||||
userSaysSomething: "{name} nói gì đó"
|
userSaysSomething: "{name} nói gì đó"
|
||||||
|
userSaysSomethingAbout: "{name} đã nói gì đó về \"{word}\""
|
||||||
makeActive: "Kích hoạt"
|
makeActive: "Kích hoạt"
|
||||||
display: "Hiển thị"
|
display: "Hiển thị"
|
||||||
copy: "Sao chép"
|
copy: "Sao chép"
|
||||||
|
copiedToClipboard: "Đã sao chép vào clipboard"
|
||||||
metrics: "Số liệu"
|
metrics: "Số liệu"
|
||||||
overview: "Tổng quan"
|
overview: "Tổng quan"
|
||||||
logs: "Nhật ký"
|
logs: "Nhật ký"
|
||||||
|
@ -1003,10 +1018,14 @@ copyErrorInfo: "Sao chép thông tin lỗi"
|
||||||
joinThisServer: "Đăng ký trên chủ máy này"
|
joinThisServer: "Đăng ký trên chủ máy này"
|
||||||
exploreOtherServers: "Tìm chủ máy khác"
|
exploreOtherServers: "Tìm chủ máy khác"
|
||||||
letsLookAtTimeline: "Thử xem Timeline"
|
letsLookAtTimeline: "Thử xem Timeline"
|
||||||
|
disableFederationConfirm: "Bạn có muốn làm điều đó mà không cần liên minh không?"
|
||||||
|
disableFederationConfirmWarn: "Ngay cả khi bị trì hoãn, bài đăng vẫn sẽ tiếp tục là công khai trừ khi được thiết lập khác. Bạn thường không cần phải làm điều này."
|
||||||
disableFederationOk: "Vô hiệu hoá"
|
disableFederationOk: "Vô hiệu hoá"
|
||||||
|
invitationRequiredToRegister: "Phiên bản này chỉ dành cho người được mời. Bạn phải nhập mã mời hợp lệ để đăng ký."
|
||||||
emailNotSupported: "Máy chủ này không hỗ trợ gửi email"
|
emailNotSupported: "Máy chủ này không hỗ trợ gửi email"
|
||||||
postToTheChannel: "Đăng lên kênh"
|
postToTheChannel: "Đăng lên kênh"
|
||||||
cannotBeChangedLater: "Không thể thay đổi sau này."
|
cannotBeChangedLater: "Không thể thay đổi sau này."
|
||||||
|
reactionAcceptance: "Phản ứng chấp nhận"
|
||||||
likeOnly: "Chỉ lượt thích"
|
likeOnly: "Chỉ lượt thích"
|
||||||
rolesAssignedToMe: "Vai trò được giao cho tôi"
|
rolesAssignedToMe: "Vai trò được giao cho tôi"
|
||||||
resetPasswordConfirm: "Bạn thực sự muốn đặt lại mật khẩu?"
|
resetPasswordConfirm: "Bạn thực sự muốn đặt lại mật khẩu?"
|
||||||
|
@ -1049,6 +1068,8 @@ options: "Tùy chọn"
|
||||||
specifyUser: "Người dùng chỉ định"
|
specifyUser: "Người dùng chỉ định"
|
||||||
failedToPreviewUrl: "Không thể xem trước"
|
failedToPreviewUrl: "Không thể xem trước"
|
||||||
update: "Cập nhật"
|
update: "Cập nhật"
|
||||||
|
cancelReactionConfirm: "Bạn có muốn hủy phản ứng của mình không?"
|
||||||
|
changeReactionConfirm: "Bạn có muốn thay đổi phản ứng của mình không?"
|
||||||
later: "Để sau"
|
later: "Để sau"
|
||||||
goToMisskey: "Tới Misskey"
|
goToMisskey: "Tới Misskey"
|
||||||
installed: "Đã tải xuống"
|
installed: "Đã tải xuống"
|
||||||
|
@ -1097,6 +1118,7 @@ mutualFollow: "Theo dõi lẫn nhau"
|
||||||
followingOrFollower: "Đang theo dõi hoặc người theo dõi"
|
followingOrFollower: "Đang theo dõi hoặc người theo dõi"
|
||||||
externalServices: "Các dịch vụ bên ngoài"
|
externalServices: "Các dịch vụ bên ngoài"
|
||||||
sourceCode: "Mã nguồn"
|
sourceCode: "Mã nguồn"
|
||||||
|
repositoryUrlDescription: "Nếu bạn có kho lưu trữ mã nguồn có thể truy cập công khai, hãy nhập URL. Nếu bạn đang sử dụng Misskey theo mặc định (không thực hiện bất kỳ thay đổi nào đối với mã nguồn), hãy nhập https://github.com/misskey-dev/misskey."
|
||||||
feedback: "Phản hồi"
|
feedback: "Phản hồi"
|
||||||
feedbackUrl: "URL phản hồi"
|
feedbackUrl: "URL phản hồi"
|
||||||
privacyPolicy: "Chính sách bảo mật"
|
privacyPolicy: "Chính sách bảo mật"
|
||||||
|
@ -1113,8 +1135,18 @@ releaseToRefresh: "Thả để làm mới"
|
||||||
refreshing: "Đang làm mới"
|
refreshing: "Đang làm mới"
|
||||||
pullDownToRefresh: "Kéo xuống để làm mới"
|
pullDownToRefresh: "Kéo xuống để làm mới"
|
||||||
cwNotationRequired: "Nếu \"Ẩn nội dung\" được bật thì cần phải có chú thích."
|
cwNotationRequired: "Nếu \"Ẩn nội dung\" được bật thì cần phải có chú thích."
|
||||||
|
decorate: "Trang trí"
|
||||||
lastNDays: "{n} ngày trước"
|
lastNDays: "{n} ngày trước"
|
||||||
|
userSaysSomethingSensitive: "Bài đăng có chứa các tập tin nhạy cảm từ {name}"
|
||||||
surrender: "Từ chối"
|
surrender: "Từ chối"
|
||||||
|
signinWithPasskey: "Đăng nhập bằng mật khẩu của bạn"
|
||||||
|
passkeyVerificationFailed: "Xác minh mật khẩu không thành công."
|
||||||
|
messageToFollower: "Tin nhắn cho người theo dõi"
|
||||||
|
yourNameContainsProhibitedWords: "Tên bạn đang cố gắng đổi có chứa chuỗi ký tự bị cấm."
|
||||||
|
yourNameContainsProhibitedWordsDescription: "Tên có chứa chuỗi ký tự bị cấm. Nếu bạn muốn sử dụng tên này, hãy liên hệ với quản trị viên máy chủ của bạn."
|
||||||
|
federationDisabled: "Liên kết bị vô hiệu hóa trên máy chủ này. Bạn không thể tương tác với người dùng trên các máy chủ khác."
|
||||||
|
reactAreYouSure: "Bạn có muốn phản hồi với \" {emoji} \" không?"
|
||||||
|
paste: "dán"
|
||||||
postForm: "Mẫu đăng"
|
postForm: "Mẫu đăng"
|
||||||
information: "Giới thiệu"
|
information: "Giới thiệu"
|
||||||
_chat:
|
_chat:
|
||||||
|
@ -1123,6 +1155,9 @@ _chat:
|
||||||
members: "Thành viên"
|
members: "Thành viên"
|
||||||
home: "Trang chính"
|
home: "Trang chính"
|
||||||
send: "Gửi"
|
send: "Gửi"
|
||||||
|
_accountSettings:
|
||||||
|
requireSigninToViewContents: "Yêu cầu đăng nhập để xem nội dung"
|
||||||
|
requireSigninToViewContentsDescription1: "Yêu cầu đăng nhập để xem tất cả ghi chú và nội dung khác mà bạn tạo. Điều này được kỳ vọng sẽ có hiệu quả trong việc ngăn chặn thông tin bị thu thập bởi các trình thu thập thông tin."
|
||||||
_delivery:
|
_delivery:
|
||||||
stop: "Đã vô hiệu hóa"
|
stop: "Đã vô hiệu hóa"
|
||||||
_type:
|
_type:
|
||||||
|
@ -1146,8 +1181,33 @@ _initialAccountSetting:
|
||||||
pushNotificationDescription: "Bật thông báo đẩy sẽ cho phép bạn nhận thông báo từ {name} trực tiếp từ thiết bị của bạn."
|
pushNotificationDescription: "Bật thông báo đẩy sẽ cho phép bạn nhận thông báo từ {name} trực tiếp từ thiết bị của bạn."
|
||||||
initialAccountSettingCompleted: "Thiết lập tài khoản thành công!"
|
initialAccountSettingCompleted: "Thiết lập tài khoản thành công!"
|
||||||
haveFun: "Hãy tận hưởng {name} nhé!"
|
haveFun: "Hãy tận hưởng {name} nhé!"
|
||||||
|
youCanContinueTutorial: "Bạn có thể tiếp tục xem hướng dẫn về cách sử dụng {name} (Misskey) hoặc bạn có thể thoát khỏi phần thiết lập tại đây và bắt đầu sử dụng ngay lập tức."
|
||||||
|
startTutorial: "Bắt đầu hướng dẫn"
|
||||||
skipAreYouSure: "Bạn thực sự muốn bỏ qua mục thiết lập tài khoản?"
|
skipAreYouSure: "Bạn thực sự muốn bỏ qua mục thiết lập tài khoản?"
|
||||||
laterAreYouSure: "Bạn thực sự muốn thiết lập tài khoản vào lúc khác?"
|
laterAreYouSure: "Bạn thực sự muốn thiết lập tài khoản vào lúc khác?"
|
||||||
|
_initialTutorial:
|
||||||
|
launchTutorial: "Bắt đầu hướng dẫn"
|
||||||
|
title: "Hướng dẫn"
|
||||||
|
wellDone: "Làm tốt!"
|
||||||
|
skipAreYouSure: "Thoát khỏi hướng dẫn?"
|
||||||
|
_landing:
|
||||||
|
title: "Chào mừng đến với Hướng dẫn"
|
||||||
|
description: "Tại đây, bạn có thể tìm hiểu những điều cơ bản về cách sử dụng Misskey và các tính năng của nó."
|
||||||
|
_note:
|
||||||
|
title: "Bài Viết là gì?"
|
||||||
|
description: "Các bài đăng trên Misskey được gọi là 'Bài Viết'. Ghi chú được sắp xếp theo thứ tự thời gian trên dòng thời gian và được cập nhật theo thời gian thực."
|
||||||
|
_timeline:
|
||||||
|
home: "Bạn có thể xem ghi chú từ những tài khoản bạn theo dõi."
|
||||||
|
local: "Bạn có thể xem ghi chú từ tất cả người dùng trên máy chủ này."
|
||||||
|
social: "Ghi chú từ dòng thời gian Trang chủ và Địa phương sẽ được hiển thị."
|
||||||
|
global: "Bạn có thể xem ghi chú từ tất cả các máy chủ được kết nối."
|
||||||
|
_postNote:
|
||||||
|
_visibility:
|
||||||
|
home: "Chỉ công khai trên dòng thời gian Trang chủ. Những người truy cập trang cá nhân của bạn, thông qua người theo dõi và thông qua ghi chú lại có thể thấy thông tin đó."
|
||||||
|
_timelineDescription:
|
||||||
|
home: "Trong dòng thời gian Trang chính, bạn có thể xem ghi chú từ các tài khoản bạn theo dõi."
|
||||||
|
local: "Trong dòng thời gian cục bộ, bạn có thể xem ghi chú từ tất cả người dùng trên máy chủ này."
|
||||||
|
social: "Dòng thời gian Xã hội hiển thị các ghi chú từ cả dòng thời gian Trang chủ và Địa phương."
|
||||||
_serverSettings:
|
_serverSettings:
|
||||||
iconUrl: "Biểu tượng URL"
|
iconUrl: "Biểu tượng URL"
|
||||||
appIconResolutionMustBe: "Độ phân giải tối thiểu là {resolution}."
|
appIconResolutionMustBe: "Độ phân giải tối thiểu là {resolution}."
|
||||||
|
@ -1308,7 +1368,7 @@ _achievements:
|
||||||
_postedAt0min0sec:
|
_postedAt0min0sec:
|
||||||
title: "Tín hiệu báo giờ"
|
title: "Tín hiệu báo giờ"
|
||||||
description: "Đăng bài vào 0 phút 0 giây"
|
description: "Đăng bài vào 0 phút 0 giây"
|
||||||
flavor: "Piiiiiii ĐÂY LÀ TIẾNG NÓI VIỆT NAM"
|
flavor: "Pin pop pop pop"
|
||||||
_selfQuote:
|
_selfQuote:
|
||||||
title: "Nói đến bản thân"
|
title: "Nói đến bản thân"
|
||||||
description: "Trích dẫn bài viết của mình"
|
description: "Trích dẫn bài viết của mình"
|
||||||
|
@ -1923,11 +1983,21 @@ _abuseReport:
|
||||||
_recipientType:
|
_recipientType:
|
||||||
mail: "Email"
|
mail: "Email"
|
||||||
_moderationLogTypes:
|
_moderationLogTypes:
|
||||||
|
createRole: "Tạo một vai trò"
|
||||||
|
deleteRole: "Xóa vai trò"
|
||||||
|
updateRole: "Cập nhật vai trò"
|
||||||
|
assignRole: "Chỉ định cho vai trò"
|
||||||
|
unassignRole: "Bỏ gán vai trò"
|
||||||
suspend: "Vô hiệu hóa"
|
suspend: "Vô hiệu hóa"
|
||||||
|
unsuspend: "Rã đông"
|
||||||
resetPassword: "Đặt lại mật khẩu"
|
resetPassword: "Đặt lại mật khẩu"
|
||||||
createInvitation: "Tạo lời mời"
|
createInvitation: "Tạo lời mời"
|
||||||
_reversi:
|
_reversi:
|
||||||
total: "Tổng cộng"
|
total: "Tổng cộng"
|
||||||
|
_customEmojisManager:
|
||||||
|
_local:
|
||||||
|
_list:
|
||||||
|
confirmDeleteEmojisDescription: "Xóa các biểu tượng cảm xúc {count} đã chọn. Bạn có muốn chạy nó không?"
|
||||||
_remoteLookupErrors:
|
_remoteLookupErrors:
|
||||||
_noSuchObject:
|
_noSuchObject:
|
||||||
title: "Không tìm thấy"
|
title: "Không tìm thấy"
|
||||||
|
|
|
@ -979,6 +979,7 @@ document: "文档"
|
||||||
numberOfPageCache: "缓存页数"
|
numberOfPageCache: "缓存页数"
|
||||||
numberOfPageCacheDescription: "设置较高的值会更方便用户,但设备的负载和内存使用量会增加。"
|
numberOfPageCacheDescription: "设置较高的值会更方便用户,但设备的负载和内存使用量会增加。"
|
||||||
logoutConfirm: "是否确认登出?"
|
logoutConfirm: "是否确认登出?"
|
||||||
|
logoutWillClearClientData: "登出时将会从浏览器中删除客户端的设置信息。如果想要在再次登入时恢复设置信息,请在设置里打开自动备份。"
|
||||||
lastActiveDate: "最后活跃时间"
|
lastActiveDate: "最后活跃时间"
|
||||||
statusbar: "状态栏"
|
statusbar: "状态栏"
|
||||||
pleaseSelect: "请选择"
|
pleaseSelect: "请选择"
|
||||||
|
@ -1344,6 +1345,7 @@ embed: "嵌入"
|
||||||
settingsMigrating: "正在迁移设置,请稍候。(之后也可以在设置 → 其它 → 迁移旧设置来手动迁移)"
|
settingsMigrating: "正在迁移设置,请稍候。(之后也可以在设置 → 其它 → 迁移旧设置来手动迁移)"
|
||||||
readonly: "只读"
|
readonly: "只读"
|
||||||
goToDeck: "返回至 Deck"
|
goToDeck: "返回至 Deck"
|
||||||
|
federationJobs: "联合作业"
|
||||||
_chat:
|
_chat:
|
||||||
noMessagesYet: "还没有消息"
|
noMessagesYet: "还没有消息"
|
||||||
newMessage: "新消息"
|
newMessage: "新消息"
|
||||||
|
@ -1912,6 +1914,7 @@ _role:
|
||||||
canManageCustomEmojis: "管理自定义表情符号"
|
canManageCustomEmojis: "管理自定义表情符号"
|
||||||
canManageAvatarDecorations: "管理头像挂件"
|
canManageAvatarDecorations: "管理头像挂件"
|
||||||
driveCapacity: "网盘容量"
|
driveCapacity: "网盘容量"
|
||||||
|
maxFileSize: "可上传的最大文件大小"
|
||||||
alwaysMarkNsfw: "总是将文件标记为 NSFW"
|
alwaysMarkNsfw: "总是将文件标记为 NSFW"
|
||||||
canUpdateBioMedia: "可以更新头像和横幅"
|
canUpdateBioMedia: "可以更新头像和横幅"
|
||||||
pinMax: "帖子置顶数量限制"
|
pinMax: "帖子置顶数量限制"
|
||||||
|
@ -2367,6 +2370,7 @@ _widgets:
|
||||||
chooseList: "选择列表"
|
chooseList: "选择列表"
|
||||||
clicker: "点击器"
|
clicker: "点击器"
|
||||||
birthdayFollowings: "今天是他们的生日"
|
birthdayFollowings: "今天是他们的生日"
|
||||||
|
chat: "聊天"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "隐藏"
|
hide: "隐藏"
|
||||||
show: "查看更多"
|
show: "查看更多"
|
||||||
|
@ -2633,6 +2637,7 @@ _deck:
|
||||||
mentions: "提及"
|
mentions: "提及"
|
||||||
direct: "指定用户"
|
direct: "指定用户"
|
||||||
roleTimeline: "角色时间线"
|
roleTimeline: "角色时间线"
|
||||||
|
chat: "聊天"
|
||||||
_dialog:
|
_dialog:
|
||||||
charactersExceeded: "已经超过了最大字符数! 当前字符数 {current} / 限制字符数 {max}"
|
charactersExceeded: "已经超过了最大字符数! 当前字符数 {current} / 限制字符数 {max}"
|
||||||
charactersBelow: "低于最小字符数!当前字符数 {current} / 限制字符数 {min}"
|
charactersBelow: "低于最小字符数!当前字符数 {current} / 限制字符数 {min}"
|
||||||
|
|
|
@ -424,6 +424,7 @@ antennaExcludeBots: "排除機器人帳戶"
|
||||||
antennaKeywordsDescription: "空格代表「以及」(AND),換行代表「或者」(OR)"
|
antennaKeywordsDescription: "空格代表「以及」(AND),換行代表「或者」(OR)"
|
||||||
notifyAntenna: "通知有新貼文"
|
notifyAntenna: "通知有新貼文"
|
||||||
withFileAntenna: "僅帶有附件的貼文"
|
withFileAntenna: "僅帶有附件的貼文"
|
||||||
|
excludeNotesInSensitiveChannel: "排除敏感頻道的貼文"
|
||||||
enableServiceworker: "啟用瀏覽器的推播通知"
|
enableServiceworker: "啟用瀏覽器的推播通知"
|
||||||
antennaUsersDescription: "填寫使用者名稱,以換行分隔"
|
antennaUsersDescription: "填寫使用者名稱,以換行分隔"
|
||||||
caseSensitive: "區分大小寫"
|
caseSensitive: "區分大小寫"
|
||||||
|
@ -978,6 +979,7 @@ document: "文件"
|
||||||
numberOfPageCache: "快取頁面數"
|
numberOfPageCache: "快取頁面數"
|
||||||
numberOfPageCacheDescription: "增加數量會提高便利性,但也會增加負荷與記憶體使用量。"
|
numberOfPageCacheDescription: "增加數量會提高便利性,但也會增加負荷與記憶體使用量。"
|
||||||
logoutConfirm: "確定要登出嗎?"
|
logoutConfirm: "確定要登出嗎?"
|
||||||
|
logoutWillClearClientData: "當您登出時,客戶端的設定資訊將從瀏覽器中清除。為了能夠在重新登入時恢復您的設定資訊,請啟用設定內的自動備份選項。"
|
||||||
lastActiveDate: "上次使用日期及時間"
|
lastActiveDate: "上次使用日期及時間"
|
||||||
statusbar: "狀態列"
|
statusbar: "狀態列"
|
||||||
pleaseSelect: "請選擇"
|
pleaseSelect: "請選擇"
|
||||||
|
@ -1307,7 +1309,7 @@ availableRoles: "可用角色"
|
||||||
acknowledgeNotesAndEnable: "了解注意事項後再開啟。"
|
acknowledgeNotesAndEnable: "了解注意事項後再開啟。"
|
||||||
federationSpecified: "此伺服器以白名單聯邦的方式運作。除了管理員指定的伺服器外,它無法與其他伺服器互動。"
|
federationSpecified: "此伺服器以白名單聯邦的方式運作。除了管理員指定的伺服器外,它無法與其他伺服器互動。"
|
||||||
federationDisabled: "此伺服器未開啟站台聯邦。無法與其他伺服器上的使用者互動。"
|
federationDisabled: "此伺服器未開啟站台聯邦。無法與其他伺服器上的使用者互動。"
|
||||||
confirmOnReact: "反應時確認"
|
confirmOnReact: "在做出反應前先確認"
|
||||||
reactAreYouSure: "用「 {emoji} 」反應嗎?"
|
reactAreYouSure: "用「 {emoji} 」反應嗎?"
|
||||||
markAsSensitiveConfirm: "要將這個媒體設定為敏感嗎?"
|
markAsSensitiveConfirm: "要將這個媒體設定為敏感嗎?"
|
||||||
unmarkAsSensitiveConfirm: "要解除這個媒體的敏感設定嗎?"
|
unmarkAsSensitiveConfirm: "要解除這個媒體的敏感設定嗎?"
|
||||||
|
@ -1342,6 +1344,8 @@ top: "上"
|
||||||
embed: "嵌入"
|
embed: "嵌入"
|
||||||
settingsMigrating: "正在移轉設定。請稍候……(之後也可以到「設定 → 其他 → 舊設定資訊移轉」中手動進行移轉)"
|
settingsMigrating: "正在移轉設定。請稍候……(之後也可以到「設定 → 其他 → 舊設定資訊移轉」中手動進行移轉)"
|
||||||
readonly: "唯讀"
|
readonly: "唯讀"
|
||||||
|
goToDeck: "回去甲板"
|
||||||
|
federationJobs: "聯邦通訊作業"
|
||||||
_chat:
|
_chat:
|
||||||
noMessagesYet: "尚無訊息"
|
noMessagesYet: "尚無訊息"
|
||||||
newMessage: "新訊息"
|
newMessage: "新訊息"
|
||||||
|
@ -1444,7 +1448,7 @@ _accountSettings:
|
||||||
makeNotesHiddenBefore: "隱藏過去的貼文"
|
makeNotesHiddenBefore: "隱藏過去的貼文"
|
||||||
makeNotesHiddenBeforeDescription: "啟用此功能後,超過設定的日期和時間或超過設定時間的貼文將僅對自己顯示(私密化)。 如果您再次停用它,貼文的公開狀態也會恢復原狀。"
|
makeNotesHiddenBeforeDescription: "啟用此功能後,超過設定的日期和時間或超過設定時間的貼文將僅對自己顯示(私密化)。 如果您再次停用它,貼文的公開狀態也會恢復原狀。"
|
||||||
mayNotEffectForFederatedNotes: "聯邦發送至遠端伺服器的貼文可能會不受影響。"
|
mayNotEffectForFederatedNotes: "聯邦發送至遠端伺服器的貼文可能會不受影響。"
|
||||||
mayNotEffectSomeSituations: "這些限制已經簡化。它們可能不適用於某些情況,例如在遠端伺服器上檢視或管理時。"
|
mayNotEffectSomeSituations: "這些限制僅是簡化版本。在某些情況下,例如在遠端伺服器上瀏覽或進行審核時,可能不會套用這些限制。"
|
||||||
notesHavePassedSpecifiedPeriod: "早於指定時間的貼文"
|
notesHavePassedSpecifiedPeriod: "早於指定時間的貼文"
|
||||||
notesOlderThanSpecifiedDateAndTime: "指定時間和日期之前的貼文"
|
notesOlderThanSpecifiedDateAndTime: "指定時間和日期之前的貼文"
|
||||||
_abuseUserReport:
|
_abuseUserReport:
|
||||||
|
@ -1911,6 +1915,7 @@ _role:
|
||||||
canManageCustomEmojis: "管理自訂表情符號"
|
canManageCustomEmojis: "管理自訂表情符號"
|
||||||
canManageAvatarDecorations: "管理頭像裝飾"
|
canManageAvatarDecorations: "管理頭像裝飾"
|
||||||
driveCapacity: "雲端硬碟容量"
|
driveCapacity: "雲端硬碟容量"
|
||||||
|
maxFileSize: "可上傳的最大檔案大小"
|
||||||
alwaysMarkNsfw: "總是將檔案標記為NSFW"
|
alwaysMarkNsfw: "總是將檔案標記為NSFW"
|
||||||
canUpdateBioMedia: "允許更新大頭貼和橫幅"
|
canUpdateBioMedia: "允許更新大頭貼和橫幅"
|
||||||
pinMax: "置頂貼文的最大數量"
|
pinMax: "置頂貼文的最大數量"
|
||||||
|
@ -2366,6 +2371,7 @@ _widgets:
|
||||||
chooseList: "選擇清單"
|
chooseList: "選擇清單"
|
||||||
clicker: "點擊器"
|
clicker: "點擊器"
|
||||||
birthdayFollowings: "今天生日的使用者"
|
birthdayFollowings: "今天生日的使用者"
|
||||||
|
chat: "聊天"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "隱藏"
|
hide: "隱藏"
|
||||||
show: "顯示內容"
|
show: "顯示內容"
|
||||||
|
@ -2632,6 +2638,7 @@ _deck:
|
||||||
mentions: "提及"
|
mentions: "提及"
|
||||||
direct: "指定使用者"
|
direct: "指定使用者"
|
||||||
roleTimeline: "角色時間軸"
|
roleTimeline: "角色時間軸"
|
||||||
|
chat: "聊天"
|
||||||
_dialog:
|
_dialog:
|
||||||
charactersExceeded: "您的貼文太長了!現時字數 {current}/限制字數 {max}"
|
charactersExceeded: "您的貼文太長了!現時字數 {current}/限制字數 {max}"
|
||||||
charactersBelow: "您的貼文太短了!現時字數 {current}/限制字數 {min}"
|
charactersBelow: "您的貼文太短了!現時字數 {current}/限制字數 {min}"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2025.4.0",
|
"version": "2025.4.1-beta.5",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -84,8 +84,7 @@
|
||||||
"@aiscript-dev/aiscript-languageserver": "-"
|
"@aiscript-dev/aiscript-languageserver": "-"
|
||||||
},
|
},
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"re2": "scripts/dependency-patches/re2.patch",
|
"re2": "scripts/dependency-patches/re2.patch"
|
||||||
"vite": "scripts/dependency-patches/vite.patch"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class CompositeNoteIndex1745378064470 {
|
||||||
|
name = 'CompositeNoteIndex1745378064470';
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_724b311e6f883751f261ebe378" ON "note" ("userId", "id" DESC)`);
|
||||||
|
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_5b87d9d19127bd5d92026017a7"`);
|
||||||
|
// Flush all cached Linear Scan Plans and redo statistics for composite index
|
||||||
|
// this is important for Postgres to learn that even in highly complex queries, using this index first can reduce the result set significantly
|
||||||
|
await queryRunner.query(`ANALYZE "user", "note"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_724b311e6f883751f261ebe378"`);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_5b87d9d19127bd5d92026017a7" ON "note" ("userId")`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,17 +37,17 @@
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@swc/core-android-arm64": "1.3.11",
|
"@swc/core-android-arm64": "1.3.11",
|
||||||
"@swc/core-darwin-arm64": "1.11.11",
|
"@swc/core-darwin-arm64": "1.11.18",
|
||||||
"@swc/core-darwin-x64": "1.11.11",
|
"@swc/core-darwin-x64": "1.11.18",
|
||||||
"@swc/core-freebsd-x64": "1.3.11",
|
"@swc/core-freebsd-x64": "1.3.11",
|
||||||
"@swc/core-linux-arm-gnueabihf": "1.11.11",
|
"@swc/core-linux-arm-gnueabihf": "1.11.18",
|
||||||
"@swc/core-linux-arm64-gnu": "1.11.11",
|
"@swc/core-linux-arm64-gnu": "1.11.18",
|
||||||
"@swc/core-linux-arm64-musl": "1.11.11",
|
"@swc/core-linux-arm64-musl": "1.11.18",
|
||||||
"@swc/core-linux-x64-gnu": "1.11.11",
|
"@swc/core-linux-x64-gnu": "1.11.18",
|
||||||
"@swc/core-linux-x64-musl": "1.11.11",
|
"@swc/core-linux-x64-musl": "1.11.18",
|
||||||
"@swc/core-win32-arm64-msvc": "1.11.11",
|
"@swc/core-win32-arm64-msvc": "1.11.18",
|
||||||
"@swc/core-win32-ia32-msvc": "1.11.11",
|
"@swc/core-win32-ia32-msvc": "1.11.18",
|
||||||
"@swc/core-win32-x64-msvc": "1.11.11",
|
"@swc/core-win32-x64-msvc": "1.11.18",
|
||||||
"@tensorflow/tfjs": "4.22.0",
|
"@tensorflow/tfjs": "4.22.0",
|
||||||
"@tensorflow/tfjs-node": "4.22.0",
|
"@tensorflow/tfjs-node": "4.22.0",
|
||||||
"bufferutil": "4.0.9",
|
"bufferutil": "4.0.9",
|
||||||
|
@ -67,8 +67,8 @@
|
||||||
"utf-8-validate": "6.0.5"
|
"utf-8-validate": "6.0.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "3.772.0",
|
"@aws-sdk/client-s3": "3.782.0",
|
||||||
"@aws-sdk/lib-storage": "3.772.0",
|
"@aws-sdk/lib-storage": "3.782.0",
|
||||||
"@discordapp/twemoji": "15.1.0",
|
"@discordapp/twemoji": "15.1.0",
|
||||||
"@fastify/accepts": "5.0.2",
|
"@fastify/accepts": "5.0.2",
|
||||||
"@fastify/cookie": "11.0.2",
|
"@fastify/cookie": "11.0.2",
|
||||||
|
@ -78,12 +78,12 @@
|
||||||
"@fastify/multipart": "9.0.3",
|
"@fastify/multipart": "9.0.3",
|
||||||
"@fastify/static": "8.1.1",
|
"@fastify/static": "8.1.1",
|
||||||
"@fastify/view": "10.0.2",
|
"@fastify/view": "10.0.2",
|
||||||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
"@misskey-dev/sharp-read-bmp": "1.3.0",
|
||||||
"@misskey-dev/summaly": "5.2.0",
|
"@misskey-dev/summaly": "5.2.0",
|
||||||
"@napi-rs/canvas": "0.1.68",
|
"@napi-rs/canvas": "0.1.69",
|
||||||
"@nestjs/common": "11.0.12",
|
"@nestjs/common": "11.0.16",
|
||||||
"@nestjs/core": "11.0.12",
|
"@nestjs/core": "11.0.15",
|
||||||
"@nestjs/testing": "11.0.12",
|
"@nestjs/testing": "11.0.15",
|
||||||
"@peertube/http-signature": "1.7.0",
|
"@peertube/http-signature": "1.7.0",
|
||||||
"@sentry/node": "8.55.0",
|
"@sentry/node": "8.55.0",
|
||||||
"@sentry/profiling-node": "8.55.0",
|
"@sentry/profiling-node": "8.55.0",
|
||||||
|
@ -91,8 +91,9 @@
|
||||||
"@sinonjs/fake-timers": "11.3.1",
|
"@sinonjs/fake-timers": "11.3.1",
|
||||||
"@smithy/node-http-handler": "2.5.0",
|
"@smithy/node-http-handler": "2.5.0",
|
||||||
"@swc/cli": "0.6.0",
|
"@swc/cli": "0.6.0",
|
||||||
"@swc/core": "1.11.11",
|
"@swc/core": "1.11.18",
|
||||||
"@twemoji/parser": "15.1.1",
|
"@twemoji/parser": "15.1.1",
|
||||||
|
"@types/redis-info": "3.0.3",
|
||||||
"accepts": "1.3.8",
|
"accepts": "1.3.8",
|
||||||
"ajv": "8.17.1",
|
"ajv": "8.17.1",
|
||||||
"archiver": "7.0.1",
|
"archiver": "7.0.1",
|
||||||
|
@ -100,7 +101,7 @@
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"body-parser": "1.20.3",
|
"body-parser": "1.20.3",
|
||||||
"bullmq": "5.44.1",
|
"bullmq": "5.48.1",
|
||||||
"cacheable-lookup": "7.0.0",
|
"cacheable-lookup": "7.0.0",
|
||||||
"cbor": "9.0.2",
|
"cbor": "9.0.2",
|
||||||
"chalk": "5.4.1",
|
"chalk": "5.4.1",
|
||||||
|
@ -111,13 +112,13 @@
|
||||||
"content-disposition": "0.5.4",
|
"content-disposition": "0.5.4",
|
||||||
"date-fns": "2.30.0",
|
"date-fns": "2.30.0",
|
||||||
"deep-email-validator": "0.1.21",
|
"deep-email-validator": "0.1.21",
|
||||||
"fastify": "5.2.1",
|
"fastify": "5.3.2",
|
||||||
"fastify-raw-body": "5.0.0",
|
"fastify-raw-body": "5.0.0",
|
||||||
"feed": "4.2.2",
|
"feed": "4.2.2",
|
||||||
"file-type": "19.6.0",
|
"file-type": "19.6.0",
|
||||||
"fluent-ffmpeg": "2.1.3",
|
"fluent-ffmpeg": "2.1.3",
|
||||||
"form-data": "4.0.2",
|
"form-data": "4.0.2",
|
||||||
"got": "14.4.6",
|
"got": "14.4.7",
|
||||||
"happy-dom": "16.8.1",
|
"happy-dom": "16.8.1",
|
||||||
"hpagent": "1.2.0",
|
"hpagent": "1.2.0",
|
||||||
"htmlescape": "1.1.1",
|
"htmlescape": "1.1.1",
|
||||||
|
@ -148,7 +149,7 @@
|
||||||
"oauth2orize": "1.12.0",
|
"oauth2orize": "1.12.0",
|
||||||
"oauth2orize-pkce": "0.1.2",
|
"oauth2orize-pkce": "0.1.2",
|
||||||
"os-utils": "0.0.14",
|
"os-utils": "0.0.14",
|
||||||
"otpauth": "9.3.6",
|
"otpauth": "9.4.0",
|
||||||
"parse5": "7.2.1",
|
"parse5": "7.2.1",
|
||||||
"pg": "8.14.1",
|
"pg": "8.14.1",
|
||||||
"pkce-challenge": "4.1.0",
|
"pkce-challenge": "4.1.0",
|
||||||
|
@ -159,6 +160,7 @@
|
||||||
"random-seed": "0.3.0",
|
"random-seed": "0.3.0",
|
||||||
"ratelimiter": "3.4.1",
|
"ratelimiter": "3.4.1",
|
||||||
"re2": "1.21.4",
|
"re2": "1.21.4",
|
||||||
|
"redis-info": "3.1.0",
|
||||||
"redis-lock": "0.1.4",
|
"redis-lock": "0.1.4",
|
||||||
"reflect-metadata": "0.2.2",
|
"reflect-metadata": "0.2.2",
|
||||||
"rename": "1.0.4",
|
"rename": "1.0.4",
|
||||||
|
@ -166,17 +168,17 @@
|
||||||
"rxjs": "7.8.2",
|
"rxjs": "7.8.2",
|
||||||
"sanitize-html": "2.15.0",
|
"sanitize-html": "2.15.0",
|
||||||
"secure-json-parse": "3.0.2",
|
"secure-json-parse": "3.0.2",
|
||||||
"sharp": "0.33.5",
|
"sharp": "0.34.1",
|
||||||
"slacc": "0.0.10",
|
"slacc": "0.0.10",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"stringz": "2.1.0",
|
"stringz": "2.1.0",
|
||||||
"systeminformation": "5.25.11",
|
"systeminformation": "5.25.11",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tmp": "0.2.3",
|
"tmp": "0.2.3",
|
||||||
"tsc-alias": "1.8.11",
|
"tsc-alias": "1.8.15",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typeorm": "0.3.21",
|
"typeorm": "0.3.22",
|
||||||
"typescript": "5.8.2",
|
"typescript": "5.8.3",
|
||||||
"ulid": "2.4.0",
|
"ulid": "2.4.0",
|
||||||
"vary": "1.1.2",
|
"vary": "1.1.2",
|
||||||
"web-push": "3.6.7",
|
"web-push": "3.6.7",
|
||||||
|
@ -186,7 +188,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/globals": "29.7.0",
|
"@jest/globals": "29.7.0",
|
||||||
"@nestjs/platform-express": "10.4.15",
|
"@nestjs/platform-express": "10.4.15",
|
||||||
"@sentry/vue": "9.8.0",
|
"@sentry/vue": "9.12.0",
|
||||||
"@simplewebauthn/types": "12.0.0",
|
"@simplewebauthn/types": "12.0.0",
|
||||||
"@swc/jest": "0.2.37",
|
"@swc/jest": "0.2.37",
|
||||||
"@types/accepts": "1.3.7",
|
"@types/accepts": "1.3.7",
|
||||||
|
@ -205,7 +207,7 @@
|
||||||
"@types/jsrsasign": "10.5.15",
|
"@types/jsrsasign": "10.5.15",
|
||||||
"@types/mime-types": "2.1.4",
|
"@types/mime-types": "2.1.4",
|
||||||
"@types/ms": "0.7.34",
|
"@types/ms": "0.7.34",
|
||||||
"@types/node": "22.13.10",
|
"@types/node": "22.14.0",
|
||||||
"@types/nodemailer": "6.4.17",
|
"@types/nodemailer": "6.4.17",
|
||||||
"@types/oauth": "0.9.6",
|
"@types/oauth": "0.9.6",
|
||||||
"@types/oauth2orize": "1.11.5",
|
"@types/oauth2orize": "1.11.5",
|
||||||
|
@ -216,17 +218,17 @@
|
||||||
"@types/random-seed": "0.3.5",
|
"@types/random-seed": "0.3.5",
|
||||||
"@types/ratelimiter": "3.4.6",
|
"@types/ratelimiter": "3.4.6",
|
||||||
"@types/rename": "1.0.7",
|
"@types/rename": "1.0.7",
|
||||||
"@types/sanitize-html": "2.13.0",
|
"@types/sanitize-html": "2.15.0",
|
||||||
"@types/semver": "7.5.8",
|
"@types/semver": "7.7.0",
|
||||||
"@types/simple-oauth2": "5.0.7",
|
"@types/simple-oauth2": "5.0.7",
|
||||||
"@types/sinonjs__fake-timers": "8.1.5",
|
"@types/sinonjs__fake-timers": "8.1.5",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@types/tmp": "0.2.6",
|
"@types/tmp": "0.2.6",
|
||||||
"@types/vary": "1.1.3",
|
"@types/vary": "1.1.3",
|
||||||
"@types/web-push": "3.6.4",
|
"@types/web-push": "3.6.4",
|
||||||
"@types/ws": "8.18.0",
|
"@types/ws": "8.18.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.27.0",
|
"@typescript-eslint/eslint-plugin": "8.29.1",
|
||||||
"@typescript-eslint/parser": "8.27.0",
|
"@typescript-eslint/parser": "8.29.1",
|
||||||
"aws-sdk-client-mock": "4.1.0",
|
"aws-sdk-client-mock": "4.1.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint-plugin-import": "2.31.0",
|
"eslint-plugin-import": "2.31.0",
|
||||||
|
|
|
@ -25,6 +25,7 @@ import InstanceChart from '@/core/chart/charts/instance.js';
|
||||||
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
|
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
|
||||||
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { AntennaService } from '@/core/AntennaService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AccountMoveService {
|
export class AccountMoveService {
|
||||||
|
@ -63,6 +64,7 @@ export class AccountMoveService {
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
private systemAccountService: SystemAccountService,
|
private systemAccountService: SystemAccountService,
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
|
private antennaService: AntennaService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,6 +125,7 @@ export class AccountMoveService {
|
||||||
this.copyMutings(src, dst),
|
this.copyMutings(src, dst),
|
||||||
this.copyRoles(src, dst),
|
this.copyRoles(src, dst),
|
||||||
this.updateLists(src, dst),
|
this.updateLists(src, dst),
|
||||||
|
this.antennaService.onMoveAccount(src, dst),
|
||||||
]);
|
]);
|
||||||
} catch {
|
} catch {
|
||||||
/* skip if any error happens */
|
/* skip if any error happens */
|
||||||
|
|
|
@ -5,18 +5,20 @@
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
|
import { In } from 'typeorm';
|
||||||
|
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||||
|
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||||
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import * as Acct from '@/misc/acct.js';
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
import type { AntennasRepository, UserListMembershipsRepository } from '@/models/_.js';
|
||||||
import type { MiAntenna } from '@/models/Antenna.js';
|
import type { MiAntenna } from '@/models/Antenna.js';
|
||||||
import type { MiNote } from '@/models/Note.js';
|
import type { MiNote } from '@/models/Note.js';
|
||||||
import type { MiUser } from '@/models/User.js';
|
import type { MiUser } from '@/models/User.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { CacheService } from './CacheService.js';
|
||||||
import * as Acct from '@/misc/acct.js';
|
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import type { AntennasRepository, UserListMembershipsRepository } from '@/models/_.js';
|
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
|
||||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
|
||||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -37,6 +39,7 @@ export class AntennaService implements OnApplicationShutdown {
|
||||||
@Inject(DI.userListMembershipsRepository)
|
@Inject(DI.userListMembershipsRepository)
|
||||||
private userListMembershipsRepository: UserListMembershipsRepository,
|
private userListMembershipsRepository: UserListMembershipsRepository,
|
||||||
|
|
||||||
|
private cacheService: CacheService,
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private fanoutTimelineService: FanoutTimelineService,
|
private fanoutTimelineService: FanoutTimelineService,
|
||||||
|
@ -111,9 +114,6 @@ export class AntennaService implements OnApplicationShutdown {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async checkHitAntenna(antenna: MiAntenna, note: (MiNote | Packed<'Note'>), noteUser: { id: MiUser['id']; username: string; host: string | null; isBot: boolean; }): Promise<boolean> {
|
public async checkHitAntenna(antenna: MiAntenna, note: (MiNote | Packed<'Note'>), noteUser: { id: MiUser['id']; username: string; host: string | null; isBot: boolean; }): Promise<boolean> {
|
||||||
if (note.visibility === 'specified') return false;
|
|
||||||
if (note.visibility === 'followers') return false;
|
|
||||||
|
|
||||||
if (antenna.excludeNotesInSensitiveChannel && note.channel?.isSensitive) return false;
|
if (antenna.excludeNotesInSensitiveChannel && note.channel?.isSensitive) return false;
|
||||||
|
|
||||||
if (antenna.excludeBots && noteUser.isBot) return false;
|
if (antenna.excludeBots && noteUser.isBot) return false;
|
||||||
|
@ -122,6 +122,18 @@ export class AntennaService implements OnApplicationShutdown {
|
||||||
|
|
||||||
if (!antenna.withReplies && note.replyId != null) return false;
|
if (!antenna.withReplies && note.replyId != null) return false;
|
||||||
|
|
||||||
|
if (note.visibility === 'specified') {
|
||||||
|
if (note.userId !== antenna.userId) {
|
||||||
|
if (note.visibleUserIds == null) return false;
|
||||||
|
if (!note.visibleUserIds.includes(antenna.userId)) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (note.visibility === 'followers') {
|
||||||
|
const isFollowing = Object.hasOwn(await this.cacheService.userFollowingsCache.fetch(antenna.userId), note.userId);
|
||||||
|
if (!isFollowing && antenna.userId !== note.userId) return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (antenna.src === 'home') {
|
if (antenna.src === 'home') {
|
||||||
// TODO
|
// TODO
|
||||||
} else if (antenna.src === 'list') {
|
} else if (antenna.src === 'list') {
|
||||||
|
@ -208,6 +220,41 @@ export class AntennaService implements OnApplicationShutdown {
|
||||||
return this.antennas;
|
return this.antennas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async onMoveAccount(src: MiUser, dst: MiUser): Promise<void> {
|
||||||
|
// There is a possibility for users to add the srcUser to their antennas, but it's low, so we don't check it.
|
||||||
|
|
||||||
|
// Get MiAntenna[] from cache and filter to select antennas with the src user is in the users list
|
||||||
|
const srcUserAcct = this.utilityService.getFullApAccount(src.username, src.host).toLowerCase();
|
||||||
|
const antennasToMigrate = (await this.getAntennas()).filter(antenna => {
|
||||||
|
return antenna.users.some(user => {
|
||||||
|
const { username, host } = Acct.parse(user);
|
||||||
|
return this.utilityService.getFullApAccount(username, host).toLowerCase() === srcUserAcct;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (antennasToMigrate.length === 0) return;
|
||||||
|
|
||||||
|
const antennaIds = antennasToMigrate.map(x => x.id);
|
||||||
|
|
||||||
|
// Update the antennas by appending dst users acct to the users list
|
||||||
|
const dstUserAcct = '@' + Acct.toString({ username: dst.username, host: dst.host });
|
||||||
|
|
||||||
|
await this.antennasRepository.createQueryBuilder('antenna')
|
||||||
|
.update()
|
||||||
|
.set({
|
||||||
|
users: () => 'array_append(antenna.users, :dstUserAcct)',
|
||||||
|
})
|
||||||
|
.where('antenna.id IN (:...antennaIds)', { antennaIds })
|
||||||
|
.setParameters({ dstUserAcct })
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
// announce update to event
|
||||||
|
for (const newAntenna of await this.antennasRepository.findBy({ id: In(antennaIds) })) {
|
||||||
|
this.globalEventService.publishInternalEvent('antennaUpdated', newAntenna);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
this.redisForSub.off('message', this.onRedisMessage);
|
this.redisForSub.off('message', this.onRedisMessage);
|
||||||
|
|
|
@ -232,7 +232,7 @@ export class ChatService {
|
||||||
|
|
||||||
const packedMessageForTo = await this.chatEntityService.packMessageDetailed(inserted, toUser);
|
const packedMessageForTo = await this.chatEntityService.packMessageDetailed(inserted, toUser);
|
||||||
this.globalEventService.publishMainStream(toUser.id, 'newChatMessage', packedMessageForTo);
|
this.globalEventService.publishMainStream(toUser.id, 'newChatMessage', packedMessageForTo);
|
||||||
//this.pushNotificationService.pushNotification(toUser.id, 'newChatMessage', packedMessageForTo);
|
this.pushNotificationService.pushNotification(toUser.id, 'newChatMessage', packedMessageForTo);
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,7 +302,7 @@ export class ChatService {
|
||||||
if (marker == null) continue;
|
if (marker == null) continue;
|
||||||
|
|
||||||
this.globalEventService.publishMainStream(membershipsOtherThanMe[i].userId, 'newChatMessage', packedMessageForTo);
|
this.globalEventService.publishMainStream(membershipsOtherThanMe[i].userId, 'newChatMessage', packedMessageForTo);
|
||||||
//this.pushNotificationService.pushNotification(membershipsOtherThanMe[i].userId, 'newChatMessage', packedMessageForTo);
|
this.pushNotificationService.pushNotification(membershipsOtherThanMe[i].userId, 'newChatMessage', packedMessageForTo);
|
||||||
}
|
}
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
|
|
|
@ -522,9 +522,16 @@ export class DriveService {
|
||||||
|
|
||||||
const policies = await this.roleService.getUserPolicies(user.id);
|
const policies = await this.roleService.getUserPolicies(user.id);
|
||||||
const driveCapacity = 1024 * 1024 * policies.driveCapacityMb;
|
const driveCapacity = 1024 * 1024 * policies.driveCapacityMb;
|
||||||
|
const maxFileSize = 1024 * 1024 * policies.maxFileSizeMb;
|
||||||
this.registerLogger.debug('drive capacity override applied');
|
this.registerLogger.debug('drive capacity override applied');
|
||||||
this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`);
|
this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`);
|
||||||
|
|
||||||
|
if (maxFileSize < info.size) {
|
||||||
|
if (isLocalUser) {
|
||||||
|
throw new IdentifiableError('f9e4e5f3-4df4-40b5-b400-f236945f7073', 'Max file size exceeded.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If usage limit exceeded
|
// If usage limit exceeded
|
||||||
if (driveCapacity < usage + info.size) {
|
if (driveCapacity < usage + info.size) {
|
||||||
if (isLocalUser) {
|
if (isLocalUser) {
|
||||||
|
|
|
@ -55,7 +55,7 @@ export class FanoutTimelineEndpointService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async getMiNotes(ps: TimelineOptions): Promise<MiNote[]> {
|
async getMiNotes(ps: TimelineOptions): Promise<MiNote[]> {
|
||||||
// 呼び出し元と以下の処理をシンプルにするためにdbFallbackを置き換える
|
// 呼び出し元と以下の処理をシンプルにするためにdbFallbackを置き換える
|
||||||
if (!ps.useDbFallback) ps.dbFallback = () => Promise.resolve([]);
|
if (!ps.useDbFallback) ps.dbFallback = () => Promise.resolve([]);
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ export const webpDefault: sharp.WebpOptions = {
|
||||||
smartSubsample: true,
|
smartSubsample: true,
|
||||||
mixed: true,
|
mixed: true,
|
||||||
effort: 2,
|
effort: 2,
|
||||||
|
loop: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const avifDefault: sharp.AvifOptions = {
|
export const avifDefault: sharp.AvifOptions = {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import { URL } from 'node:url';
|
import { URL } from 'node:url';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import * as parse5 from 'parse5';
|
import * as parse5 from 'parse5';
|
||||||
import { Window, XMLSerializer } from 'happy-dom';
|
import { type Document, type HTMLParagraphElement, Window, XMLSerializer } from 'happy-dom';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { intersperse } from '@/misc/prelude/array.js';
|
import { intersperse } from '@/misc/prelude/array.js';
|
||||||
|
@ -23,6 +23,8 @@ type ChildNode = DefaultTreeAdapterMap['childNode'];
|
||||||
const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/;
|
const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/;
|
||||||
const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/;
|
const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/;
|
||||||
|
|
||||||
|
export type Appender = (document: Document, body: HTMLParagraphElement) => void;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MfmService {
|
export class MfmService {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -267,7 +269,7 @@ export class MfmService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public toHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = []) {
|
public toHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = [], additionalAppenders: Appender[] = []) {
|
||||||
if (nodes == null) {
|
if (nodes == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -492,6 +494,10 @@ export class MfmService {
|
||||||
|
|
||||||
appendChildren(nodes, body);
|
appendChildren(nodes, body);
|
||||||
|
|
||||||
|
for (const additionalAppender of additionalAppenders) {
|
||||||
|
additionalAppender(doc, body);
|
||||||
|
}
|
||||||
|
|
||||||
// Remove the unnecessary namespace
|
// Remove the unnecessary namespace
|
||||||
const serialized = new XMLSerializer().serializeToString(body).replace(/^\s*<p xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">/, '<p>');
|
const serialized = new XMLSerializer().serializeToString(body).replace(/^\s*<p xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">/, '<p>');
|
||||||
|
|
||||||
|
|
|
@ -576,7 +576,14 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
}, {
|
}, {
|
||||||
delay,
|
delay,
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ type PushNotificationsTypes = {
|
||||||
note: Packed<'Note'>;
|
note: Packed<'Note'>;
|
||||||
};
|
};
|
||||||
'readAllNotifications': undefined;
|
'readAllNotifications': undefined;
|
||||||
|
newChatMessage: Packed<'ChatMessage'>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reduce length because push message servers have character limits
|
// Reduce length because push message servers have character limits
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { MetricsTime, type JobType } from 'bullmq';
|
||||||
|
import { parse as parseRedisInfo } from 'redis-info';
|
||||||
import type { IActivity } from '@/core/activitypub/type.js';
|
import type { IActivity } from '@/core/activitypub/type.js';
|
||||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||||
import type { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js';
|
import type { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js';
|
||||||
|
@ -38,6 +40,18 @@ import type {
|
||||||
import type httpSignature from '@peertube/http-signature';
|
import type httpSignature from '@peertube/http-signature';
|
||||||
import type * as Bull from 'bullmq';
|
import type * as Bull from 'bullmq';
|
||||||
|
|
||||||
|
export const QUEUE_TYPES = [
|
||||||
|
'system',
|
||||||
|
'endedPollNotification',
|
||||||
|
'deliver',
|
||||||
|
'inbox',
|
||||||
|
'db',
|
||||||
|
'relationship',
|
||||||
|
'objectStorage',
|
||||||
|
'userWebhookDeliver',
|
||||||
|
'systemWebhookDeliver',
|
||||||
|
] as const;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QueueService {
|
export class QueueService {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -57,50 +71,58 @@ export class QueueService {
|
||||||
this.systemQueue.add('tickCharts', {
|
this.systemQueue.add('tickCharts', {
|
||||||
}, {
|
}, {
|
||||||
repeat: { pattern: '55 * * * *' },
|
repeat: { pattern: '55 * * * *' },
|
||||||
removeOnComplete: true,
|
removeOnComplete: 10,
|
||||||
|
removeOnFail: 30,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.systemQueue.add('resyncCharts', {
|
this.systemQueue.add('resyncCharts', {
|
||||||
}, {
|
}, {
|
||||||
repeat: { pattern: '0 0 * * *' },
|
repeat: { pattern: '0 0 * * *' },
|
||||||
removeOnComplete: true,
|
removeOnComplete: 10,
|
||||||
|
removeOnFail: 30,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.systemQueue.add('cleanCharts', {
|
this.systemQueue.add('cleanCharts', {
|
||||||
}, {
|
}, {
|
||||||
repeat: { pattern: '0 0 * * *' },
|
repeat: { pattern: '0 0 * * *' },
|
||||||
removeOnComplete: true,
|
removeOnComplete: 10,
|
||||||
|
removeOnFail: 30,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.systemQueue.add('aggregateRetention', {
|
this.systemQueue.add('aggregateRetention', {
|
||||||
}, {
|
}, {
|
||||||
repeat: { pattern: '0 0 * * *' },
|
repeat: { pattern: '0 0 * * *' },
|
||||||
removeOnComplete: true,
|
removeOnComplete: 10,
|
||||||
|
removeOnFail: 30,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.systemQueue.add('clean', {
|
this.systemQueue.add('clean', {
|
||||||
}, {
|
}, {
|
||||||
repeat: { pattern: '0 0 * * *' },
|
repeat: { pattern: '0 0 * * *' },
|
||||||
removeOnComplete: true,
|
removeOnComplete: 10,
|
||||||
|
removeOnFail: 30,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.systemQueue.add('checkExpiredMutings', {
|
this.systemQueue.add('checkExpiredMutings', {
|
||||||
}, {
|
}, {
|
||||||
repeat: { pattern: '*/5 * * * *' },
|
repeat: { pattern: '*/5 * * * *' },
|
||||||
removeOnComplete: true,
|
removeOnComplete: 10,
|
||||||
|
removeOnFail: 30,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.systemQueue.add('bakeBufferedReactions', {
|
this.systemQueue.add('bakeBufferedReactions', {
|
||||||
}, {
|
}, {
|
||||||
repeat: { pattern: '0 0 * * *' },
|
repeat: { pattern: '0 0 * * *' },
|
||||||
removeOnComplete: true,
|
removeOnComplete: 10,
|
||||||
|
removeOnFail: 30,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.systemQueue.add('checkModeratorsActivity', {
|
this.systemQueue.add('checkModeratorsActivity', {
|
||||||
}, {
|
}, {
|
||||||
// 毎時30分に起動
|
// 毎時30分に起動
|
||||||
repeat: { pattern: '30 * * * *' },
|
repeat: { pattern: '30 * * * *' },
|
||||||
removeOnComplete: true,
|
removeOnComplete: 10,
|
||||||
|
removeOnFail: 30,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,13 +144,21 @@ export class QueueService {
|
||||||
isSharedInbox,
|
isSharedInbox,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.deliverQueue.add(to, data, {
|
const label = to.replace('https://', '').replace('/inbox', '');
|
||||||
|
|
||||||
|
return this.deliverQueue.add(label, data, {
|
||||||
attempts: this.config.deliverJobMaxAttempts ?? 12,
|
attempts: this.config.deliverJobMaxAttempts ?? 12,
|
||||||
backoff: {
|
backoff: {
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
},
|
},
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,12 +180,18 @@ export class QueueService {
|
||||||
backoff: {
|
backoff: {
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
},
|
},
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.deliverQueue.addBulk(Array.from(inboxes.entries(), d => ({
|
await this.deliverQueue.addBulk(Array.from(inboxes.entries(), d => ({
|
||||||
name: d[0],
|
name: d[0].replace('https://', '').replace('/inbox', ''),
|
||||||
data: {
|
data: {
|
||||||
user,
|
user,
|
||||||
content: contentBody,
|
content: contentBody,
|
||||||
|
@ -176,13 +212,21 @@ export class QueueService {
|
||||||
signature,
|
signature,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.inboxQueue.add('', data, {
|
const label = (activity.id ?? '').replace('https://', '').replace('/activity', '');
|
||||||
|
|
||||||
|
return this.inboxQueue.add(label, data, {
|
||||||
attempts: this.config.inboxJobMaxAttempts ?? 8,
|
attempts: this.config.inboxJobMaxAttempts ?? 8,
|
||||||
backoff: {
|
backoff: {
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
},
|
},
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,8 +235,14 @@ export class QueueService {
|
||||||
return this.dbQueue.add('deleteDriveFiles', {
|
return this.dbQueue.add('deleteDriveFiles', {
|
||||||
user: { id: user.id },
|
user: { id: user.id },
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,8 +251,14 @@ export class QueueService {
|
||||||
return this.dbQueue.add('exportCustomEmojis', {
|
return this.dbQueue.add('exportCustomEmojis', {
|
||||||
user: { id: user.id },
|
user: { id: user.id },
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,8 +267,14 @@ export class QueueService {
|
||||||
return this.dbQueue.add('exportNotes', {
|
return this.dbQueue.add('exportNotes', {
|
||||||
user: { id: user.id },
|
user: { id: user.id },
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,8 +283,14 @@ export class QueueService {
|
||||||
return this.dbQueue.add('exportClips', {
|
return this.dbQueue.add('exportClips', {
|
||||||
user: { id: user.id },
|
user: { id: user.id },
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,8 +299,14 @@ export class QueueService {
|
||||||
return this.dbQueue.add('exportFavorites', {
|
return this.dbQueue.add('exportFavorites', {
|
||||||
user: { id: user.id },
|
user: { id: user.id },
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,8 +317,14 @@ export class QueueService {
|
||||||
excludeMuting,
|
excludeMuting,
|
||||||
excludeInactive,
|
excludeInactive,
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,8 +333,14 @@ export class QueueService {
|
||||||
return this.dbQueue.add('exportMuting', {
|
return this.dbQueue.add('exportMuting', {
|
||||||
user: { id: user.id },
|
user: { id: user.id },
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,8 +349,14 @@ export class QueueService {
|
||||||
return this.dbQueue.add('exportBlocking', {
|
return this.dbQueue.add('exportBlocking', {
|
||||||
user: { id: user.id },
|
user: { id: user.id },
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,8 +365,14 @@ export class QueueService {
|
||||||
return this.dbQueue.add('exportUserLists', {
|
return this.dbQueue.add('exportUserLists', {
|
||||||
user: { id: user.id },
|
user: { id: user.id },
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,8 +381,14 @@ export class QueueService {
|
||||||
return this.dbQueue.add('exportAntennas', {
|
return this.dbQueue.add('exportAntennas', {
|
||||||
user: { id: user.id },
|
user: { id: user.id },
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,8 +399,14 @@ export class QueueService {
|
||||||
fileId: fileId,
|
fileId: fileId,
|
||||||
withReplies,
|
withReplies,
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,8 +422,14 @@ export class QueueService {
|
||||||
user: { id: user.id },
|
user: { id: user.id },
|
||||||
fileId: fileId,
|
fileId: fileId,
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,8 +439,14 @@ export class QueueService {
|
||||||
user: { id: user.id },
|
user: { id: user.id },
|
||||||
fileId: fileId,
|
fileId: fileId,
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,8 +466,14 @@ export class QueueService {
|
||||||
name,
|
name,
|
||||||
data,
|
data,
|
||||||
opts: {
|
opts: {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -356,8 +484,14 @@ export class QueueService {
|
||||||
user: { id: user.id },
|
user: { id: user.id },
|
||||||
fileId: fileId,
|
fileId: fileId,
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,8 +501,14 @@ export class QueueService {
|
||||||
user: { id: user.id },
|
user: { id: user.id },
|
||||||
fileId: fileId,
|
fileId: fileId,
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,8 +518,14 @@ export class QueueService {
|
||||||
user: { id: user.id },
|
user: { id: user.id },
|
||||||
antenna,
|
antenna,
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -389,8 +535,14 @@ export class QueueService {
|
||||||
user: { id: user.id },
|
user: { id: user.id },
|
||||||
soft: opts.soft,
|
soft: opts.soft,
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -440,8 +592,14 @@ export class QueueService {
|
||||||
withReplies: data.withReplies,
|
withReplies: data.withReplies,
|
||||||
},
|
},
|
||||||
opts: {
|
opts: {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
...opts,
|
...opts,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -452,16 +610,28 @@ export class QueueService {
|
||||||
return this.objectStorageQueue.add('deleteFile', {
|
return this.objectStorageQueue.add('deleteFile', {
|
||||||
key: key,
|
key: key,
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public createCleanRemoteFilesJob() {
|
public createCleanRemoteFilesJob() {
|
||||||
return this.objectStorageQueue.add('cleanRemoteFiles', {}, {
|
return this.objectStorageQueue.add('cleanRemoteFiles', {}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,8 +662,14 @@ export class QueueService {
|
||||||
backoff: {
|
backoff: {
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
},
|
},
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -523,21 +699,201 @@ export class QueueService {
|
||||||
backoff: {
|
backoff: {
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
},
|
},
|
||||||
removeOnComplete: true,
|
removeOnComplete: {
|
||||||
removeOnFail: true,
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 30,
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
age: 3600 * 24 * 7, // keep up to 7 days
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public destroy() {
|
private getQueue(type: typeof QUEUE_TYPES[number]): Bull.Queue {
|
||||||
this.deliverQueue.once('cleaned', (jobs, status) => {
|
switch (type) {
|
||||||
//deliverLogger.succ(`Cleaned ${jobs.length} ${status} jobs`);
|
case 'system': return this.systemQueue;
|
||||||
});
|
case 'endedPollNotification': return this.endedPollNotificationQueue;
|
||||||
this.deliverQueue.clean(0, 0, 'delayed');
|
case 'deliver': return this.deliverQueue;
|
||||||
|
case 'inbox': return this.inboxQueue;
|
||||||
|
case 'db': return this.dbQueue;
|
||||||
|
case 'relationship': return this.relationshipQueue;
|
||||||
|
case 'objectStorage': return this.objectStorageQueue;
|
||||||
|
case 'userWebhookDeliver': return this.userWebhookDeliverQueue;
|
||||||
|
case 'systemWebhookDeliver': return this.systemWebhookDeliverQueue;
|
||||||
|
default: throw new Error(`Unrecognized queue type: ${type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.inboxQueue.once('cleaned', (jobs, status) => {
|
@bindThis
|
||||||
//inboxLogger.succ(`Cleaned ${jobs.length} ${status} jobs`);
|
public async queueClear(queueType: typeof QUEUE_TYPES[number], state: '*' | 'completed' | 'wait' | 'active' | 'paused' | 'prioritized' | 'delayed' | 'failed') {
|
||||||
|
const queue = this.getQueue(queueType);
|
||||||
|
|
||||||
|
if (state === '*') {
|
||||||
|
await Promise.all([
|
||||||
|
queue.clean(0, 0, 'completed'),
|
||||||
|
queue.clean(0, 0, 'wait'),
|
||||||
|
queue.clean(0, 0, 'active'),
|
||||||
|
queue.clean(0, 0, 'paused'),
|
||||||
|
queue.clean(0, 0, 'prioritized'),
|
||||||
|
queue.clean(0, 0, 'delayed'),
|
||||||
|
queue.clean(0, 0, 'failed'),
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
await queue.clean(0, 0, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async queuePromoteJobs(queueType: typeof QUEUE_TYPES[number]) {
|
||||||
|
const queue = this.getQueue(queueType);
|
||||||
|
await queue.promoteJobs();
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async queueRetryJob(queueType: typeof QUEUE_TYPES[number], jobId: string) {
|
||||||
|
const queue = this.getQueue(queueType);
|
||||||
|
const job: Bull.Job | null = await queue.getJob(jobId);
|
||||||
|
if (job) {
|
||||||
|
if (job.finishedOn != null) {
|
||||||
|
await job.retry();
|
||||||
|
} else {
|
||||||
|
await job.promote();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async queueRemoveJob(queueType: typeof QUEUE_TYPES[number], jobId: string) {
|
||||||
|
const queue = this.getQueue(queueType);
|
||||||
|
const job: Bull.Job | null = await queue.getJob(jobId);
|
||||||
|
if (job) {
|
||||||
|
await job.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private packJobData(job: Bull.Job) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
const stacktrace = job.stacktrace ? job.stacktrace.filter(Boolean) : [];
|
||||||
|
stacktrace.reverse();
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: job.id,
|
||||||
|
name: job.name,
|
||||||
|
data: job.data,
|
||||||
|
opts: job.opts,
|
||||||
|
timestamp: job.timestamp,
|
||||||
|
processedOn: job.processedOn,
|
||||||
|
processedBy: job.processedBy,
|
||||||
|
finishedOn: job.finishedOn,
|
||||||
|
progress: job.progress,
|
||||||
|
attempts: job.attemptsMade,
|
||||||
|
delay: job.delay,
|
||||||
|
failedReason: job.failedReason,
|
||||||
|
stacktrace: stacktrace,
|
||||||
|
returnValue: job.returnvalue,
|
||||||
|
isFailed: !!job.failedReason || (Array.isArray(stacktrace) && stacktrace.length > 0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async queueGetJob(queueType: typeof QUEUE_TYPES[number], jobId: string) {
|
||||||
|
const queue = this.getQueue(queueType);
|
||||||
|
const job: Bull.Job | null = await queue.getJob(jobId);
|
||||||
|
if (job) {
|
||||||
|
return this.packJobData(job);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Job not found: ${jobId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async queueGetJobs(queueType: typeof QUEUE_TYPES[number], jobTypes: JobType[], search?: string) {
|
||||||
|
const RETURN_LIMIT = 100;
|
||||||
|
const queue = this.getQueue(queueType);
|
||||||
|
let jobs: Bull.Job[];
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
jobs = await queue.getJobs(jobTypes, 0, 1000);
|
||||||
|
|
||||||
|
jobs = jobs.filter(job => {
|
||||||
|
const jobString = JSON.stringify(job).toLowerCase();
|
||||||
|
return search.toLowerCase().split(' ').every(term => {
|
||||||
|
return jobString.includes(term);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
jobs = jobs.slice(0, RETURN_LIMIT);
|
||||||
|
} else {
|
||||||
|
jobs = await queue.getJobs(jobTypes, 0, RETURN_LIMIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jobs.map(job => this.packJobData(job));
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async queueGetQueues() {
|
||||||
|
const fetchings = QUEUE_TYPES.map(async type => {
|
||||||
|
const queue = this.getQueue(type);
|
||||||
|
|
||||||
|
const counts = await queue.getJobCounts();
|
||||||
|
const isPaused = await queue.isPaused();
|
||||||
|
const metrics_completed = await queue.getMetrics('completed', 0, MetricsTime.ONE_WEEK);
|
||||||
|
const metrics_failed = await queue.getMetrics('failed', 0, MetricsTime.ONE_WEEK);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: type,
|
||||||
|
counts: counts,
|
||||||
|
isPaused,
|
||||||
|
metrics: {
|
||||||
|
completed: metrics_completed,
|
||||||
|
failed: metrics_failed,
|
||||||
|
},
|
||||||
|
};
|
||||||
});
|
});
|
||||||
this.inboxQueue.clean(0, 0, 'delayed');
|
|
||||||
|
return await Promise.all(fetchings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async queueGetQueue(queueType: typeof QUEUE_TYPES[number]) {
|
||||||
|
const queue = this.getQueue(queueType);
|
||||||
|
const counts = await queue.getJobCounts();
|
||||||
|
const isPaused = await queue.isPaused();
|
||||||
|
const metrics_completed = await queue.getMetrics('completed', 0, MetricsTime.ONE_WEEK);
|
||||||
|
const metrics_failed = await queue.getMetrics('failed', 0, MetricsTime.ONE_WEEK);
|
||||||
|
const db = parseRedisInfo(await (await queue.client).info());
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: queueType,
|
||||||
|
qualifiedName: queue.qualifiedName,
|
||||||
|
counts: counts,
|
||||||
|
isPaused,
|
||||||
|
metrics: {
|
||||||
|
completed: metrics_completed,
|
||||||
|
failed: metrics_failed,
|
||||||
|
},
|
||||||
|
db: {
|
||||||
|
version: db.redis_version,
|
||||||
|
mode: db.redis_mode,
|
||||||
|
runId: db.run_id,
|
||||||
|
processId: db.process_id,
|
||||||
|
port: parseInt(db.tcp_port),
|
||||||
|
os: db.os,
|
||||||
|
uptime: parseInt(db.uptime_in_seconds),
|
||||||
|
memory: {
|
||||||
|
total: parseInt(db.total_system_memory) || parseInt(db.maxmemory),
|
||||||
|
used: parseInt(db.used_memory),
|
||||||
|
fragmentationRatio: parseInt(db.mem_fragmentation_ratio),
|
||||||
|
peak: parseInt(db.used_memory_peak),
|
||||||
|
},
|
||||||
|
clients: {
|
||||||
|
connected: parseInt(db.connected_clients),
|
||||||
|
blocked: parseInt(db.blocked_clients),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ export type RolePolicies = {
|
||||||
canUseTranslator: boolean;
|
canUseTranslator: boolean;
|
||||||
canHideAds: boolean;
|
canHideAds: boolean;
|
||||||
driveCapacityMb: number;
|
driveCapacityMb: number;
|
||||||
|
maxFileSizeMb: number;
|
||||||
alwaysMarkNsfw: boolean;
|
alwaysMarkNsfw: boolean;
|
||||||
canUpdateBioMedia: boolean;
|
canUpdateBioMedia: boolean;
|
||||||
pinLimit: number;
|
pinLimit: number;
|
||||||
|
@ -81,6 +82,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
||||||
canUseTranslator: true,
|
canUseTranslator: true,
|
||||||
canHideAds: false,
|
canHideAds: false,
|
||||||
driveCapacityMb: 100,
|
driveCapacityMb: 100,
|
||||||
|
maxFileSizeMb: 10,
|
||||||
alwaysMarkNsfw: false,
|
alwaysMarkNsfw: false,
|
||||||
canUpdateBioMedia: true,
|
canUpdateBioMedia: true,
|
||||||
pinLimit: 5,
|
pinLimit: 5,
|
||||||
|
@ -391,6 +393,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)),
|
canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)),
|
||||||
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
|
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
|
||||||
driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)),
|
driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)),
|
||||||
|
maxFileSizeMb: calc('maxFileSizeMb', vs => Math.max(...vs)),
|
||||||
alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)),
|
alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)),
|
||||||
canUpdateBioMedia: calc('canUpdateBioMedia', vs => vs.some(v => v === true)),
|
canUpdateBioMedia: calc('canUpdateBioMedia', vs => vs.some(v => v === true)),
|
||||||
pinLimit: calc('pinLimit', vs => Math.max(...vs)),
|
pinLimit: calc('pinLimit', vs => Math.max(...vs)),
|
||||||
|
|
|
@ -5,11 +5,14 @@
|
||||||
|
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||||
import { DataSource, IsNull } from 'typeorm';
|
import { DataSource, IsNull } from 'typeorm';
|
||||||
|
import * as Redis from 'ioredis';
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import { MiLocalUser, MiUser } from '@/models/User.js';
|
import { MiLocalUser, MiUser } from '@/models/User.js';
|
||||||
import { MiSystemAccount, MiUsedUsername, MiUserKeypair, MiUserProfile, type UsersRepository, type SystemAccountsRepository } from '@/models/_.js';
|
import { MiSystemAccount, MiUsedUsername, MiUserKeypair, MiUserProfile, type UsersRepository, type SystemAccountsRepository } from '@/models/_.js';
|
||||||
import type { MiMeta, UserProfilesRepository } from '@/models/_.js';
|
import type { MiMeta, UserProfilesRepository } from '@/models/_.js';
|
||||||
|
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||||
import { MemoryKVCache } from '@/misc/cache.js';
|
import { MemoryKVCache } from '@/misc/cache.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
@ -20,10 +23,13 @@ import { genRsaKeyPair } from '@/misc/gen-key-pair.js';
|
||||||
export const SYSTEM_ACCOUNT_TYPES = ['actor', 'relay', 'proxy'] as const;
|
export const SYSTEM_ACCOUNT_TYPES = ['actor', 'relay', 'proxy'] as const;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SystemAccountService {
|
export class SystemAccountService implements OnApplicationShutdown {
|
||||||
private cache: MemoryKVCache<MiLocalUser>;
|
private cache: MemoryKVCache<MiLocalUser>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.redisForSub)
|
||||||
|
private redisForSub: Redis.Redis,
|
||||||
|
|
||||||
@Inject(DI.db)
|
@Inject(DI.db)
|
||||||
private db: DataSource,
|
private db: DataSource,
|
||||||
|
|
||||||
|
@ -42,6 +48,31 @@ export class SystemAccountService {
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
) {
|
) {
|
||||||
this.cache = new MemoryKVCache<MiLocalUser>(1000 * 60 * 10); // 10m
|
this.cache = new MemoryKVCache<MiLocalUser>(1000 * 60 * 10); // 10m
|
||||||
|
|
||||||
|
this.redisForSub.on('message', this.onMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private async onMessage(_: string, data: string): Promise<void> {
|
||||||
|
const obj = JSON.parse(data);
|
||||||
|
|
||||||
|
if (obj.channel === 'internal') {
|
||||||
|
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
|
||||||
|
switch (type) {
|
||||||
|
case 'metaUpdated': {
|
||||||
|
if (body.before != null && body.before.name !== body.after.name) {
|
||||||
|
for (const account of SYSTEM_ACCOUNT_TYPES) {
|
||||||
|
await this.updateCorrespondingUserProfile(account, {
|
||||||
|
name: body.after.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -145,7 +176,7 @@ export class SystemAccountService {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async updateCorrespondingUserProfile(type: typeof SYSTEM_ACCOUNT_TYPES[number], extra: {
|
public async updateCorrespondingUserProfile(type: typeof SYSTEM_ACCOUNT_TYPES[number], extra: {
|
||||||
name?: string;
|
name?: string | null;
|
||||||
description?: MiUserProfile['description'];
|
description?: MiUserProfile['description'];
|
||||||
}): Promise<MiLocalUser> {
|
}): Promise<MiLocalUser> {
|
||||||
const user = await this.fetch(type);
|
const user = await this.fetch(type);
|
||||||
|
@ -169,4 +200,15 @@ export class SystemAccountService {
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public dispose(): void {
|
||||||
|
this.redisForSub.off('message', this.onMessage);
|
||||||
|
this.cache.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public onApplicationShutdown(signal?: string): void {
|
||||||
|
this.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -411,8 +411,8 @@ export class WebhookTestService {
|
||||||
name: user.name,
|
name: user.name,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
host: user.host,
|
host: user.host,
|
||||||
avatarUrl: user.avatarUrl,
|
avatarUrl: user.avatarId == null ? null : user.avatarUrl,
|
||||||
avatarBlurhash: user.avatarBlurhash,
|
avatarBlurhash: user.avatarId == null ? null : user.avatarBlurhash,
|
||||||
avatarDecorations: user.avatarDecorations.map(it => ({
|
avatarDecorations: user.avatarDecorations.map(it => ({
|
||||||
id: it.id,
|
id: it.id,
|
||||||
angle: it.angle,
|
angle: it.angle,
|
||||||
|
@ -441,8 +441,8 @@ export class WebhookTestService {
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
updatedAt: user.updatedAt?.toISOString() ?? null,
|
updatedAt: user.updatedAt?.toISOString() ?? null,
|
||||||
lastFetchedAt: user.lastFetchedAt?.toISOString() ?? null,
|
lastFetchedAt: user.lastFetchedAt?.toISOString() ?? null,
|
||||||
bannerUrl: user.bannerUrl,
|
bannerUrl: user.bannerId == null ? null : user.bannerUrl,
|
||||||
bannerBlurhash: user.bannerBlurhash,
|
bannerBlurhash: user.bannerId == null ? null : user.bannerBlurhash,
|
||||||
isLocked: user.isLocked,
|
isLocked: user.isLocked,
|
||||||
isSilenced: false,
|
isSilenced: false,
|
||||||
isSuspended: user.isSuspended,
|
isSuspended: user.isSuspended,
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import * as mfm from 'mfm-js';
|
import * as mfm from 'mfm-js';
|
||||||
import { MfmService } from '@/core/MfmService.js';
|
import { MfmService, Appender } from '@/core/MfmService.js';
|
||||||
import type { MiNote } from '@/models/Note.js';
|
import type { MiNote } from '@/models/Note.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { extractApHashtagObjects } from './models/tag.js';
|
import { extractApHashtagObjects } from './models/tag.js';
|
||||||
|
@ -25,17 +25,17 @@ export class ApMfmService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public getNoteHtml(note: Pick<MiNote, 'text' | 'mentionedRemoteUsers'>, apAppend?: string) {
|
public getNoteHtml(note: Pick<MiNote, 'text' | 'mentionedRemoteUsers'>, additionalAppender: Appender[] = []) {
|
||||||
let noMisskeyContent = false;
|
let noMisskeyContent = false;
|
||||||
const srcMfm = (note.text ?? '') + (apAppend ?? '');
|
const srcMfm = (note.text ?? '');
|
||||||
|
|
||||||
const parsed = mfm.parse(srcMfm);
|
const parsed = mfm.parse(srcMfm);
|
||||||
|
|
||||||
if (!apAppend && parsed?.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) {
|
if (!additionalAppender.length && parsed.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) {
|
||||||
noMisskeyContent = true;
|
noMisskeyContent = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = this.mfmService.toHtml(parsed, JSON.parse(note.mentionedRemoteUsers));
|
const content = this.mfmService.toHtml(parsed, JSON.parse(note.mentionedRemoteUsers), additionalAppender);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content,
|
content,
|
||||||
|
|
|
@ -19,7 +19,7 @@ import type { MiEmoji } from '@/models/Emoji.js';
|
||||||
import type { MiPoll } from '@/models/Poll.js';
|
import type { MiPoll } from '@/models/Poll.js';
|
||||||
import type { MiPollVote } from '@/models/PollVote.js';
|
import type { MiPollVote } from '@/models/PollVote.js';
|
||||||
import { UserKeypairService } from '@/core/UserKeypairService.js';
|
import { UserKeypairService } from '@/core/UserKeypairService.js';
|
||||||
import { MfmService } from '@/core/MfmService.js';
|
import { MfmService, type Appender } from '@/core/MfmService.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||||
import type { MiUserKeypair } from '@/models/UserKeypair.js';
|
import type { MiUserKeypair } from '@/models/UserKeypair.js';
|
||||||
|
@ -430,10 +430,24 @@ export class ApRendererService {
|
||||||
poll = await this.pollsRepository.findOneBy({ noteId: note.id });
|
poll = await this.pollsRepository.findOneBy({ noteId: note.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
let apAppend = '';
|
const apAppend: Appender[] = [];
|
||||||
|
|
||||||
if (quote) {
|
if (quote) {
|
||||||
apAppend += `\n\nRE: ${quote}`;
|
// Append quote link as `<br><br><span class="quote-inline">RE: <a href="...">...</a></span>`
|
||||||
|
// the claas name `quote-inline` is used in non-misskey clients for styling quote notes.
|
||||||
|
// For compatibility, the span part should be kept as possible.
|
||||||
|
apAppend.push((doc, body) => {
|
||||||
|
body.appendChild(doc.createElement('br'));
|
||||||
|
body.appendChild(doc.createElement('br'));
|
||||||
|
const span = doc.createElement('span');
|
||||||
|
span.className = 'quote-inline';
|
||||||
|
span.appendChild(doc.createTextNode('RE: '));
|
||||||
|
const link = doc.createElement('a');
|
||||||
|
link.setAttribute('href', quote);
|
||||||
|
link.textContent = quote;
|
||||||
|
span.appendChild(link);
|
||||||
|
body.appendChild(span);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw;
|
const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw;
|
||||||
|
@ -509,7 +523,7 @@ export class ApRendererService {
|
||||||
const urlPart = match[0];
|
const urlPart = match[0];
|
||||||
const urlPartParsed = new URL(urlPart);
|
const urlPartParsed = new URL(urlPart);
|
||||||
const restPart = maybeUrl.slice(match[0].length);
|
const restPart = maybeUrl.slice(match[0].length);
|
||||||
|
|
||||||
return `<a href="${urlPartParsed.href}" rel="me nofollow noopener" target="_blank">${urlPart}</a>${restPart}`;
|
return `<a href="${urlPartParsed.href}" rel="me nofollow noopener" target="_blank">${urlPart}</a>${restPart}`;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return maybeUrl;
|
return maybeUrl;
|
||||||
|
|
|
@ -486,8 +486,8 @@ export class UserEntityService implements OnModuleInit {
|
||||||
name: user.name,
|
name: user.name,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
host: user.host,
|
host: user.host,
|
||||||
avatarUrl: user.avatarUrl ?? this.getIdenticonUrl(user),
|
avatarUrl: (user.avatarId == null ? null : user.avatarUrl) ?? this.getIdenticonUrl(user),
|
||||||
avatarBlurhash: user.avatarBlurhash,
|
avatarBlurhash: (user.avatarId == null ? null : user.avatarBlurhash),
|
||||||
avatarDecorations: user.avatarDecorations.length > 0 ? this.avatarDecorationService.getAll().then(decorations => user.avatarDecorations.filter(ud => decorations.some(d => d.id === ud.id)).map(ud => ({
|
avatarDecorations: user.avatarDecorations.length > 0 ? this.avatarDecorationService.getAll().then(decorations => user.avatarDecorations.filter(ud => decorations.some(d => d.id === ud.id)).map(ud => ({
|
||||||
id: ud.id,
|
id: ud.id,
|
||||||
angle: ud.angle || undefined,
|
angle: ud.angle || undefined,
|
||||||
|
@ -533,8 +533,8 @@ export class UserEntityService implements OnModuleInit {
|
||||||
createdAt: this.idService.parse(user.id).date.toISOString(),
|
createdAt: this.idService.parse(user.id).date.toISOString(),
|
||||||
updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null,
|
updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null,
|
||||||
lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null,
|
lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null,
|
||||||
bannerUrl: user.bannerUrl,
|
bannerUrl: user.bannerId == null ? null : user.bannerUrl,
|
||||||
bannerBlurhash: user.bannerBlurhash,
|
bannerBlurhash: user.bannerId == null ? null : user.bannerBlurhash,
|
||||||
isLocked: user.isLocked,
|
isLocked: user.isLocked,
|
||||||
isSilenced: this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote),
|
isSilenced: this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote),
|
||||||
isSuspended: user.isSuspended,
|
isSuspended: user.isSuspended,
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { MiUser } from './User.js';
|
||||||
import { MiChannel } from './Channel.js';
|
import { MiChannel } from './Channel.js';
|
||||||
import type { MiDriveFile } from './DriveFile.js';
|
import type { MiDriveFile } from './DriveFile.js';
|
||||||
|
|
||||||
|
@Index(['userId', 'id'])
|
||||||
@Entity('note')
|
@Entity('note')
|
||||||
export class MiNote {
|
export class MiNote {
|
||||||
@PrimaryColumn(id())
|
@PrimaryColumn(id())
|
||||||
|
@ -65,7 +66,6 @@ export class MiNote {
|
||||||
})
|
})
|
||||||
public cw: string | null;
|
public cw: string | null;
|
||||||
|
|
||||||
@Index()
|
|
||||||
@Column({
|
@Column({
|
||||||
...id(),
|
...id(),
|
||||||
comment: 'The ID of author.',
|
comment: 'The ID of author.',
|
||||||
|
|
|
@ -118,21 +118,25 @@ export class MiUser {
|
||||||
@JoinColumn()
|
@JoinColumn()
|
||||||
public banner: MiDriveFile | null;
|
public banner: MiDriveFile | null;
|
||||||
|
|
||||||
|
// avatarId が null になったとしてもこれが null でない可能性があるため、このフィールドを使うときは avatarId の non-null チェックをすること
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 512, nullable: true,
|
length: 512, nullable: true,
|
||||||
})
|
})
|
||||||
public avatarUrl: string | null;
|
public avatarUrl: string | null;
|
||||||
|
|
||||||
|
// bannerId が null になったとしてもこれが null でない可能性があるため、このフィールドを使うときは bannerId の non-null チェックをすること
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 512, nullable: true,
|
length: 512, nullable: true,
|
||||||
})
|
})
|
||||||
public bannerUrl: string | null;
|
public bannerUrl: string | null;
|
||||||
|
|
||||||
|
// avatarId が null になったとしてもこれが null でない可能性があるため、このフィールドを使うときは avatarId の non-null チェックをすること
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 128, nullable: true,
|
length: 128, nullable: true,
|
||||||
})
|
})
|
||||||
public avatarBlurhash: string | null;
|
public avatarBlurhash: string | null;
|
||||||
|
|
||||||
|
// bannerId が null になったとしてもこれが null でない可能性があるため、このフィールドを使うときは bannerId の non-null チェックをすること
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 128, nullable: true,
|
length: 128, nullable: true,
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,29 +3,48 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FindOneOptions, InsertQueryBuilder, ObjectLiteral, Repository, SelectQueryBuilder } from 'typeorm';
|
import {
|
||||||
|
FindOneOptions,
|
||||||
|
InsertQueryBuilder,
|
||||||
|
ObjectLiteral,
|
||||||
|
QueryRunner,
|
||||||
|
Repository,
|
||||||
|
SelectQueryBuilder,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js';
|
||||||
import { RelationCountLoader } from 'typeorm/query-builder/relation-count/RelationCountLoader.js';
|
import { RelationCountLoader } from 'typeorm/query-builder/relation-count/RelationCountLoader.js';
|
||||||
import { RelationIdLoader } from 'typeorm/query-builder/relation-id/RelationIdLoader.js';
|
import { RelationIdLoader } from 'typeorm/query-builder/relation-id/RelationIdLoader.js';
|
||||||
import { RawSqlResultsToEntityTransformer } from 'typeorm/query-builder/transformer/RawSqlResultsToEntityTransformer.js';
|
import {
|
||||||
import { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
RawSqlResultsToEntityTransformer,
|
||||||
|
} from 'typeorm/query-builder/transformer/RawSqlResultsToEntityTransformer.js';
|
||||||
import { MiAbuseReportNotificationRecipient } from '@/models/AbuseReportNotificationRecipient.js';
|
import { MiAbuseReportNotificationRecipient } from '@/models/AbuseReportNotificationRecipient.js';
|
||||||
|
import { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
||||||
import { MiAccessToken } from '@/models/AccessToken.js';
|
import { MiAccessToken } from '@/models/AccessToken.js';
|
||||||
import { MiAd } from '@/models/Ad.js';
|
import { MiAd } from '@/models/Ad.js';
|
||||||
import { MiAnnouncement } from '@/models/Announcement.js';
|
import { MiAnnouncement } from '@/models/Announcement.js';
|
||||||
import { MiAnnouncementRead } from '@/models/AnnouncementRead.js';
|
import { MiAnnouncementRead } from '@/models/AnnouncementRead.js';
|
||||||
import { MiAntenna } from '@/models/Antenna.js';
|
import { MiAntenna } from '@/models/Antenna.js';
|
||||||
import { MiApp } from '@/models/App.js';
|
import { MiApp } from '@/models/App.js';
|
||||||
import { MiAvatarDecoration } from '@/models/AvatarDecoration.js';
|
|
||||||
import { MiAuthSession } from '@/models/AuthSession.js';
|
import { MiAuthSession } from '@/models/AuthSession.js';
|
||||||
|
import { MiAvatarDecoration } from '@/models/AvatarDecoration.js';
|
||||||
import { MiBlocking } from '@/models/Blocking.js';
|
import { MiBlocking } from '@/models/Blocking.js';
|
||||||
import { MiChannelFollowing } from '@/models/ChannelFollowing.js';
|
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
|
||||||
|
import { MiChannel } from '@/models/Channel.js';
|
||||||
import { MiChannelFavorite } from '@/models/ChannelFavorite.js';
|
import { MiChannelFavorite } from '@/models/ChannelFavorite.js';
|
||||||
|
import { MiChannelFollowing } from '@/models/ChannelFollowing.js';
|
||||||
|
import { MiChatApproval } from '@/models/ChatApproval.js';
|
||||||
|
import { MiChatMessage } from '@/models/ChatMessage.js';
|
||||||
|
import { MiChatRoom } from '@/models/ChatRoom.js';
|
||||||
|
import { MiChatRoomInvitation } from '@/models/ChatRoomInvitation.js';
|
||||||
|
import { MiChatRoomMembership } from '@/models/ChatRoomMembership.js';
|
||||||
import { MiClip } from '@/models/Clip.js';
|
import { MiClip } from '@/models/Clip.js';
|
||||||
import { MiClipNote } from '@/models/ClipNote.js';
|
|
||||||
import { MiClipFavorite } from '@/models/ClipFavorite.js';
|
import { MiClipFavorite } from '@/models/ClipFavorite.js';
|
||||||
|
import { MiClipNote } from '@/models/ClipNote.js';
|
||||||
import { MiDriveFile } from '@/models/DriveFile.js';
|
import { MiDriveFile } from '@/models/DriveFile.js';
|
||||||
import { MiDriveFolder } from '@/models/DriveFolder.js';
|
import { MiDriveFolder } from '@/models/DriveFolder.js';
|
||||||
import { MiEmoji } from '@/models/Emoji.js';
|
import { MiEmoji } from '@/models/Emoji.js';
|
||||||
|
import { MiFlash } from '@/models/Flash.js';
|
||||||
|
import { MiFlashLike } from '@/models/FlashLike.js';
|
||||||
import { MiFollowing } from '@/models/Following.js';
|
import { MiFollowing } from '@/models/Following.js';
|
||||||
import { MiFollowRequest } from '@/models/FollowRequest.js';
|
import { MiFollowRequest } from '@/models/FollowRequest.js';
|
||||||
import { MiGalleryLike } from '@/models/GalleryLike.js';
|
import { MiGalleryLike } from '@/models/GalleryLike.js';
|
||||||
|
@ -35,7 +54,6 @@ import { MiInstance } from '@/models/Instance.js';
|
||||||
import { MiMeta } from '@/models/Meta.js';
|
import { MiMeta } from '@/models/Meta.js';
|
||||||
import { MiModerationLog } from '@/models/ModerationLog.js';
|
import { MiModerationLog } from '@/models/ModerationLog.js';
|
||||||
import { MiMuting } from '@/models/Muting.js';
|
import { MiMuting } from '@/models/Muting.js';
|
||||||
import { MiRenoteMuting } from '@/models/RenoteMuting.js';
|
|
||||||
import { MiNote } from '@/models/Note.js';
|
import { MiNote } from '@/models/Note.js';
|
||||||
import { MiNoteFavorite } from '@/models/NoteFavorite.js';
|
import { MiNoteFavorite } from '@/models/NoteFavorite.js';
|
||||||
import { MiNoteReaction } from '@/models/NoteReaction.js';
|
import { MiNoteReaction } from '@/models/NoteReaction.js';
|
||||||
|
@ -50,42 +68,38 @@ import { MiPromoRead } from '@/models/PromoRead.js';
|
||||||
import { MiRegistrationTicket } from '@/models/RegistrationTicket.js';
|
import { MiRegistrationTicket } from '@/models/RegistrationTicket.js';
|
||||||
import { MiRegistryItem } from '@/models/RegistryItem.js';
|
import { MiRegistryItem } from '@/models/RegistryItem.js';
|
||||||
import { MiRelay } from '@/models/Relay.js';
|
import { MiRelay } from '@/models/Relay.js';
|
||||||
|
import { MiRenoteMuting } from '@/models/RenoteMuting.js';
|
||||||
|
import { MiRetentionAggregation } from '@/models/RetentionAggregation.js';
|
||||||
|
import { MiReversiGame } from '@/models/ReversiGame.js';
|
||||||
|
import { MiRole } from '@/models/Role.js';
|
||||||
|
import { MiRoleAssignment } from '@/models/RoleAssignment.js';
|
||||||
import { MiSignin } from '@/models/Signin.js';
|
import { MiSignin } from '@/models/Signin.js';
|
||||||
import { MiSwSubscription } from '@/models/SwSubscription.js';
|
import { MiSwSubscription } from '@/models/SwSubscription.js';
|
||||||
import { MiSystemAccount } from '@/models/SystemAccount.js';
|
import { MiSystemAccount } from '@/models/SystemAccount.js';
|
||||||
|
import { MiSystemWebhook } from '@/models/SystemWebhook.js';
|
||||||
import { MiUsedUsername } from '@/models/UsedUsername.js';
|
import { MiUsedUsername } from '@/models/UsedUsername.js';
|
||||||
import { MiUser } from '@/models/User.js';
|
import { MiUser } from '@/models/User.js';
|
||||||
import { MiUserIp } from '@/models/UserIp.js';
|
import { MiUserIp } from '@/models/UserIp.js';
|
||||||
import { MiUserKeypair } from '@/models/UserKeypair.js';
|
import { MiUserKeypair } from '@/models/UserKeypair.js';
|
||||||
import { MiUserList } from '@/models/UserList.js';
|
import { MiUserList } from '@/models/UserList.js';
|
||||||
|
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
|
||||||
import { MiUserListMembership } from '@/models/UserListMembership.js';
|
import { MiUserListMembership } from '@/models/UserListMembership.js';
|
||||||
|
import { MiUserMemo } from '@/models/UserMemo.js';
|
||||||
import { MiUserNotePining } from '@/models/UserNotePining.js';
|
import { MiUserNotePining } from '@/models/UserNotePining.js';
|
||||||
import { MiUserPending } from '@/models/UserPending.js';
|
import { MiUserPending } from '@/models/UserPending.js';
|
||||||
import { MiUserProfile } from '@/models/UserProfile.js';
|
import { MiUserProfile } from '@/models/UserProfile.js';
|
||||||
import { MiUserPublickey } from '@/models/UserPublickey.js';
|
import { MiUserPublickey } from '@/models/UserPublickey.js';
|
||||||
import { MiUserSecurityKey } from '@/models/UserSecurityKey.js';
|
import { MiUserSecurityKey } from '@/models/UserSecurityKey.js';
|
||||||
import { MiUserMemo } from '@/models/UserMemo.js';
|
|
||||||
import { MiWebhook } from '@/models/Webhook.js';
|
import { MiWebhook } from '@/models/Webhook.js';
|
||||||
import { MiSystemWebhook } from '@/models/SystemWebhook.js';
|
|
||||||
import { MiChannel } from '@/models/Channel.js';
|
|
||||||
import { MiRetentionAggregation } from '@/models/RetentionAggregation.js';
|
|
||||||
import { MiRole } from '@/models/Role.js';
|
|
||||||
import { MiRoleAssignment } from '@/models/RoleAssignment.js';
|
|
||||||
import { MiFlash } from '@/models/Flash.js';
|
|
||||||
import { MiFlashLike } from '@/models/FlashLike.js';
|
|
||||||
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
|
|
||||||
import { MiChatMessage } from '@/models/ChatMessage.js';
|
|
||||||
import { MiChatRoom } from '@/models/ChatRoom.js';
|
|
||||||
import { MiChatRoomMembership } from '@/models/ChatRoomMembership.js';
|
|
||||||
import { MiChatRoomInvitation } from '@/models/ChatRoomInvitation.js';
|
|
||||||
import { MiChatApproval } from '@/models/ChatApproval.js';
|
|
||||||
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
|
|
||||||
import { MiReversiGame } from '@/models/ReversiGame.js';
|
|
||||||
import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
|
import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
|
||||||
|
|
||||||
export interface MiRepository<T extends ObjectLiteral> {
|
export interface MiRepository<T extends ObjectLiteral> {
|
||||||
createTableColumnNames(this: Repository<T> & MiRepository<T>): string[];
|
createTableColumnNames(this: Repository<T> & MiRepository<T>): string[];
|
||||||
|
|
||||||
insertOne(this: Repository<T> & MiRepository<T>, entity: QueryDeepPartialEntity<T>, findOptions?: Pick<FindOneOptions<T>, 'relations'>): Promise<T>;
|
insertOne(this: Repository<T> & MiRepository<T>, entity: QueryDeepPartialEntity<T>, findOptions?: Pick<FindOneOptions<T>, 'relations'>): Promise<T>;
|
||||||
|
|
||||||
|
insertOneImpl(this: Repository<T> & MiRepository<T>, entity: QueryDeepPartialEntity<T>, findOptions?: Pick<FindOneOptions<T>, 'relations'>, queryRunner?: QueryRunner): Promise<T>;
|
||||||
|
|
||||||
selectAliasColumnNames(this: Repository<T> & MiRepository<T>, queryBuilder: InsertQueryBuilder<T>, builder: SelectQueryBuilder<T>): void;
|
selectAliasColumnNames(this: Repository<T> & MiRepository<T>, queryBuilder: InsertQueryBuilder<T>, builder: SelectQueryBuilder<T>): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +108,21 @@ export const miRepository = {
|
||||||
return this.metadata.columns.filter(column => column.isSelect && !column.isVirtual).map(column => column.databaseName);
|
return this.metadata.columns.filter(column => column.isSelect && !column.isVirtual).map(column => column.databaseName);
|
||||||
},
|
},
|
||||||
async insertOne(entity, findOptions?) {
|
async insertOne(entity, findOptions?) {
|
||||||
|
const opt = this.manager.connection.options as PostgresConnectionOptions;
|
||||||
|
if (opt.replication) {
|
||||||
|
const queryRunner = this.manager.connection.createQueryRunner('master');
|
||||||
|
try {
|
||||||
|
return this.insertOneImpl(entity, findOptions, queryRunner);
|
||||||
|
} finally {
|
||||||
|
await queryRunner.release();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return this.insertOneImpl(entity, findOptions);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async insertOneImpl(entity, findOptions?, queryRunner?) {
|
||||||
|
// ---- insert + returningの結果を共通テーブル式(CTE)に保持するクエリを生成 ----
|
||||||
|
|
||||||
const queryBuilder = this.createQueryBuilder().insert().values(entity);
|
const queryBuilder = this.createQueryBuilder().insert().values(entity);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const mainAlias = queryBuilder.expressionMap.mainAlias!;
|
const mainAlias = queryBuilder.expressionMap.mainAlias!;
|
||||||
|
@ -101,7 +130,9 @@ export const miRepository = {
|
||||||
mainAlias.name = 't';
|
mainAlias.name = 't';
|
||||||
const columnNames = this.createTableColumnNames();
|
const columnNames = this.createTableColumnNames();
|
||||||
queryBuilder.returning(columnNames.reduce((a, c) => `${a}, ${queryBuilder.escape(c)}`, '').slice(2));
|
queryBuilder.returning(columnNames.reduce((a, c) => `${a}, ${queryBuilder.escape(c)}`, '').slice(2));
|
||||||
const builder = this.createQueryBuilder().addCommonTableExpression(queryBuilder, 'cte', { columnNames });
|
|
||||||
|
// ---- 共通テーブル式(CTE)から結果を取得 ----
|
||||||
|
const builder = this.createQueryBuilder(undefined, queryRunner).addCommonTableExpression(queryBuilder, 'cte', { columnNames });
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
builder.expressionMap.mainAlias!.tablePath = 'cte';
|
builder.expressionMap.mainAlias!.tablePath = 'cte';
|
||||||
this.selectAliasColumnNames(queryBuilder, builder);
|
this.selectAliasColumnNames(queryBuilder, builder);
|
||||||
|
@ -204,7 +235,9 @@ export {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AbuseUserReportsRepository = Repository<MiAbuseUserReport> & MiRepository<MiAbuseUserReport>;
|
export type AbuseUserReportsRepository = Repository<MiAbuseUserReport> & MiRepository<MiAbuseUserReport>;
|
||||||
export type AbuseReportNotificationRecipientRepository = Repository<MiAbuseReportNotificationRecipient> & MiRepository<MiAbuseReportNotificationRecipient>;
|
export type AbuseReportNotificationRecipientRepository =
|
||||||
|
Repository<MiAbuseReportNotificationRecipient>
|
||||||
|
& MiRepository<MiAbuseReportNotificationRecipient>;
|
||||||
export type AccessTokensRepository = Repository<MiAccessToken> & MiRepository<MiAccessToken>;
|
export type AccessTokensRepository = Repository<MiAccessToken> & MiRepository<MiAccessToken>;
|
||||||
export type AdsRepository = Repository<MiAd> & MiRepository<MiAd>;
|
export type AdsRepository = Repository<MiAd> & MiRepository<MiAd>;
|
||||||
export type AnnouncementsRepository = Repository<MiAnnouncement> & MiRepository<MiAnnouncement>;
|
export type AnnouncementsRepository = Repository<MiAnnouncement> & MiRepository<MiAnnouncement>;
|
||||||
|
|
|
@ -224,6 +224,10 @@ export const packedRolePoliciesSchema = {
|
||||||
type: 'integer',
|
type: 'integer',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
maxFileSizeMb: {
|
||||||
|
type: 'integer',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
alwaysMarkNsfw: {
|
alwaysMarkNsfw: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
// https://github.com/typeorm/typeorm/issues/2400
|
// https://github.com/typeorm/typeorm/issues/2400
|
||||||
import pg from 'pg';
|
import pg from 'pg';
|
||||||
import { DataSource, Logger } from 'typeorm';
|
import { DataSource, Logger, type QueryRunner } from 'typeorm';
|
||||||
import * as highlight from 'cli-highlight';
|
import * as highlight from 'cli-highlight';
|
||||||
import { entities as charts } from '@/core/chart/entities.js';
|
import { entities as charts } from '@/core/chart/entities.js';
|
||||||
import { Config } from '@/config.js';
|
import { Config } from '@/config.js';
|
||||||
|
@ -96,6 +96,7 @@ const sqlLogger = dbLogger.createSubLogger('sql', 'gray');
|
||||||
export type LoggerProps = {
|
export type LoggerProps = {
|
||||||
disableQueryTruncation?: boolean;
|
disableQueryTruncation?: boolean;
|
||||||
enableQueryParamLogging?: boolean;
|
enableQueryParamLogging?: boolean;
|
||||||
|
printReplicationMode?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function highlightSql(sql: string) {
|
function highlightSql(sql: string) {
|
||||||
|
@ -121,8 +122,10 @@ class MyCustomLogger implements Logger {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private transformQueryLog(sql: string) {
|
private transformQueryLog(sql: string, opts?: {
|
||||||
let modded = sql;
|
prefix?: string;
|
||||||
|
}) {
|
||||||
|
let modded = opts?.prefix ? opts.prefix + sql : sql;
|
||||||
if (!this.props.disableQueryTruncation) {
|
if (!this.props.disableQueryTruncation) {
|
||||||
modded = truncateSql(modded);
|
modded = truncateSql(modded);
|
||||||
}
|
}
|
||||||
|
@ -140,18 +143,27 @@ class MyCustomLogger implements Logger {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public logQuery(query: string, parameters?: any[]) {
|
public logQuery(query: string, parameters?: any[], queryRunner?: QueryRunner) {
|
||||||
sqlLogger.info(this.transformQueryLog(query), this.transformParameters(parameters));
|
const prefix = (this.props.printReplicationMode && queryRunner)
|
||||||
|
? `[${queryRunner.getReplicationMode()}] `
|
||||||
|
: undefined;
|
||||||
|
sqlLogger.info(this.transformQueryLog(query, { prefix }), this.transformParameters(parameters));
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public logQueryError(error: string, query: string, parameters?: any[]) {
|
public logQueryError(error: string, query: string, parameters?: any[], queryRunner?: QueryRunner) {
|
||||||
sqlLogger.error(this.transformQueryLog(query), this.transformParameters(parameters));
|
const prefix = (this.props.printReplicationMode && queryRunner)
|
||||||
|
? `[${queryRunner.getReplicationMode()}] `
|
||||||
|
: undefined;
|
||||||
|
sqlLogger.error(this.transformQueryLog(query, { prefix }), this.transformParameters(parameters));
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public logQuerySlow(time: number, query: string, parameters?: any[]) {
|
public logQuerySlow(time: number, query: string, parameters?: any[], queryRunner?: QueryRunner) {
|
||||||
sqlLogger.warn(this.transformQueryLog(query), this.transformParameters(parameters));
|
const prefix = (this.props.printReplicationMode && queryRunner)
|
||||||
|
? `[${queryRunner.getReplicationMode()}] `
|
||||||
|
: undefined;
|
||||||
|
sqlLogger.warn(this.transformQueryLog(query, { prefix }), this.transformParameters(parameters));
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -298,6 +310,7 @@ export function createPostgresDataSource(config: Config) {
|
||||||
? new MyCustomLogger({
|
? new MyCustomLogger({
|
||||||
disableQueryTruncation: config.logging?.sql?.disableQueryTruncation,
|
disableQueryTruncation: config.logging?.sql?.disableQueryTruncation,
|
||||||
enableQueryParamLogging: config.logging?.sql?.enableQueryParamLogging,
|
enableQueryParamLogging: config.logging?.sql?.enableQueryParamLogging,
|
||||||
|
printReplicationMode: !!config.dbReplications,
|
||||||
})
|
})
|
||||||
: undefined,
|
: undefined,
|
||||||
maxQueryExecutionTime: 300,
|
maxQueryExecutionTime: 300,
|
||||||
|
|
|
@ -44,7 +44,7 @@ import { BakeBufferedReactionsProcessorService } from './processors/BakeBuffered
|
||||||
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
||||||
import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
|
import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
|
||||||
import { QueueLoggerService } from './QueueLoggerService.js';
|
import { QueueLoggerService } from './QueueLoggerService.js';
|
||||||
import { QUEUE, baseQueueOptions } from './const.js';
|
import { QUEUE, baseWorkerOptions } from './const.js';
|
||||||
|
|
||||||
// ref. https://github.com/misskey-dev/misskey/pull/7635#issue-971097019
|
// ref. https://github.com/misskey-dev/misskey/pull/7635#issue-971097019
|
||||||
function httpRelatedBackoff(attemptsMade: number) {
|
function httpRelatedBackoff(attemptsMade: number) {
|
||||||
|
@ -175,7 +175,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||||
return processer(job);
|
return processer(job);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
...baseQueueOptions(this.config, QUEUE.SYSTEM),
|
...baseWorkerOptions(this.config, QUEUE.SYSTEM),
|
||||||
autorun: false,
|
autorun: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -232,7 +232,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||||
return processer(job);
|
return processer(job);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
...baseQueueOptions(this.config, QUEUE.DB),
|
...baseWorkerOptions(this.config, QUEUE.DB),
|
||||||
autorun: false,
|
autorun: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -264,7 +264,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||||
return this.deliverProcessorService.process(job);
|
return this.deliverProcessorService.process(job);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
...baseQueueOptions(this.config, QUEUE.DELIVER),
|
...baseWorkerOptions(this.config, QUEUE.DELIVER),
|
||||||
autorun: false,
|
autorun: false,
|
||||||
concurrency: this.config.deliverJobConcurrency ?? 128,
|
concurrency: this.config.deliverJobConcurrency ?? 128,
|
||||||
limiter: {
|
limiter: {
|
||||||
|
@ -304,7 +304,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||||
return this.inboxProcessorService.process(job);
|
return this.inboxProcessorService.process(job);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
...baseQueueOptions(this.config, QUEUE.INBOX),
|
...baseWorkerOptions(this.config, QUEUE.INBOX),
|
||||||
autorun: false,
|
autorun: false,
|
||||||
concurrency: this.config.inboxJobConcurrency ?? 16,
|
concurrency: this.config.inboxJobConcurrency ?? 16,
|
||||||
limiter: {
|
limiter: {
|
||||||
|
@ -344,7 +344,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||||
return this.userWebhookDeliverProcessorService.process(job);
|
return this.userWebhookDeliverProcessorService.process(job);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
...baseQueueOptions(this.config, QUEUE.USER_WEBHOOK_DELIVER),
|
...baseWorkerOptions(this.config, QUEUE.USER_WEBHOOK_DELIVER),
|
||||||
autorun: false,
|
autorun: false,
|
||||||
concurrency: 64,
|
concurrency: 64,
|
||||||
limiter: {
|
limiter: {
|
||||||
|
@ -384,7 +384,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||||
return this.systemWebhookDeliverProcessorService.process(job);
|
return this.systemWebhookDeliverProcessorService.process(job);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
...baseQueueOptions(this.config, QUEUE.SYSTEM_WEBHOOK_DELIVER),
|
...baseWorkerOptions(this.config, QUEUE.SYSTEM_WEBHOOK_DELIVER),
|
||||||
autorun: false,
|
autorun: false,
|
||||||
concurrency: 16,
|
concurrency: 16,
|
||||||
limiter: {
|
limiter: {
|
||||||
|
@ -434,7 +434,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||||
return processer(job);
|
return processer(job);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
...baseQueueOptions(this.config, QUEUE.RELATIONSHIP),
|
...baseWorkerOptions(this.config, QUEUE.RELATIONSHIP),
|
||||||
autorun: false,
|
autorun: false,
|
||||||
concurrency: this.config.relationshipJobConcurrency ?? 16,
|
concurrency: this.config.relationshipJobConcurrency ?? 16,
|
||||||
limiter: {
|
limiter: {
|
||||||
|
@ -479,7 +479,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||||
return processer(job);
|
return processer(job);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
...baseQueueOptions(this.config, QUEUE.OBJECT_STORAGE),
|
...baseWorkerOptions(this.config, QUEUE.OBJECT_STORAGE),
|
||||||
autorun: false,
|
autorun: false,
|
||||||
concurrency: 16,
|
concurrency: 16,
|
||||||
});
|
});
|
||||||
|
@ -512,7 +512,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||||
return this.endedPollNotificationProcessorService.process(job);
|
return this.endedPollNotificationProcessorService.process(job);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
...baseQueueOptions(this.config, QUEUE.ENDED_POLL_NOTIFICATION),
|
...baseWorkerOptions(this.config, QUEUE.ENDED_POLL_NOTIFICATION),
|
||||||
autorun: false,
|
autorun: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { MetricsTime } from 'bullmq';
|
||||||
import { Config } from '@/config.js';
|
import { Config } from '@/config.js';
|
||||||
import type * as Bull from 'bullmq';
|
import type * as Bull from 'bullmq';
|
||||||
|
|
||||||
|
@ -27,3 +28,12 @@ export function baseQueueOptions(config: Config, queueName: typeof QUEUE[keyof t
|
||||||
prefix: config.redisForJobQueue.prefix ? `${config.redisForJobQueue.prefix}:queue:${queueName}` : `queue:${queueName}`,
|
prefix: config.redisForJobQueue.prefix ? `${config.redisForJobQueue.prefix}:queue:${queueName}` : `queue:${queueName}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function baseWorkerOptions(config: Config, queueName: typeof QUEUE[keyof typeof QUEUE]): Bull.WorkerOptions {
|
||||||
|
return {
|
||||||
|
...baseQueueOptions(config, queueName),
|
||||||
|
metrics: {
|
||||||
|
maxDataPoints: MetricsTime.ONE_WEEK,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||||
import * as Acct from '@/misc/acct.js';
|
import * as Acct from '@/misc/acct.js';
|
||||||
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify';
|
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify';
|
||||||
import type { FindOptionsWhere } from 'typeorm';
|
import type { FindOptionsWhere } from 'typeorm';
|
||||||
|
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
||||||
|
|
||||||
const ACTIVITY_JSON = 'application/activity+json; charset=utf-8';
|
const ACTIVITY_JSON = 'application/activity+json; charset=utf-8';
|
||||||
const LD_JSON = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8';
|
const LD_JSON = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8';
|
||||||
|
@ -75,6 +76,7 @@ export class ActivityPubServerService {
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
private userKeypairService: UserKeypairService,
|
private userKeypairService: UserKeypairService,
|
||||||
private queryService: QueryService,
|
private queryService: QueryService,
|
||||||
|
private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
|
||||||
) {
|
) {
|
||||||
//this.createServer = this.createServer.bind(this);
|
//this.createServer = this.createServer.bind(this);
|
||||||
}
|
}
|
||||||
|
@ -461,16 +463,28 @@ export class ActivityPubServerService {
|
||||||
const partOf = `${this.config.url}/users/${userId}/outbox`;
|
const partOf = `${this.config.url}/users/${userId}/outbox`;
|
||||||
|
|
||||||
if (page) {
|
if (page) {
|
||||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), sinceId, untilId)
|
const notes = this.meta.enableFanoutTimeline ? await this.fanoutTimelineEndpointService.getMiNotes({
|
||||||
.andWhere('note.userId = :userId', { userId: user.id })
|
sinceId: sinceId ?? null,
|
||||||
.andWhere(new Brackets(qb => {
|
untilId: untilId ?? null,
|
||||||
qb
|
limit: limit,
|
||||||
.where('note.visibility = \'public\'')
|
allowPartial: false, // Possibly true? IDK it's OK for ordered collection.
|
||||||
.orWhere('note.visibility = \'home\'');
|
me: null,
|
||||||
}))
|
redisTimelines: [
|
||||||
.andWhere('note.localOnly = FALSE');
|
`userTimeline:${user.id}`,
|
||||||
|
`userTimelineWithReplies:${user.id}`,
|
||||||
const notes = await query.limit(limit).getMany();
|
],
|
||||||
|
useDbFallback: true,
|
||||||
|
ignoreAuthorFromMute: true,
|
||||||
|
excludePureRenotes: false,
|
||||||
|
noteFilter: (note) => {
|
||||||
|
if (note.visibility !== 'home' && note.visibility !== 'public') return false;
|
||||||
|
if (note.localOnly) return false;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
dbFallback: async (untilId, sinceId, limit) => {
|
||||||
|
return await this.getUserNotesFromDb(sinceId, untilId, limit, user.id);
|
||||||
|
},
|
||||||
|
}) : await this.getUserNotesFromDb(sinceId ?? null, untilId ?? null, limit, user.id);
|
||||||
|
|
||||||
if (sinceId) notes.reverse();
|
if (sinceId) notes.reverse();
|
||||||
|
|
||||||
|
@ -508,6 +522,20 @@ export class ActivityPubServerService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private async getUserNotesFromDb(untilId: string | null, sinceId: string | null, limit: number, userId: MiUser['id']) {
|
||||||
|
return await this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), sinceId, untilId)
|
||||||
|
.andWhere('note.userId = :userId', { userId })
|
||||||
|
.andWhere(new Brackets(qb => {
|
||||||
|
qb
|
||||||
|
.where('note.visibility = \'public\'')
|
||||||
|
.orWhere('note.visibility = \'home\'');
|
||||||
|
}))
|
||||||
|
.andWhere('note.localOnly = FALSE')
|
||||||
|
.limit(limit)
|
||||||
|
.getMany();
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async userInfo(request: FastifyRequest, reply: FastifyReply, user: MiUser | null) {
|
private async userInfo(request: FastifyRequest, reply: FastifyReply, user: MiUser | null) {
|
||||||
if (this.meta.federation === 'none') {
|
if (this.meta.federation === 'none') {
|
||||||
|
@ -735,7 +763,7 @@ export class ActivityPubServerService {
|
||||||
const acct = Acct.parse(request.params.acct);
|
const acct = Acct.parse(request.params.acct);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({
|
const user = await this.usersRepository.findOneBy({
|
||||||
usernameLower: acct.username,
|
usernameLower: acct.username.toLowerCase(),
|
||||||
host: acct.host ?? IsNull(),
|
host: acct.host ?? IsNull(),
|
||||||
isSuspended: false,
|
isSuspended: false,
|
||||||
});
|
});
|
||||||
|
|
|
@ -221,7 +221,7 @@ export class ServerService implements OnApplicationShutdown {
|
||||||
reply.header('Cache-Control', 'public, max-age=86400');
|
reply.header('Cache-Control', 'public, max-age=86400');
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
reply.redirect(user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user));
|
reply.redirect((user.avatarId == null ? null : user.avatarUrl) ?? this.userEntityService.getIdenticonUrl(user));
|
||||||
} else {
|
} else {
|
||||||
reply.redirect('/static-assets/user-unknown.png');
|
reply.redirect('/static-assets/user-unknown.png');
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,7 +138,7 @@ fastify.get('/.well-known/change-password', async (request, reply) => {
|
||||||
|
|
||||||
const fromAcct = (acct: Acct.Acct): FindOptionsWhere<MiUser> | number =>
|
const fromAcct = (acct: Acct.Acct): FindOptionsWhere<MiUser> | number =>
|
||||||
!acct.host || acct.host === this.config.host.toLowerCase() ? {
|
!acct.host || acct.host === this.config.host.toLowerCase() ? {
|
||||||
usernameLower: acct.username,
|
usernameLower: acct.username.toLowerCase(),
|
||||||
host: IsNull(),
|
host: IsNull(),
|
||||||
isSuspended: false,
|
isSuspended: false,
|
||||||
} : 422;
|
} : 422;
|
||||||
|
|
|
@ -67,8 +67,14 @@ export * as 'admin/promo/create' from './endpoints/admin/promo/create.js';
|
||||||
export * as 'admin/queue/clear' from './endpoints/admin/queue/clear.js';
|
export * as 'admin/queue/clear' from './endpoints/admin/queue/clear.js';
|
||||||
export * as 'admin/queue/deliver-delayed' from './endpoints/admin/queue/deliver-delayed.js';
|
export * as 'admin/queue/deliver-delayed' from './endpoints/admin/queue/deliver-delayed.js';
|
||||||
export * as 'admin/queue/inbox-delayed' from './endpoints/admin/queue/inbox-delayed.js';
|
export * as 'admin/queue/inbox-delayed' from './endpoints/admin/queue/inbox-delayed.js';
|
||||||
export * as 'admin/queue/promote' from './endpoints/admin/queue/promote.js';
|
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/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';
|
export * as 'admin/queue/stats' from './endpoints/admin/queue/stats.js';
|
||||||
|
export * as 'admin/queue/queues' from './endpoints/admin/queue/queues.js';
|
||||||
|
export * as 'admin/queue/queue-stats' from './endpoints/admin/queue/queue-stats.js';
|
||||||
export * as 'admin/relays/add' from './endpoints/admin/relays/add.js';
|
export * as 'admin/relays/add' from './endpoints/admin/relays/add.js';
|
||||||
export * as 'admin/relays/list' from './endpoints/admin/relays/list.js';
|
export * as 'admin/relays/list' from './endpoints/admin/relays/list.js';
|
||||||
export * as 'admin/relays/remove' from './endpoints/admin/relays/remove.js';
|
export * as 'admin/relays/remove' from './endpoints/admin/relays/remove.js';
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['admin'],
|
tags: ['admin'],
|
||||||
|
@ -18,8 +18,11 @@ export const meta = {
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {},
|
properties: {
|
||||||
required: [],
|
queue: { type: 'string', enum: QUEUE_TYPES },
|
||||||
|
state: { type: 'string', enum: ['*', 'completed', 'wait', 'active', 'paused', 'prioritized', 'delayed', 'failed'] },
|
||||||
|
},
|
||||||
|
required: ['queue', 'state'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -29,7 +32,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
this.queueService.destroy();
|
this.queueService.queueClear(ps.queue, ps.state);
|
||||||
|
|
||||||
this.moderationLogService.log(me, 'clearQueue');
|
this.moderationLogService.log(me, 'clearQueue');
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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 { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['admin'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireModerator: true,
|
||||||
|
kind: 'read:admin:queue',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
queue: { type: 'string', enum: QUEUE_TYPES },
|
||||||
|
state: { type: 'array', items: { type: 'string', enum: ['active', 'wait', 'delayed', 'completed', 'failed'] } },
|
||||||
|
search: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['queue', 'state'],
|
||||||
|
} 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.queueGetJobs(ps.queue, ps.state, ps.search);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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 { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['admin'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireModerator: true,
|
||||||
|
kind: 'write:admin:queue',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
queue: { type: 'string', enum: QUEUE_TYPES },
|
||||||
|
},
|
||||||
|
required: ['queue'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
private moderationLogService: ModerationLogService,
|
||||||
|
private queueService: QueueService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
this.queueService.queuePromoteJobs(ps.queue);
|
||||||
|
|
||||||
|
this.moderationLogService.log(me, 'promoteQueue');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,77 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 { ModerationLogService } from '@/core/ModerationLogService.js';
|
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
|
||||||
|
|
||||||
export const meta = {
|
|
||||||
tags: ['admin'],
|
|
||||||
|
|
||||||
requireCredential: true,
|
|
||||||
requireModerator: true,
|
|
||||||
kind: 'write:admin:queue',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const paramDef = {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
type: { type: 'string', enum: ['deliver', 'inbox'] },
|
|
||||||
},
|
|
||||||
required: ['type'],
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
|
||||||
constructor(
|
|
||||||
private moderationLogService: ModerationLogService,
|
|
||||||
private queueService: QueueService,
|
|
||||||
) {
|
|
||||||
super(meta, paramDef, async (ps, me) => {
|
|
||||||
let delayedQueues;
|
|
||||||
|
|
||||||
switch (ps.type) {
|
|
||||||
case 'deliver':
|
|
||||||
delayedQueues = await this.queueService.deliverQueue.getDelayed();
|
|
||||||
for (let queueIndex = 0; queueIndex < delayedQueues.length; queueIndex++) {
|
|
||||||
const queue = delayedQueues[queueIndex];
|
|
||||||
try {
|
|
||||||
await queue.promote();
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
if (e.message.indexOf('not in a delayed state') !== -1) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'inbox':
|
|
||||||
delayedQueues = await this.queueService.inboxQueue.getDelayed();
|
|
||||||
for (let queueIndex = 0; queueIndex < delayedQueues.length; queueIndex++) {
|
|
||||||
const queue = delayedQueues[queueIndex];
|
|
||||||
try {
|
|
||||||
await queue.promote();
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
if (e.message.indexOf('not in a delayed state') !== -1) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.moderationLogService.log(me, 'promoteQueue');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* 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 { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['admin'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireModerator: true,
|
||||||
|
kind: 'read:admin:queue',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
queue: { type: 'string', enum: QUEUE_TYPES },
|
||||||
|
},
|
||||||
|
required: ['queue'],
|
||||||
|
} 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.queueGetQueue(ps.queue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* 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 { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['admin'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireModerator: true,
|
||||||
|
kind: 'read:admin:queue',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
},
|
||||||
|
required: [],
|
||||||
|
} 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.queueGetQueues();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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 { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['admin'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireModerator: true,
|
||||||
|
kind: 'write:admin:queue',
|
||||||
|
} 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 moderationLogService: ModerationLogService,
|
||||||
|
private queueService: QueueService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
this.queueService.queueRemoveJob(ps.queue, ps.jobId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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 { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['admin'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireModerator: true,
|
||||||
|
kind: 'write:admin:queue',
|
||||||
|
} 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 moderationLogService: ModerationLogService,
|
||||||
|
private queueService: QueueService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
this.queueService.queueRetryJob(ps.queue, ps.jobId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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 { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['admin'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireModerator: true,
|
||||||
|
kind: 'read:admin:queue',
|
||||||
|
} 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 moderationLogService: ModerationLogService,
|
||||||
|
private queueService: QueueService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
return this.queueService.queueGetJob(ps.queue, ps.jobId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,9 +10,9 @@ import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||||
import { DriveService } from '@/core/DriveService.js';
|
import { DriveService } from '@/core/DriveService.js';
|
||||||
import { ApiError } from '../../../error.js';
|
|
||||||
import { MiMeta } from '@/models/_.js';
|
import { MiMeta } from '@/models/_.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { ApiError } from '../../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['drive'],
|
tags: ['drive'],
|
||||||
|
@ -56,6 +56,12 @@ export const meta = {
|
||||||
code: 'NO_FREE_SPACE',
|
code: 'NO_FREE_SPACE',
|
||||||
id: 'd08dbc37-a6a9-463a-8c47-96c32ab5f064',
|
id: 'd08dbc37-a6a9-463a-8c47-96c32ab5f064',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
maxFileSizeExceeded: {
|
||||||
|
message: 'Cannot upload the file because it exceeds the maximum file size.',
|
||||||
|
code: 'MAX_FILE_SIZE_EXCEEDED',
|
||||||
|
id: 'b9d8c348-33f0-4673-b9a9-5d4da058977a',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
@ -115,6 +121,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
if (err instanceof IdentifiableError) {
|
if (err instanceof IdentifiableError) {
|
||||||
if (err.id === '282f77bf-5816-4f72-9264-aa14d8261a21') throw new ApiError(meta.errors.inappropriate);
|
if (err.id === '282f77bf-5816-4f72-9264-aa14d8261a21') throw new ApiError(meta.errors.inappropriate);
|
||||||
if (err.id === 'c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6') throw new ApiError(meta.errors.noFreeSpace);
|
if (err.id === 'c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6') throw new ApiError(meta.errors.noFreeSpace);
|
||||||
|
if (err.id === 'f9e4e5f3-4df4-40b5-b400-f236945f7073') throw new ApiError(meta.errors.maxFileSizeExceeded);
|
||||||
}
|
}
|
||||||
throw new ApiError();
|
throw new ApiError();
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -534,7 +534,7 @@ export class ClientServerService {
|
||||||
|
|
||||||
return await reply.view('user', {
|
return await reply.view('user', {
|
||||||
user, profile, me,
|
user, profile, me,
|
||||||
avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
|
avatarUrl: _user.avatarUrl,
|
||||||
sub: request.params.sub,
|
sub: request.params.sub,
|
||||||
...await this.generateCommonPugData(this.meta),
|
...await this.generateCommonPugData(this.meta),
|
||||||
clientCtx: htmlSafeJsonStringify({
|
clientCtx: htmlSafeJsonStringify({
|
||||||
|
|
|
@ -65,7 +65,7 @@ export class FeedService {
|
||||||
generator: 'Misskey',
|
generator: 'Misskey',
|
||||||
description: `${user.notesCount} Notes, ${profile.followingVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.followersVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`,
|
description: `${user.notesCount} Notes, ${profile.followingVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.followersVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`,
|
||||||
link: author.link,
|
link: author.link,
|
||||||
image: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
|
image: (user.avatarId == null ? null : user.avatarUrl) ?? this.userEntityService.getIdenticonUrl(user),
|
||||||
feedLinks: {
|
feedLinks: {
|
||||||
json: `${author.link}.json`,
|
json: `${author.link}.json`,
|
||||||
atom: `${author.link}.atom`,
|
atom: `${author.link}.atom`,
|
||||||
|
|
|
@ -381,7 +381,8 @@ describe('User', () => {
|
||||||
|
|
||||||
await alice.client.request('i/delete-account', { password: alice.password });
|
await alice.client.request('i/delete-account', { password: alice.password });
|
||||||
// NOTE: user deletion query is slow
|
// NOTE: user deletion query is slow
|
||||||
await sleep(4000);
|
// FIXME: ensure user is removed successfully
|
||||||
|
await sleep(10000);
|
||||||
|
|
||||||
const following = await bob.client.request('users/following', { userId: bob.id });
|
const following = await bob.client.request('users/following', { userId: bob.id });
|
||||||
strictEqual(following.length, 0); // no following relation
|
strictEqual(following.length, 0); // no following relation
|
||||||
|
@ -480,7 +481,8 @@ describe('User', () => {
|
||||||
|
|
||||||
await aAdmin.client.request('admin/suspend-user', { userId: alice.id });
|
await aAdmin.client.request('admin/suspend-user', { userId: alice.id });
|
||||||
// NOTE: user deletion query is slow
|
// NOTE: user deletion query is slow
|
||||||
await sleep(4000);
|
// FIXME: ensure user is removed successfully
|
||||||
|
await sleep(10000);
|
||||||
|
|
||||||
const following = await bob.client.request('users/following', { userId: bob.id });
|
const following = await bob.client.request('users/following', { userId: bob.id });
|
||||||
strictEqual(following.length, 0); // no following relation
|
strictEqual(following.length, 0); // no following relation
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
|
||||||
import {
|
import {
|
||||||
api,
|
api,
|
||||||
failedApiCall,
|
failedApiCall,
|
||||||
|
@ -19,6 +18,7 @@ import {
|
||||||
userList,
|
userList,
|
||||||
} from '../utils.js';
|
} from '../utils.js';
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
||||||
|
|
||||||
const compareBy = <T extends { id: string }>(selector: (s: T) => string = (s: T): string => s.id) => (a: T, b: T): number => {
|
const compareBy = <T extends { id: string }>(selector: (s: T) => string = (s: T): string => s.id) => (a: T, b: T): number => {
|
||||||
return selector(a).localeCompare(selector(b));
|
return selector(a).localeCompare(selector(b));
|
||||||
|
@ -235,12 +235,12 @@ describe('アンテナ', () => {
|
||||||
await failedApiCall({
|
await failedApiCall({
|
||||||
endpoint: 'antennas/create',
|
endpoint: 'antennas/create',
|
||||||
parameters: { ...defaultParam, keywords: [[]], excludeKeywords: [[]] },
|
parameters: { ...defaultParam, keywords: [[]], excludeKeywords: [[]] },
|
||||||
user: alice
|
user: alice,
|
||||||
}, {
|
}, {
|
||||||
status: 400,
|
status: 400,
|
||||||
code: 'EMPTY_KEYWORD',
|
code: 'EMPTY_KEYWORD',
|
||||||
id: '53ee222e-1ddd-4f9a-92e5-9fb82ddb463a'
|
id: '53ee222e-1ddd-4f9a-92e5-9fb82ddb463a',
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
//#region 更新(antennas/update)
|
//#region 更新(antennas/update)
|
||||||
|
@ -274,12 +274,12 @@ describe('アンテナ', () => {
|
||||||
await failedApiCall({
|
await failedApiCall({
|
||||||
endpoint: 'antennas/update',
|
endpoint: 'antennas/update',
|
||||||
parameters: { ...defaultParam, antennaId: antenna.id, keywords: [[]], excludeKeywords: [[]] },
|
parameters: { ...defaultParam, antennaId: antenna.id, keywords: [[]], excludeKeywords: [[]] },
|
||||||
user: alice
|
user: alice,
|
||||||
}, {
|
}, {
|
||||||
status: 400,
|
status: 400,
|
||||||
code: 'EMPTY_KEYWORD',
|
code: 'EMPTY_KEYWORD',
|
||||||
id: '721aaff6-4e1b-4d88-8de6-877fae9f68c4'
|
id: '721aaff6-4e1b-4d88-8de6-877fae9f68c4',
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
@ -375,14 +375,23 @@ describe('アンテナ', () => {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// https://github.com/misskey-dev/misskey/issues/9025
|
label: 'フォロワー限定投稿とDM投稿を含む',
|
||||||
label: 'ただし、フォロワー限定投稿とDM投稿を含まない。フォロワーであっても。',
|
|
||||||
parameters: () => ({}),
|
parameters: () => ({}),
|
||||||
posts: [
|
posts: [
|
||||||
{ note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'public' }), included: true },
|
{ note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'public' }), included: true },
|
||||||
{ note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'home' }), included: true },
|
{ note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'home' }), included: true },
|
||||||
{ note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'followers' }) },
|
{ note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'followers' }), included: true },
|
||||||
{ note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'specified', visibleUserIds: [alice.id] }) },
|
{ note: (): Promise<Note> => post(bob, { text: `${keyword}`, visibility: 'specified', visibleUserIds: [alice.id] }), included: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'フォロワー限定投稿とDM投稿を含まない',
|
||||||
|
parameters: () => ({}),
|
||||||
|
posts: [
|
||||||
|
{ note: (): Promise<Note> => post(bob, { text: `${keyword}`, visibility: 'public' }), included: true },
|
||||||
|
{ note: (): Promise<Note> => post(bob, { text: `${keyword}`, visibility: 'home' }), included: true },
|
||||||
|
{ note: (): Promise<Note> => post(bob, { text: `${keyword}`, visibility: 'followers' }) },
|
||||||
|
{ note: (): Promise<Note> => post(bob, { text: `${keyword}`, visibility: 'specified', visibleUserIds: [carol.id] }) },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -44,7 +44,7 @@ describe('AnnouncementService', () => {
|
||||||
return usersRepository.insert({
|
return usersRepository.insert({
|
||||||
id: genAidx(Date.now()),
|
id: genAidx(Date.now()),
|
||||||
username: un,
|
username: un,
|
||||||
usernameLower: un,
|
usernameLower: un.toLowerCase(),
|
||||||
...data,
|
...data,
|
||||||
})
|
})
|
||||||
.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
|
.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
|
||||||
|
|
|
@ -89,8 +89,8 @@ describe('SigninWithPasskeyApiService', () => {
|
||||||
app = await Test.createTestingModule({
|
app = await Test.createTestingModule({
|
||||||
imports: [GlobalModule, CoreModule],
|
imports: [GlobalModule, CoreModule],
|
||||||
providers: [
|
providers: [
|
||||||
SigninWithPasskeyApiService,
|
SigninWithPasskeyApiService,
|
||||||
{ provide: RateLimiterService, useClass: FakeLimiter },
|
{ provide: RateLimiterService, useClass: FakeLimiter },
|
||||||
{ provide: SigninService, useClass: FakeSigninService },
|
{ provide: SigninService, useClass: FakeSigninService },
|
||||||
],
|
],
|
||||||
}).useMocker((token) => {
|
}).useMocker((token) => {
|
||||||
|
@ -115,7 +115,7 @@ describe('SigninWithPasskeyApiService', () => {
|
||||||
jest.spyOn(webAuthnService, 'verifySignInWithPasskeyAuthentication').mockImplementation(FakeWebauthnVerify);
|
jest.spyOn(webAuthnService, 'verifySignInWithPasskeyAuthentication').mockImplementation(FakeWebauthnVerify);
|
||||||
|
|
||||||
const dummyUser = {
|
const dummyUser = {
|
||||||
id: uid, username: uid, usernameLower: uid.toLocaleLowerCase(), uri: null, host: null,
|
id: uid, username: uid, usernameLower: uid.toLowerCase(), uri: null, host: null,
|
||||||
};
|
};
|
||||||
const dummyProfile = {
|
const dummyProfile = {
|
||||||
userId: uid,
|
userId: uid,
|
||||||
|
|
|
@ -74,7 +74,7 @@ describe('UserEntityService', () => {
|
||||||
...userData,
|
...userData,
|
||||||
id: genAidx(Date.now()),
|
id: genAidx(Date.now()),
|
||||||
username: un,
|
username: un,
|
||||||
usernameLower: un,
|
usernameLower: un.toLowerCase(),
|
||||||
})
|
})
|
||||||
.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
|
.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
|
||||||
|
|
||||||
|
|
|
@ -21,34 +21,34 @@
|
||||||
"astring": "1.9.0",
|
"astring": "1.9.0",
|
||||||
"buraha": "0.0.1",
|
"buraha": "0.0.1",
|
||||||
"estree-walker": "3.0.3",
|
"estree-walker": "3.0.3",
|
||||||
|
"frontend-shared": "workspace:*",
|
||||||
|
"json5": "2.2.3",
|
||||||
"mfm-js": "0.24.0",
|
"mfm-js": "0.24.0",
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
"frontend-shared": "workspace:*",
|
|
||||||
"punycode.js": "2.3.1",
|
"punycode.js": "2.3.1",
|
||||||
"rollup": "4.36.0",
|
"rollup": "4.39.0",
|
||||||
"sass": "1.86.0",
|
"sass": "1.86.3",
|
||||||
"shiki": "3.2.1",
|
"shiki": "3.2.2",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tsc-alias": "1.8.11",
|
"tsc-alias": "1.8.15",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typescript": "5.8.2",
|
"typescript": "5.8.3",
|
||||||
"uuid": "11.1.0",
|
"uuid": "11.1.0",
|
||||||
"json5": "2.2.3",
|
"vite": "6.3.1",
|
||||||
"vite": "6.2.4",
|
|
||||||
"vue": "3.5.13"
|
"vue": "3.5.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/summaly": "5.2.0",
|
"@misskey-dev/summaly": "5.2.0",
|
||||||
"@testing-library/vue": "8.1.0",
|
"@testing-library/vue": "8.1.0",
|
||||||
"@types/estree": "1.0.6",
|
"@types/estree": "1.0.7",
|
||||||
"@types/micromatch": "4.0.9",
|
"@types/micromatch": "4.0.9",
|
||||||
"@types/node": "22.13.11",
|
"@types/node": "22.14.0",
|
||||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@types/ws": "8.18.0",
|
"@types/ws": "8.18.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.27.0",
|
"@typescript-eslint/eslint-plugin": "8.29.1",
|
||||||
"@typescript-eslint/parser": "8.27.0",
|
"@typescript-eslint/parser": "8.29.1",
|
||||||
"@vitest/coverage-v8": "3.0.9",
|
"@vitest/coverage-v8": "3.1.1",
|
||||||
"@vue/runtime-core": "3.5.13",
|
"@vue/runtime-core": "3.5.13",
|
||||||
"acorn": "8.14.1",
|
"acorn": "8.14.1",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
"start-server-and-test": "2.0.11",
|
"start-server-and-test": "2.0.11",
|
||||||
"vite-plugin-turbosnap": "1.0.3",
|
"vite-plugin-turbosnap": "1.0.3",
|
||||||
"vue-component-type-helpers": "2.2.8",
|
"vue-component-type-helpers": "2.2.8",
|
||||||
"vue-eslint-parser": "10.1.1",
|
"vue-eslint-parser": "10.1.3",
|
||||||
"vue-tsc": "2.2.8"
|
"vue-tsc": "2.2.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,7 @@ export const ROLE_POLICIES = [
|
||||||
'canUseTranslator',
|
'canUseTranslator',
|
||||||
'canHideAds',
|
'canHideAds',
|
||||||
'driveCapacityMb',
|
'driveCapacityMb',
|
||||||
|
'maxFileSizeMb',
|
||||||
'alwaysMarkNsfw',
|
'alwaysMarkNsfw',
|
||||||
'canUpdateBioMedia',
|
'canUpdateBioMedia',
|
||||||
'pinLimit',
|
'pinLimit',
|
||||||
|
|
|
@ -21,14 +21,14 @@
|
||||||
"lint": "pnpm typecheck && pnpm eslint"
|
"lint": "pnpm typecheck && pnpm eslint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "22.13.11",
|
"@types/node": "22.14.0",
|
||||||
"@typescript-eslint/eslint-plugin": "8.27.0",
|
"@typescript-eslint/eslint-plugin": "8.29.1",
|
||||||
"@typescript-eslint/parser": "8.27.0",
|
"@typescript-eslint/parser": "8.29.1",
|
||||||
"esbuild": "0.25.1",
|
"esbuild": "0.25.2",
|
||||||
"eslint-plugin-vue": "10.0.0",
|
"eslint-plugin-vue": "10.0.0",
|
||||||
"nodemon": "3.1.9",
|
"nodemon": "3.1.9",
|
||||||
"typescript": "5.8.2",
|
"typescript": "5.8.3",
|
||||||
"vue-eslint-parser": "10.1.1"
|
"vue-eslint-parser": "10.1.3"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"js-built"
|
"js-built"
|
||||||
|
|
|
@ -33,6 +33,8 @@
|
||||||
navFg: '@fg',
|
navFg: '@fg',
|
||||||
navActive: '@accent',
|
navActive: '@accent',
|
||||||
navIndicator: '@indicator',
|
navIndicator: '@indicator',
|
||||||
|
pageHeaderBg: '@bg',
|
||||||
|
pageHeaderFg: '@fg',
|
||||||
link: '#44a4c1',
|
link: '#44a4c1',
|
||||||
hashtag: '#ff9156',
|
hashtag: '#ff9156',
|
||||||
mention: '@accent',
|
mention: '@accent',
|
||||||
|
|
|
@ -33,6 +33,8 @@
|
||||||
navFg: '@fg',
|
navFg: '@fg',
|
||||||
navActive: '@accent',
|
navActive: '@accent',
|
||||||
navIndicator: '@indicator',
|
navIndicator: '@indicator',
|
||||||
|
pageHeaderBg: '@bg',
|
||||||
|
pageHeaderFg: '@fg',
|
||||||
link: '#44a4c1',
|
link: '#44a4c1',
|
||||||
hashtag: '#ff9156',
|
hashtag: '#ff9156',
|
||||||
mention: '@accent',
|
mention: '@accent',
|
||||||
|
|
|
@ -55,7 +55,7 @@ await fs.readFile(
|
||||||
'../../locales/ja-JP.yml',
|
'../../locales/ja-JP.yml',
|
||||||
'assets/**',
|
'assets/**',
|
||||||
'public/**',
|
'public/**',
|
||||||
'../../pnpm-lock.yaml',
|
'package.json',
|
||||||
]).length
|
]).length
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -43,6 +43,41 @@ export function channel(id = 'somechannelid', name = 'Some Channel', bannerUrl:
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function chatMessage(room = false, id = 'somechatmessageid', text = 'Hello!'): entities.ChatMessage {
|
||||||
|
const fromUser = userLite();
|
||||||
|
const toRoom = chatRoom();
|
||||||
|
const toUser = userLite('touserid');
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
createdAt: '2016-12-28T22:49:51.000Z',
|
||||||
|
fromUserId: fromUser.id,
|
||||||
|
fromUser,
|
||||||
|
text,
|
||||||
|
isRead: false,
|
||||||
|
reactions: [],
|
||||||
|
...room ? {
|
||||||
|
toRoomId: toRoom.id,
|
||||||
|
toRoom,
|
||||||
|
} : {
|
||||||
|
toUserId: toUser.id,
|
||||||
|
toUser,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function chatRoom(id = 'somechatroomid', name = 'Some Chat Room'): entities.ChatRoom {
|
||||||
|
const owner = userLite('someownerid');
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
createdAt: '2016-12-28T22:49:51.000Z',
|
||||||
|
ownerId: owner.id,
|
||||||
|
owner,
|
||||||
|
name,
|
||||||
|
description: 'A chat room for testing',
|
||||||
|
isMuted: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function clip(id = 'someclipid', name = 'Some Clip'): entities.Clip {
|
export function clip(id = 'someclipid', name = 'Some Clip'): entities.Clip {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
"@rollup/plugin-json": "6.1.0",
|
"@rollup/plugin-json": "6.1.0",
|
||||||
"@rollup/plugin-replace": "6.0.2",
|
"@rollup/plugin-replace": "6.0.2",
|
||||||
"@rollup/pluginutils": "5.1.4",
|
"@rollup/pluginutils": "5.1.4",
|
||||||
"@sentry/vue": "9.8.0",
|
"@sentry/vue": "9.12.0",
|
||||||
"@syuilo/aiscript": "0.19.0",
|
"@syuilo/aiscript": "0.19.0",
|
||||||
"@tabler/icons-webfont": "3.31.0",
|
"@tabler/icons-webfont": "3.31.0",
|
||||||
"@twemoji/parser": "15.1.1",
|
"@twemoji/parser": "15.1.1",
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15",
|
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15",
|
||||||
"analytics": "0.8.16",
|
"analytics": "0.8.16",
|
||||||
"astring": "1.9.0",
|
"astring": "1.9.0",
|
||||||
"broadcast-channel": "7.0.0",
|
"broadcast-channel": "7.1.0",
|
||||||
"buraha": "0.0.1",
|
"buraha": "0.0.1",
|
||||||
"canvas-confetti": "1.9.3",
|
"canvas-confetti": "1.9.3",
|
||||||
"chart.js": "4.4.8",
|
"chart.js": "4.4.8",
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
"chartjs-chart-matrix": "2.1.1",
|
"chartjs-chart-matrix": "2.1.1",
|
||||||
"chartjs-plugin-gradient": "0.6.1",
|
"chartjs-plugin-gradient": "0.6.1",
|
||||||
"chartjs-plugin-zoom": "2.2.0",
|
"chartjs-plugin-zoom": "2.2.0",
|
||||||
"chromatic": "11.27.0",
|
"chromatic": "11.28.0",
|
||||||
"compare-versions": "6.1.1",
|
"compare-versions": "6.1.1",
|
||||||
"cropperjs": "2.0.0",
|
"cropperjs": "2.0.0",
|
||||||
"date-fns": "4.1.0",
|
"date-fns": "4.1.0",
|
||||||
|
@ -60,65 +60,65 @@
|
||||||
"misskey-reversi": "workspace:*",
|
"misskey-reversi": "workspace:*",
|
||||||
"photoswipe": "5.4.4",
|
"photoswipe": "5.4.4",
|
||||||
"punycode.js": "2.3.1",
|
"punycode.js": "2.3.1",
|
||||||
"rollup": "4.36.0",
|
"rollup": "4.39.0",
|
||||||
"sanitize-html": "2.15.0",
|
"sanitize-html": "2.15.0",
|
||||||
"sass": "1.86.0",
|
"sass": "1.86.3",
|
||||||
"shiki": "3.2.1",
|
"shiki": "3.2.2",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
"three": "0.174.0",
|
"three": "0.175.0",
|
||||||
"throttle-debounce": "5.0.2",
|
"throttle-debounce": "5.0.2",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tsc-alias": "1.8.11",
|
"tsc-alias": "1.8.15",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typescript": "5.8.2",
|
"typescript": "5.8.3",
|
||||||
"uuid": "11.1.0",
|
"uuid": "11.1.0",
|
||||||
"v-code-diff": "1.13.1",
|
"v-code-diff": "1.13.1",
|
||||||
"vite": "6.2.4",
|
"vite": "6.3.1",
|
||||||
"vue": "3.5.13",
|
"vue": "3.5.13",
|
||||||
"vuedraggable": "next",
|
"vuedraggable": "next",
|
||||||
"wanakana": "5.3.1"
|
"wanakana": "5.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/summaly": "5.2.0",
|
"@misskey-dev/summaly": "5.2.0",
|
||||||
"@storybook/addon-actions": "8.6.7",
|
"@storybook/addon-actions": "8.6.12",
|
||||||
"@storybook/addon-essentials": "8.6.7",
|
"@storybook/addon-essentials": "8.6.12",
|
||||||
"@storybook/addon-interactions": "8.6.7",
|
"@storybook/addon-interactions": "8.6.12",
|
||||||
"@storybook/addon-links": "8.6.7",
|
"@storybook/addon-links": "8.6.12",
|
||||||
"@storybook/addon-mdx-gfm": "8.6.7",
|
"@storybook/addon-mdx-gfm": "8.6.12",
|
||||||
"@storybook/addon-storysource": "8.6.7",
|
"@storybook/addon-storysource": "8.6.12",
|
||||||
"@storybook/blocks": "8.6.7",
|
"@storybook/blocks": "8.6.12",
|
||||||
"@storybook/components": "8.6.7",
|
"@storybook/components": "8.6.12",
|
||||||
"@storybook/core-events": "8.6.7",
|
"@storybook/core-events": "8.6.12",
|
||||||
"@storybook/manager-api": "8.6.7",
|
"@storybook/manager-api": "8.6.12",
|
||||||
"@storybook/preview-api": "8.6.7",
|
"@storybook/preview-api": "8.6.12",
|
||||||
"@storybook/react": "8.6.7",
|
"@storybook/react": "8.6.12",
|
||||||
"@storybook/react-vite": "8.6.7",
|
"@storybook/react-vite": "8.6.12",
|
||||||
"@storybook/test": "8.6.7",
|
"@storybook/test": "8.6.12",
|
||||||
"@storybook/theming": "8.6.7",
|
"@storybook/theming": "8.6.12",
|
||||||
"@storybook/types": "8.6.7",
|
"@storybook/types": "8.6.12",
|
||||||
"@storybook/vue3": "8.6.7",
|
"@storybook/vue3": "8.6.12",
|
||||||
"@storybook/vue3-vite": "8.6.7",
|
"@storybook/vue3-vite": "8.6.12",
|
||||||
"@testing-library/vue": "8.1.0",
|
"@testing-library/vue": "8.1.0",
|
||||||
"@types/canvas-confetti": "1.9.0",
|
"@types/canvas-confetti": "1.9.0",
|
||||||
"@types/estree": "1.0.6",
|
"@types/estree": "1.0.7",
|
||||||
"@types/matter-js": "0.19.8",
|
"@types/matter-js": "0.19.8",
|
||||||
"@types/micromatch": "4.0.9",
|
"@types/micromatch": "4.0.9",
|
||||||
"@types/node": "22.13.11",
|
"@types/node": "22.14.0",
|
||||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||||
"@types/sanitize-html": "2.13.0",
|
"@types/sanitize-html": "2.15.0",
|
||||||
"@types/seedrandom": "3.0.8",
|
"@types/seedrandom": "3.0.8",
|
||||||
"@types/throttle-debounce": "5.0.2",
|
"@types/throttle-debounce": "5.0.2",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@types/ws": "8.18.0",
|
"@types/ws": "8.18.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.27.0",
|
"@typescript-eslint/eslint-plugin": "8.29.1",
|
||||||
"@typescript-eslint/parser": "8.27.0",
|
"@typescript-eslint/parser": "8.29.1",
|
||||||
"@vitest/coverage-v8": "3.0.9",
|
"@vitest/coverage-v8": "3.1.1",
|
||||||
"@vue/compiler-core": "3.5.13",
|
"@vue/compiler-core": "3.5.13",
|
||||||
"@vue/runtime-core": "3.5.13",
|
"@vue/runtime-core": "3.5.13",
|
||||||
"acorn": "8.14.1",
|
"acorn": "8.14.1",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "14.2.0",
|
"cypress": "14.3.0",
|
||||||
"eslint-plugin-import": "2.31.0",
|
"eslint-plugin-import": "2.31.0",
|
||||||
"eslint-plugin-vue": "10.0.0",
|
"eslint-plugin-vue": "10.0.0",
|
||||||
"fast-glob": "3.3.3",
|
"fast-glob": "3.3.3",
|
||||||
|
@ -130,18 +130,17 @@
|
||||||
"msw-storybook-addon": "2.0.4",
|
"msw-storybook-addon": "2.0.4",
|
||||||
"nodemon": "3.1.9",
|
"nodemon": "3.1.9",
|
||||||
"prettier": "3.5.3",
|
"prettier": "3.5.3",
|
||||||
"react": "19.0.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.0.0",
|
"react-dom": "19.1.0",
|
||||||
"seedrandom": "3.0.5",
|
"seedrandom": "3.0.5",
|
||||||
"start-server-and-test": "2.0.11",
|
"start-server-and-test": "2.0.11",
|
||||||
"storybook": "8.6.7",
|
"storybook": "8.6.12",
|
||||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||||
"vite-node": "3.0.9",
|
|
||||||
"vite-plugin-turbosnap": "1.0.3",
|
"vite-plugin-turbosnap": "1.0.3",
|
||||||
"vitest": "3.0.9",
|
"vitest": "3.1.1",
|
||||||
"vitest-fetch-mock": "0.4.5",
|
"vitest-fetch-mock": "0.4.5",
|
||||||
"vue-component-type-helpers": "2.2.8",
|
"vue-component-type-helpers": "2.2.8",
|
||||||
"vue-eslint-parser": "10.1.1",
|
"vue-eslint-parser": "10.1.3",
|
||||||
"vue-tsc": "2.2.8"
|
"vue-tsc": "2.2.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,14 +21,19 @@ type AccountWithToken = Misskey.entities.MeDetailed & { token: string };
|
||||||
|
|
||||||
export async function getAccounts(): Promise<{
|
export async function getAccounts(): Promise<{
|
||||||
host: string;
|
host: string;
|
||||||
user: Misskey.entities.User;
|
id: Misskey.entities.User['id'];
|
||||||
|
username: Misskey.entities.User['username'];
|
||||||
|
user?: Misskey.entities.User | null;
|
||||||
token: string | null;
|
token: string | null;
|
||||||
}[]> {
|
}[]> {
|
||||||
const tokens = store.s.accountTokens;
|
const tokens = store.s.accountTokens;
|
||||||
|
const accountInfos = store.s.accountInfos;
|
||||||
const accounts = prefer.s.accounts;
|
const accounts = prefer.s.accounts;
|
||||||
return accounts.map(([host, user]) => ({
|
return accounts.map(([host, user]) => ({
|
||||||
host,
|
host,
|
||||||
user,
|
id: user.id,
|
||||||
|
username: user.username,
|
||||||
|
user: accountInfos[host + '/' + user.id],
|
||||||
token: tokens[host + '/' + user.id] ?? null,
|
token: tokens[host + '/' + user.id] ?? null,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -36,7 +41,8 @@ export async function getAccounts(): Promise<{
|
||||||
async function addAccount(host: string, user: Misskey.entities.User, token: AccountWithToken['token']) {
|
async function addAccount(host: string, user: Misskey.entities.User, token: AccountWithToken['token']) {
|
||||||
if (!prefer.s.accounts.some(x => x[0] === host && x[1].id === user.id)) {
|
if (!prefer.s.accounts.some(x => x[0] === host && x[1].id === user.id)) {
|
||||||
store.set('accountTokens', { ...store.s.accountTokens, [host + '/' + user.id]: token });
|
store.set('accountTokens', { ...store.s.accountTokens, [host + '/' + user.id]: token });
|
||||||
prefer.commit('accounts', [...prefer.s.accounts, [host, user]]);
|
store.set('accountInfos', { ...store.s.accountInfos, [host + '/' + user.id]: user });
|
||||||
|
prefer.commit('accounts', [...prefer.s.accounts, [host, { id: user.id, username: user.username }]]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +50,10 @@ export async function removeAccount(host: string, id: AccountWithToken['id']) {
|
||||||
const tokens = JSON.parse(JSON.stringify(store.s.accountTokens));
|
const tokens = JSON.parse(JSON.stringify(store.s.accountTokens));
|
||||||
delete tokens[host + '/' + id];
|
delete tokens[host + '/' + id];
|
||||||
store.set('accountTokens', tokens);
|
store.set('accountTokens', tokens);
|
||||||
|
const accountInfos = JSON.parse(JSON.stringify(store.s.accountInfos));
|
||||||
|
delete accountInfos[host + '/' + id];
|
||||||
|
store.set('accountInfos', accountInfos);
|
||||||
|
|
||||||
prefer.commit('accounts', prefer.s.accounts.filter(x => x[0] !== host || x[1].id !== id));
|
prefer.commit('accounts', prefer.s.accounts.filter(x => x[0] !== host || x[1].id !== id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,14 +131,7 @@ export function updateCurrentAccount(accountData: Misskey.entities.MeDetailed) {
|
||||||
for (const [key, value] of Object.entries(accountData)) {
|
for (const [key, value] of Object.entries(accountData)) {
|
||||||
$i[key] = value;
|
$i[key] = value;
|
||||||
}
|
}
|
||||||
prefer.commit('accounts', prefer.s.accounts.map(([host, user]) => {
|
store.set('accountInfos', { ...store.s.accountInfos, [host + '/' + $i.id]: $i });
|
||||||
// TODO: $iのホストも比較したいけど通常null
|
|
||||||
if (user.id === $i.id) {
|
|
||||||
return [host, $i];
|
|
||||||
} else {
|
|
||||||
return [host, user];
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
$i.token = token;
|
$i.token = token;
|
||||||
miLocalStorage.setItem('account', JSON.stringify($i));
|
miLocalStorage.setItem('account', JSON.stringify($i));
|
||||||
}
|
}
|
||||||
|
@ -138,17 +141,9 @@ export function updateCurrentAccountPartial(accountData: Partial<Misskey.entitie
|
||||||
for (const [key, value] of Object.entries(accountData)) {
|
for (const [key, value] of Object.entries(accountData)) {
|
||||||
$i[key] = value;
|
$i[key] = value;
|
||||||
}
|
}
|
||||||
prefer.commit('accounts', prefer.s.accounts.map(([host, user]) => {
|
|
||||||
// TODO: $iのホストも比較したいけど通常null
|
store.set('accountInfos', { ...store.s.accountInfos, [host + '/' + $i.id]: $i });
|
||||||
if (user.id === $i.id) {
|
|
||||||
const newUser = JSON.parse(JSON.stringify($i));
|
|
||||||
for (const [key, value] of Object.entries(accountData)) {
|
|
||||||
newUser[key] = value;
|
|
||||||
}
|
|
||||||
return [host, newUser];
|
|
||||||
}
|
|
||||||
return [host, user];
|
|
||||||
}));
|
|
||||||
miLocalStorage.setItem('account', JSON.stringify($i));
|
miLocalStorage.setItem('account', JSON.stringify($i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,25 +218,42 @@ export async function openAccountMenu(opts: {
|
||||||
}, ev: MouseEvent) {
|
}, ev: MouseEvent) {
|
||||||
if (!$i) return;
|
if (!$i) return;
|
||||||
|
|
||||||
function createItem(host: string, account: Misskey.entities.User): MenuItem {
|
function createItem(host: string, id: Misskey.entities.User['id'], username: Misskey.entities.User['username'], account: Misskey.entities.User | null | undefined, token: string): MenuItem {
|
||||||
return {
|
if (account) {
|
||||||
type: 'user' as const,
|
return {
|
||||||
user: account,
|
type: 'user' as const,
|
||||||
active: opts.active != null ? opts.active === account.id : false,
|
user: account,
|
||||||
action: async () => {
|
active: opts.active != null ? opts.active === id : false,
|
||||||
if (opts.onChoose) {
|
action: async () => {
|
||||||
opts.onChoose(account);
|
if (opts.onChoose) {
|
||||||
} else {
|
opts.onChoose(account);
|
||||||
switchAccount(host, account.id);
|
} else {
|
||||||
}
|
switchAccount(host, id);
|
||||||
},
|
}
|
||||||
};
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
type: 'button' as const,
|
||||||
|
text: username,
|
||||||
|
active: opts.active != null ? opts.active === id : false,
|
||||||
|
action: async () => {
|
||||||
|
if (opts.onChoose) {
|
||||||
|
fetchAccount(token, id).then(account => {
|
||||||
|
opts.onChoose(account);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
switchAccount(host, id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuItems: MenuItem[] = [];
|
const menuItems: MenuItem[] = [];
|
||||||
|
|
||||||
// TODO: $iのホストも比較したいけど通常null
|
// TODO: $iのホストも比較したいけど通常null
|
||||||
const accountItems = (await getAccounts().then(accounts => accounts.filter(x => x.user.id !== $i.id))).map(a => createItem(a.host, a.user));
|
const accountItems = (await getAccounts().then(accounts => accounts.filter(x => x.id !== $i.id))).map(a => createItem(a.host, a.id, a.username, a.user, a.token));
|
||||||
|
|
||||||
if (opts.withExtraOperation) {
|
if (opts.withExtraOperation) {
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
|
@ -254,7 +266,7 @@ export async function openAccountMenu(opts: {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (opts.includeCurrentAccount) {
|
if (opts.includeCurrentAccount) {
|
||||||
menuItems.push(createItem(host, $i));
|
menuItems.push(createItem(host, $i.id, $i.username, $i, $i.token));
|
||||||
}
|
}
|
||||||
|
|
||||||
menuItems.push(...accountItems);
|
menuItems.push(...accountItems);
|
||||||
|
@ -290,7 +302,7 @@ export async function openAccountMenu(opts: {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (opts.includeCurrentAccount) {
|
if (opts.includeCurrentAccount) {
|
||||||
menuItems.push(createItem(host, $i));
|
menuItems.push(createItem(host, $i.id, $i.username, $i, $i.token));
|
||||||
}
|
}
|
||||||
|
|
||||||
menuItems.push(...accountItems);
|
menuItems.push(...accountItems);
|
||||||
|
|
|
@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
</I18n>
|
</I18n>
|
||||||
</template>
|
</template>
|
||||||
<MkSpacer :marginMin="20" :marginMax="28">
|
<div class="_spacer" style="--MI_SPACER-min: 20px; --MI_SPACER-max: 28px;">
|
||||||
<div class="_gaps_m" :class="$style.root">
|
<div class="_gaps_m" :class="$style.root">
|
||||||
<div class="">
|
<div class="">
|
||||||
<MkTextarea v-model="comment">
|
<MkTextarea v-model="comment">
|
||||||
|
@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkButton primary full :disabled="comment.length === 0" @click="send">{{ i18n.ts.send }}</MkButton>
|
<MkButton primary full :disabled="comment.length === 0" @click="send">{{ i18n.ts.send }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</div>
|
||||||
</MkWindow>
|
</MkWindow>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkSpacer :contentMax="700">
|
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
||||||
<div>
|
<div>
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<MkInput v-model="name">
|
<MkInput v-model="name">
|
||||||
|
@ -48,7 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
|
@ -157,7 +157,7 @@ async function init() {
|
||||||
|
|
||||||
const accounts = await getAccounts();
|
const accounts = await getAccounts();
|
||||||
|
|
||||||
const accountIdsToFetch = accounts.map(a => a.user.id).filter(id => !users.value.has(id));
|
const accountIdsToFetch = accounts.map(a => a.id).filter(id => !users.value.has(id));
|
||||||
|
|
||||||
if (accountIdsToFetch.length > 0) {
|
if (accountIdsToFetch.length > 0) {
|
||||||
const usersRes = await misskeyApi('users/show', {
|
const usersRes = await misskeyApi('users/show', {
|
||||||
|
@ -169,7 +169,7 @@ async function init() {
|
||||||
|
|
||||||
users.value.set(user.id, {
|
users.value.set(user.id, {
|
||||||
...user,
|
...user,
|
||||||
token: accounts.find(a => a.user.id === user.id)!.token,
|
token: accounts.find(a => a.id === user.id)!.token,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,12 +15,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</li>
|
</li>
|
||||||
<li tabindex="-1" :class="$style.item" @click="chooseUser()" @keydown="onKeydown">{{ i18n.ts.selectUser }}</li>
|
<li tabindex="-1" :class="$style.item" @click="chooseUser()" @keydown="onKeydown">{{ i18n.ts.selectUser }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
<ol v-else-if="hashtags.length > 0" ref="suggests" :class="$style.list">
|
<ol v-else-if="type === 'hashtag' && hashtags.length > 0" ref="suggests" :class="$style.list">
|
||||||
<li v-for="hashtag in hashtags" tabindex="-1" :class="$style.item" @click="complete(type, hashtag)" @keydown="onKeydown">
|
<li v-for="hashtag in hashtags" tabindex="-1" :class="$style.item" @click="complete(type, hashtag)" @keydown="onKeydown">
|
||||||
<span class="name">{{ hashtag }}</span>
|
<span class="name">{{ hashtag }}</span>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
<ol v-else-if="emojis.length > 0" ref="suggests" :class="$style.list">
|
<ol v-else-if="type === 'emoji' || type === 'emojiComplete' && emojis.length > 0" ref="suggests" :class="$style.list">
|
||||||
<li v-for="emoji in emojis" :key="emoji.emoji" :class="$style.item" tabindex="-1" @click="complete(type, emoji.emoji)" @keydown="onKeydown">
|
<li v-for="emoji in emojis" :key="emoji.emoji" :class="$style.item" tabindex="-1" @click="complete(type, emoji.emoji)" @keydown="onKeydown">
|
||||||
<MkCustomEmoji v-if="'isCustomEmoji' in emoji && emoji.isCustomEmoji" :name="emoji.emoji" :class="$style.emoji" :fallbackToImage="true"/>
|
<MkCustomEmoji v-if="'isCustomEmoji' in emoji && emoji.isCustomEmoji" :name="emoji.emoji" :class="$style.emoji" :fallbackToImage="true"/>
|
||||||
<MkEmoji v-else :emoji="emoji.emoji" :class="$style.emoji"/>
|
<MkEmoji v-else :emoji="emoji.emoji" :class="$style.emoji"/>
|
||||||
|
@ -30,12 +30,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<span v-if="emoji.aliasOf" :class="$style.emojiAlias">({{ emoji.aliasOf }})</span>
|
<span v-if="emoji.aliasOf" :class="$style.emojiAlias">({{ emoji.aliasOf }})</span>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
<ol v-else-if="mfmTags.length > 0" ref="suggests" :class="$style.list">
|
<ol v-else-if="type === 'mfmTag' && mfmTags.length > 0" ref="suggests" :class="$style.list">
|
||||||
<li v-for="tag in mfmTags" tabindex="-1" :class="$style.item" @click="complete(type, tag)" @keydown="onKeydown">
|
<li v-for="tag in mfmTags" tabindex="-1" :class="$style.item" @click="complete(type, tag)" @keydown="onKeydown">
|
||||||
<span>{{ tag }}</span>
|
<span>{{ tag }}</span>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
<ol v-else-if="mfmParams.length > 0" ref="suggests" :class="$style.list">
|
<ol v-else-if="type === 'mfmParam' && mfmParams.length > 0" ref="suggests" :class="$style.list">
|
||||||
<li v-for="param in mfmParams" tabindex="-1" :class="$style.item" @click="complete(type, q.params.toSpliced(-1, 1, param).join(','))" @keydown="onKeydown">
|
<li v-for="param in mfmParams" tabindex="-1" :class="$style.item" @click="complete(type, q.params.toSpliced(-1, 1, param).join(','))" @keydown="onKeydown">
|
||||||
<span>{{ param }}</span>
|
<span>{{ param }}</span>
|
||||||
</li>
|
</li>
|
||||||
|
@ -58,12 +58,44 @@ import { store } from '@/store.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { customEmojis } from '@/custom-emojis.js';
|
import { customEmojis } from '@/custom-emojis.js';
|
||||||
import { searchEmoji } from '@/utility/search-emoji.js';
|
import { searchEmoji, searchEmojiExact } from '@/utility/search-emoji.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
|
||||||
|
export type CompleteInfo = {
|
||||||
|
user: {
|
||||||
|
payload: any;
|
||||||
|
query: string | null;
|
||||||
|
},
|
||||||
|
hashtag: {
|
||||||
|
payload: string;
|
||||||
|
query: string;
|
||||||
|
},
|
||||||
|
// `:emo` -> `:emoji:` or some unicode emoji
|
||||||
|
emoji: {
|
||||||
|
payload: string;
|
||||||
|
query: string;
|
||||||
|
},
|
||||||
|
// like emoji but for `:emoji:` -> unicode emoji
|
||||||
|
emojiComplete: {
|
||||||
|
payload: string;
|
||||||
|
query: string;
|
||||||
|
},
|
||||||
|
mfmTag: {
|
||||||
|
payload: string;
|
||||||
|
query: string;
|
||||||
|
},
|
||||||
|
mfmParam: {
|
||||||
|
payload: string;
|
||||||
|
query: {
|
||||||
|
tag: string;
|
||||||
|
params: string[];
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const lib = emojilist.filter(x => x.category !== 'flags');
|
const lib = emojilist.filter(x => x.category !== 'flags');
|
||||||
|
|
||||||
const emojiDb = computed(() => {
|
const unicodeEmojiDB = computed(() => {
|
||||||
//#region Unicode Emoji
|
//#region Unicode Emoji
|
||||||
const char2path = prefer.r.emojiStyle.value === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath;
|
const char2path = prefer.r.emojiStyle.value === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath;
|
||||||
|
|
||||||
|
@ -87,6 +119,12 @@ const emojiDb = computed(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
unicodeEmojiDB.sort((a, b) => a.name.length - b.name.length);
|
unicodeEmojiDB.sort((a, b) => a.name.length - b.name.length);
|
||||||
|
|
||||||
|
return unicodeEmojiDB;
|
||||||
|
});
|
||||||
|
|
||||||
|
const emojiDb = computed(() => {
|
||||||
|
//#region Unicode Emoji
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region Custom Emoji
|
//#region Custom Emoji
|
||||||
|
@ -114,7 +152,7 @@ const emojiDb = computed(() => {
|
||||||
customEmojiDB.sort((a, b) => a.name.length - b.name.length);
|
customEmojiDB.sort((a, b) => a.name.length - b.name.length);
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
return markRaw([...customEmojiDB, ...unicodeEmojiDB]);
|
return markRaw([...customEmojiDB, ...unicodeEmojiDB.value]);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -123,18 +161,23 @@ export default {
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup generic="T extends keyof CompleteInfo">
|
||||||
const props = defineProps<{
|
type PropsType<T extends keyof CompleteInfo> = {
|
||||||
type: string;
|
type: T;
|
||||||
q: any;
|
q: CompleteInfo[T]['query'];
|
||||||
textarea: HTMLTextAreaElement;
|
// なぜかわからないけど HTMLTextAreaElement | HTMLInputElement だと addEventListener/removeEventListenerがエラー
|
||||||
|
textarea: (HTMLTextAreaElement | HTMLInputElement) & HTMLElement;
|
||||||
close: () => void;
|
close: () => void;
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
}>();
|
};
|
||||||
|
//const props = defineProps<PropsType<keyof CompleteInfo>>();
|
||||||
|
// ↑と同じだけど↓にしないとdiscriminated unionにならない。
|
||||||
|
// https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#discriminated-unions
|
||||||
|
const props = defineProps<PropsType<'user'> | PropsType<'hashtag'> | PropsType<'emoji'> | PropsType<'emojiComplete'> | PropsType<'mfmTag'> | PropsType<'mfmParam'>>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: 'done', value: { type: string; value: any }): void;
|
<T extends keyof CompleteInfo>(event: 'done', value: { type: T; value: CompleteInfo[T]['payload'] }): void;
|
||||||
(event: 'closed'): void;
|
(event: 'closed'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -151,10 +194,10 @@ const mfmParams = ref<string[]>([]);
|
||||||
const select = ref(-1);
|
const select = ref(-1);
|
||||||
const zIndex = os.claimZIndex('high');
|
const zIndex = os.claimZIndex('high');
|
||||||
|
|
||||||
function complete(type: string, value: any) {
|
function complete<T extends keyof CompleteInfo>(type: T, value: CompleteInfo[T]['payload']) {
|
||||||
emit('done', { type, value });
|
emit('done', { type, value });
|
||||||
emit('closed');
|
emit('closed');
|
||||||
if (type === 'emoji') {
|
if (type === 'emoji' || type === 'emojiComplete') {
|
||||||
let recents = store.s.recentlyUsedEmojis;
|
let recents = store.s.recentlyUsedEmojis;
|
||||||
recents = recents.filter((emoji: any) => emoji !== value);
|
recents = recents.filter((emoji: any) => emoji !== value);
|
||||||
recents.unshift(value);
|
recents.unshift(value);
|
||||||
|
@ -243,6 +286,8 @@ function exec() {
|
||||||
}
|
}
|
||||||
|
|
||||||
emojis.value = searchEmoji(props.q, emojiDb.value);
|
emojis.value = searchEmoji(props.q, emojiDb.value);
|
||||||
|
} else if (props.type === 'emojiComplete') {
|
||||||
|
emojis.value = searchEmojiExact(props.q, unicodeEmojiDB.value);
|
||||||
} else if (props.type === 'mfmTag') {
|
} else if (props.type === 'mfmTag') {
|
||||||
if (!props.q || props.q === '') {
|
if (!props.q || props.q === '') {
|
||||||
mfmTags.value = MFM_TAGS;
|
mfmTags.value = MFM_TAGS;
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { http, HttpResponse } from 'msw';
|
||||||
|
import { action } from '@storybook/addon-actions';
|
||||||
|
import { chatMessage } from '../../.storybook/fakes';
|
||||||
|
import MkChatHistories from './MkChatHistories.vue';
|
||||||
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
|
import type * as Misskey from 'misskey-js';
|
||||||
|
export const Default = {
|
||||||
|
render(args) {
|
||||||
|
return {
|
||||||
|
components: {
|
||||||
|
MkChatHistories,
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
args,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
props() {
|
||||||
|
return {
|
||||||
|
...this.args,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: '<MkChatHistories v-bind="props" />',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
msw: {
|
||||||
|
handlers: [
|
||||||
|
http.post('/api/chat/history', async ({ request }) => {
|
||||||
|
const body = await request.json() as Misskey.entities.ChatHistoryRequest;
|
||||||
|
action('POST /api/chat/history')(body);
|
||||||
|
return HttpResponse.json([chatMessage(body.room)]);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies StoryObj<typeof MkChatHistories>;
|
|
@ -0,0 +1,208 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="history.length > 0" class="_gaps_s">
|
||||||
|
<MkA
|
||||||
|
v-for="item in history"
|
||||||
|
:key="item.id"
|
||||||
|
:class="[$style.message, { [$style.isMe]: item.isMe, [$style.isRead]: item.message.isRead }]"
|
||||||
|
class="_panel"
|
||||||
|
:to="item.message.toRoomId ? `/chat/room/${item.message.toRoomId}` : `/chat/user/${item.other!.id}`"
|
||||||
|
>
|
||||||
|
<MkAvatar v-if="item.message.toRoomId" :class="$style.messageAvatar" :user="item.message.fromUser" indicator :preview="false"/>
|
||||||
|
<MkAvatar v-else-if="item.other" :class="$style.messageAvatar" :user="item.other" indicator :preview="false"/>
|
||||||
|
<div :class="$style.messageBody">
|
||||||
|
<header v-if="item.message.toRoom" :class="$style.messageHeader">
|
||||||
|
<span :class="$style.messageHeaderName"><i class="ti ti-users"></i> {{ item.message.toRoom.name }}</span>
|
||||||
|
<MkTime :time="item.message.createdAt" :class="$style.messageHeaderTime"/>
|
||||||
|
</header>
|
||||||
|
<header v-else :class="$style.messageHeader">
|
||||||
|
<MkUserName :class="$style.messageHeaderName" :user="item.other!"/>
|
||||||
|
<MkAcct :class="$style.messageHeaderUsername" :user="item.other!"/>
|
||||||
|
<MkTime :time="item.message.createdAt" :class="$style.messageHeaderTime"/>
|
||||||
|
</header>
|
||||||
|
<div :class="$style.messageBodyText"><span v-if="item.isMe" :class="$style.youSaid">{{ i18n.ts.you }}:</span>{{ item.message.text }}</div>
|
||||||
|
</div>
|
||||||
|
</MkA>
|
||||||
|
</div>
|
||||||
|
<div v-if="!initializing && history.length == 0" class="_fullinfo">
|
||||||
|
<div>{{ i18n.ts._chat.noHistory }}</div>
|
||||||
|
</div>
|
||||||
|
<MkLoading v-if="initializing"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onActivated, onDeactivated, onMounted, ref } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
import { useInterval } from '@@/js/use-interval.js';
|
||||||
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { ensureSignin } from '@/i.js';
|
||||||
|
|
||||||
|
const $i = ensureSignin();
|
||||||
|
|
||||||
|
const history = ref<{
|
||||||
|
id: string;
|
||||||
|
message: Misskey.entities.ChatMessage;
|
||||||
|
other: Misskey.entities.ChatMessage['fromUser'] | Misskey.entities.ChatMessage['toUser'] | null;
|
||||||
|
isMe: boolean;
|
||||||
|
}[]>([]);
|
||||||
|
|
||||||
|
const initializing = ref(true);
|
||||||
|
const fetching = ref(false);
|
||||||
|
|
||||||
|
async function fetchHistory() {
|
||||||
|
if (fetching.value) return;
|
||||||
|
|
||||||
|
fetching.value = true;
|
||||||
|
|
||||||
|
const [userMessages, roomMessages] = await Promise.all([
|
||||||
|
misskeyApi('chat/history', { room: false }),
|
||||||
|
misskeyApi('chat/history', { room: true }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
history.value = [...userMessages, ...roomMessages]
|
||||||
|
.toSorted((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
|
||||||
|
.map(m => ({
|
||||||
|
id: m.id,
|
||||||
|
message: m,
|
||||||
|
other: (!('room' in m) || m.room == null) ? (m.fromUserId === $i.id ? m.toUser : m.fromUser) : null,
|
||||||
|
isMe: m.fromUserId === $i.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
fetching.value = false;
|
||||||
|
initializing.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let isActivated = true;
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
isActivated = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
onDeactivated(() => {
|
||||||
|
isActivated = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
useInterval(() => {
|
||||||
|
// TODO: DOM的にバックグラウンドになっていないかどうかも考慮する
|
||||||
|
if (!window.document.hidden && isActivated) {
|
||||||
|
fetchHistory();
|
||||||
|
}
|
||||||
|
}, 1000 * 10, {
|
||||||
|
immediate: false,
|
||||||
|
afterMounted: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
fetchHistory();
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchHistory();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.message {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
padding: 16px 24px;
|
||||||
|
|
||||||
|
&.isRead,
|
||||||
|
&.isMe {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.isMe):not(.isRead) {
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 100%;
|
||||||
|
background-color: var(--MI_THEME-accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (max-width: 500px) {
|
||||||
|
.message {
|
||||||
|
font-size: 90%;
|
||||||
|
padding: 14px 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (max-width: 450px) {
|
||||||
|
.message {
|
||||||
|
font-size: 80%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.messageAvatar {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
margin: 0 16px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (max-width: 500px) {
|
||||||
|
.messageAvatar {
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (max-width: 450px) {
|
||||||
|
.messageAvatar {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.messageBody {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messageHeader {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messageHeaderName {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messageHeaderUsername {
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messageHeaderTime {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messageBodyText {
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.youSaid {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -12,8 +12,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #fallback>
|
<template #fallback>
|
||||||
<MkLoading/>
|
<MkLoading/>
|
||||||
</template>
|
</template>
|
||||||
<XCode v-if="show && lang" :code="code" :lang="lang"/>
|
<XCode v-if="show && lang" class="_selectable" :code="code" :lang="lang"/>
|
||||||
<pre v-else-if="show" :class="$style.codeBlockFallbackRoot"><code :class="$style.codeBlockFallbackCode">{{ code }}</code></pre>
|
<pre v-else-if="show" class="_selectable" :class="$style.codeBlockFallbackRoot"><code :class="$style.codeBlockFallbackCode">{{ code }}</code></pre>
|
||||||
<button v-else :class="$style.codePlaceholderRoot" @click="show = true">
|
<button v-else :class="$style.codePlaceholderRoot" @click="show = true">
|
||||||
<div :class="$style.codePlaceholderContainer">
|
<div :class="$style.codePlaceholderContainer">
|
||||||
<div><i class="ti ti-code"></i> {{ i18n.ts.code }}</div>
|
<div><i class="ti ti-code"></i> {{ i18n.ts.code }}</div>
|
||||||
|
@ -70,11 +70,9 @@ function copy() {
|
||||||
.codeBlockFallbackRoot {
|
.codeBlockFallbackRoot {
|
||||||
display: block;
|
display: block;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
background: var(--MI_THEME-bg);
|
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
margin: .5em 0;
|
margin: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border-radius: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.codeBlockFallbackCode {
|
.codeBlockFallbackCode {
|
||||||
|
|
|
@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkModalWindow ref="dialogEl" @close="cancel()" @closed="emit('closed')">
|
<MkModalWindow ref="dialogEl" @close="cancel()" @closed="emit('closed')">
|
||||||
<template #header>:{{ emoji.name }}:</template>
|
<template #header>:{{ emoji.name }}:</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<MkSpacer>
|
<div class="_spacer">
|
||||||
<div style="display: flex; flex-direction: column; gap: 1em;">
|
<div style="display: flex; flex-direction: column; gap: 1em;">
|
||||||
<div :class="$style.emojiImgWrapper">
|
<div :class="$style.emojiImgWrapper">
|
||||||
<MkCustomEmoji :name="emoji.name" :normal="true" :useOriginalSize="true" style="height: 100%;"></MkCustomEmoji>
|
<MkCustomEmoji :name="emoji.name" :normal="true" :useOriginalSize="true" style="height: 100%;"></MkCustomEmoji>
|
||||||
|
@ -50,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</MkModalWindow>
|
</MkModalWindow>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import MkDisableSection from './MkDisableSection.vue';
|
||||||
|
void MkDisableSection;
|
|
@ -626,13 +626,13 @@ function getMenu() {
|
||||||
text: i18n.ts.upload + ' (' + i18n.ts.compress + ')',
|
text: i18n.ts.upload + ' (' + i18n.ts.compress + ')',
|
||||||
icon: 'ti ti-upload',
|
icon: 'ti ti-upload',
|
||||||
action: () => {
|
action: () => {
|
||||||
chooseFileFromPc(true, { keepOriginal: false });
|
chooseFileFromPc(true, { uploadFolder: folder.value?.id, keepOriginal: false });
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
text: i18n.ts.upload,
|
text: i18n.ts.upload,
|
||||||
icon: 'ti ti-upload',
|
icon: 'ti ti-upload',
|
||||||
action: () => {
|
action: () => {
|
||||||
chooseFileFromPc(true, { keepOriginal: true });
|
chooseFileFromPc(true, { uploadFolder: folder.value?.id, keepOriginal: true });
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
text: i18n.ts.fromUrl,
|
text: i18n.ts.fromUrl,
|
||||||
|
|
|
@ -15,12 +15,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
@closed="emit('closed')"
|
@closed="emit('closed')"
|
||||||
>
|
>
|
||||||
<template #header>{{ i18n.ts.describeFile }}</template>
|
<template #header>{{ i18n.ts.describeFile }}</template>
|
||||||
<MkSpacer :marginMin="20" :marginMax="28">
|
<div class="_spacer" style="--MI_SPACER-min: 20px; --MI_SPACER-max: 28px;">
|
||||||
<MkDriveFileThumbnail :file="file" fit="contain" style="height: 100px; margin-bottom: 16px;"/>
|
<MkDriveFileThumbnail :file="file" fit="contain" style="height: 100px; margin-bottom: 16px;"/>
|
||||||
<MkTextarea v-model="caption" autofocus :placeholder="i18n.ts.inputNewDescription">
|
<MkTextarea v-model="caption" autofocus :placeholder="i18n.ts.inputNewDescription">
|
||||||
<template #label>{{ i18n.ts.caption }}</template>
|
<template #label>{{ i18n.ts.caption }}</template>
|
||||||
</MkTextarea>
|
</MkTextarea>
|
||||||
</MkSpacer>
|
</div>
|
||||||
</MkModalWindow>
|
</MkModalWindow>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -38,15 +38,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
>
|
>
|
||||||
<KeepAlive>
|
<KeepAlive>
|
||||||
<div v-show="opened">
|
<div v-show="opened">
|
||||||
<MkSpacer v-if="withSpacer" :marginMin="spacerMin" :marginMax="spacerMax">
|
<MkStickyContainer>
|
||||||
<slot></slot>
|
<template #header>
|
||||||
</MkSpacer>
|
<div v-if="$slots.header" :class="$style.inBodyHeader">
|
||||||
<div v-else>
|
<slot name="header"></slot>
|
||||||
<slot></slot>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
<div v-if="$slots.footer" :class="$style.footer">
|
|
||||||
<slot name="footer"></slot>
|
<div v-if="withSpacer" class="_spacer" :style="{ '--MI_SPACER-min': props.spacerMin + 'px', '--MI_SPACER-max': props.spacerMax + 'px' }">
|
||||||
</div>
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div v-if="$slots.footer" :class="$style.inBodyFooter">
|
||||||
|
<slot name="footer"></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</MkStickyContainer>
|
||||||
</div>
|
</div>
|
||||||
</KeepAlive>
|
</KeepAlive>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
@ -230,14 +241,21 @@ onMounted(() => {
|
||||||
|
|
||||||
&.bgSame {
|
&.bgSame {
|
||||||
background: var(--MI_THEME-bg);
|
background: var(--MI_THEME-bg);
|
||||||
|
|
||||||
|
.inBodyHeader {
|
||||||
|
background: color(from var(--MI_THEME-bg) srgb r g b / 0.75);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.inBodyHeader {
|
||||||
position: sticky !important;
|
background: color(from var(--MI_THEME-panel) srgb r g b / 0.75);
|
||||||
z-index: 1;
|
-webkit-backdrop-filter: var(--MI-blur, blur(15px));
|
||||||
bottom: var(--MI-stickyBottom, 0px);
|
backdrop-filter: var(--MI-blur, blur(15px));
|
||||||
left: 0;
|
border-bottom: solid 0.5px var(--MI_THEME-divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inBodyFooter {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
background: color(from var(--MI_THEME-bg) srgb r g b / 0.5);
|
background: color(from var(--MI_THEME-bg) srgb r g b / 0.5);
|
||||||
-webkit-backdrop-filter: var(--MI-blur, blur(15px));
|
-webkit-backdrop-filter: var(--MI-blur, blur(15px));
|
||||||
|
|
|
@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
>
|
>
|
||||||
<template #header>{{ i18n.ts.forgotPassword }}</template>
|
<template #header>{{ i18n.ts.forgotPassword }}</template>
|
||||||
|
|
||||||
<MkSpacer :marginMin="20" :marginMax="28">
|
<div class="_spacer" style="--MI_SPACER-min: 20px; --MI_SPACER-max: 28px;">
|
||||||
<form v-if="instance.enableEmail" @submit.prevent="onSubmit">
|
<form v-if="instance.enableEmail" @submit.prevent="onSubmit">
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autofocus required>
|
<MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autofocus required>
|
||||||
|
@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div v-else>
|
<div v-else>
|
||||||
{{ i18n.ts._forgotPassword.contactAdmin }}
|
{{ i18n.ts._forgotPassword.contactAdmin }}
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</div>
|
||||||
</MkModalWindow>
|
</MkModalWindow>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<MkSpacer :marginMin="20" :marginMax="32">
|
<div class="_spacer" style="--MI_SPACER-min: 20px; --MI_SPACER-max: 32px;">
|
||||||
<div v-if="Object.keys(form).filter(item => !form[item].hidden).length > 0" class="_gaps_m">
|
<div v-if="Object.keys(form).filter(item => !form[item].hidden).length > 0" class="_gaps_m">
|
||||||
<template v-for="(v, k) in Object.fromEntries(Object.entries(form))">
|
<template v-for="(v, k) in Object.fromEntries(Object.entries(form))">
|
||||||
<template v-if="typeof v.hidden == 'function' ? v.hidden(values) : v.hidden"></template>
|
<template v-if="typeof v.hidden == 'function' ? v.hidden(values) : v.hidden"></template>
|
||||||
|
@ -66,7 +66,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" draggable="false"/>
|
||||||
<div>{{ i18n.ts.nothing }}</div>
|
<div>{{ i18n.ts.nothing }}</div>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</div>
|
||||||
</MkModalWindow>
|
</MkModalWindow>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -31,9 +31,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template v-for="item in (items2 ?? [])">
|
<template v-for="item in (items2 ?? [])">
|
||||||
<div v-if="item.type === 'divider'" role="separator" tabindex="-1" :class="$style.divider"></div>
|
<div v-if="item.type === 'divider'" role="separator" tabindex="-1" :class="$style.divider"></div>
|
||||||
|
|
||||||
<span v-else-if="item.type === 'label'" role="menuitem" tabindex="-1" :class="[$style.label, $style.item]">
|
<div v-else-if="item.type === 'label'" role="menuitem" tabindex="-1" :class="[$style.label]">
|
||||||
<span style="opacity: 0.7;">{{ item.text }}</span>
|
<span>{{ item.text }}</span>
|
||||||
</span>
|
</div>
|
||||||
|
|
||||||
<span v-else-if="item.type === 'pending'" role="menuitem" tabindex="0" :class="[$style.pending, $style.item]">
|
<span v-else-if="item.type === 'pending'" role="menuitem" tabindex="0" :class="[$style.pending, $style.item]">
|
||||||
<span><MkEllipsis/></span>
|
<span><MkEllipsis/></span>
|
||||||
|
@ -619,12 +619,6 @@ onBeforeUnmount(() => {
|
||||||
--menuActiveBg: var(--MI_THEME-accentedBg);
|
--menuActiveBg: var(--MI_THEME-accentedBg);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.label {
|
|
||||||
pointer-events: none;
|
|
||||||
font-size: 0.7em;
|
|
||||||
padding-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.pending {
|
&.pending {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
@ -694,6 +688,19 @@ onBeforeUnmount(() => {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
position: relative;
|
||||||
|
padding: 6px 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 0.7em;
|
||||||
|
text-align: left;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
opacity: 0.7;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.divider {
|
.divider {
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
border-top: solid 0.5px var(--MI_THEME-divider);
|
border-top: solid 0.5px var(--MI_THEME-divider);
|
||||||
|
|
|
@ -101,7 +101,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
|
<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
|
||||||
</div>
|
</div>
|
||||||
<MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" :note="appearNote" :maxNumber="16" @mockUpdateMyReaction="emitUpdReaction">
|
<MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" style="margin-top: 6px;" :note="appearNote" :maxNumber="16" @mockUpdateMyReaction="emitUpdReaction">
|
||||||
<template #more>
|
<template #more>
|
||||||
<MkA :to="`/notes/${appearNote.id}/reactions`" :class="[$style.reactionOmitted]">{{ i18n.ts.more }}</MkA>
|
<MkA :to="`/notes/${appearNote.id}/reactions`" :class="[$style.reactionOmitted]">{{ i18n.ts.more }}</MkA>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -124,7 +124,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkTime :time="appearNote.createdAt" mode="detail" colored/>
|
<MkTime :time="appearNote.createdAt" mode="detail" colored/>
|
||||||
</MkA>
|
</MkA>
|
||||||
</div>
|
</div>
|
||||||
<MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" ref="reactionsViewer" :note="appearNote"/>
|
<MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" ref="reactionsViewer" style="margin-top: 6px;" :note="appearNote"/>
|
||||||
<button class="_button" :class="$style.noteFooterButton" @click="reply()">
|
<button class="_button" :class="$style.noteFooterButton" @click="reply()">
|
||||||
<i class="ti ti-arrow-back-up"></i>
|
<i class="ti ti-arrow-back-up"></i>
|
||||||
<p v-if="appearNote.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.repliesCount) }}</p>
|
<p v-if="appearNote.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.repliesCount) }}</p>
|
||||||
|
|
|
@ -14,7 +14,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template #default="{ items: notes }">
|
<template #default="{ items: notes }">
|
||||||
<component
|
<component
|
||||||
:is="prefer.s.animation ? TransitionGroup : 'div'" :class="[$style.root, { [$style.noGap]: noGap, '_gaps': !noGap }]"
|
:is="prefer.s.animation ? TransitionGroup : 'div'"
|
||||||
|
:class="[$style.root, { [$style.noGap]: noGap, '_gaps': !noGap, [$style.reverse]: pagination.reversed }]"
|
||||||
:enterActiveClass="$style.transition_x_enterActive"
|
:enterActiveClass="$style.transition_x_enterActive"
|
||||||
:leaveActiveClass="$style.transition_x_leaveActive"
|
:leaveActiveClass="$style.transition_x_leaveActive"
|
||||||
:enterFromClass="$style.transition_x_enterFrom"
|
:enterFromClass="$style.transition_x_enterFrom"
|
||||||
|
@ -23,13 +24,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
tag="div"
|
tag="div"
|
||||||
>
|
>
|
||||||
<template v-for="(note, i) in notes" :key="note.id">
|
<template v-for="(note, i) in notes" :key="note.id">
|
||||||
<div v-if="note._shouldInsertAd_" :class="[$style.noteWithAd, { '_gaps': !noGap }]">
|
<div v-if="note._shouldInsertAd_" :class="[$style.noteWithAd, { '_gaps': !noGap }]" :data-scroll-anchor="note.id">
|
||||||
<MkNote :class="$style.note" :note="note" :withHardMute="true"/>
|
<MkNote :class="$style.note" :note="note" :withHardMute="true"/>
|
||||||
<div :class="$style.ad">
|
<div :class="$style.ad">
|
||||||
<MkAd :preferForms="['horizontal', 'horizontal-big']"/>
|
<MkAd :preferForms="['horizontal', 'horizontal-big']"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MkNote v-else :class="$style.note" :note="note" :withHardMute="true"/>
|
<MkNote v-else :class="$style.note" :note="note" :withHardMute="true" :data-scroll-anchor="note.id"/>
|
||||||
</template>
|
</template>
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
@ -73,6 +74,11 @@ defineExpose({
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reverse {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
container-type: inline-size;
|
container-type: inline-size;
|
||||||
|
|
||||||
|
|
|
@ -296,6 +296,7 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
|
||||||
right: -2px;
|
right: -2px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
background: var(--MI_THEME-panel);
|
background: var(--MI_THEME-panel);
|
||||||
|
@ -310,73 +311,61 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
.t_follow, .t_followRequestAccepted, .t_receiveFollowRequest {
|
.t_follow, .t_followRequestAccepted, .t_receiveFollowRequest {
|
||||||
padding: 3px;
|
|
||||||
background: var(--eventFollow);
|
background: var(--eventFollow);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.t_renote {
|
.t_renote {
|
||||||
padding: 3px;
|
|
||||||
background: var(--eventRenote);
|
background: var(--eventRenote);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.t_quote {
|
.t_quote {
|
||||||
padding: 3px;
|
|
||||||
background: var(--eventRenote);
|
background: var(--eventRenote);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.t_reply {
|
.t_reply {
|
||||||
padding: 3px;
|
|
||||||
background: var(--eventReply);
|
background: var(--eventReply);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.t_mention {
|
.t_mention {
|
||||||
padding: 3px;
|
|
||||||
background: var(--eventOther);
|
background: var(--eventOther);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.t_pollEnded {
|
.t_pollEnded {
|
||||||
padding: 3px;
|
|
||||||
background: var(--eventOther);
|
background: var(--eventOther);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.t_achievementEarned {
|
.t_achievementEarned {
|
||||||
padding: 3px;
|
|
||||||
background: var(--eventAchievement);
|
background: var(--eventAchievement);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.t_exportCompleted {
|
.t_exportCompleted {
|
||||||
padding: 3px;
|
|
||||||
background: var(--eventOther);
|
background: var(--eventOther);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.t_roleAssigned {
|
.t_roleAssigned {
|
||||||
padding: 3px;
|
|
||||||
background: var(--eventOther);
|
background: var(--eventOther);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.t_login {
|
.t_login {
|
||||||
padding: 3px;
|
|
||||||
background: var(--eventLogin);
|
background: var(--eventLogin);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.t_createToken {
|
.t_createToken {
|
||||||
padding: 3px;
|
|
||||||
background: var(--eventOther);
|
background: var(--eventOther);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.t_chatRoomInvitationReceived {
|
.t_chatRoomInvitationReceived {
|
||||||
padding: 3px;
|
|
||||||
background: var(--eventOther);
|
background: var(--eventOther);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
>
|
>
|
||||||
<template #header>{{ i18n.ts.notificationSetting }}</template>
|
<template #header>{{ i18n.ts.notificationSetting }}</template>
|
||||||
|
|
||||||
<MkSpacer :marginMin="20" :marginMax="28">
|
<div class="_spacer" style="--MI_SPACER-min: 20px; --MI_SPACER-max: 28px;">
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<MkInfo>{{ i18n.ts.notificationSettingDesc }}</MkInfo>
|
<MkInfo>{{ i18n.ts.notificationSettingDesc }}</MkInfo>
|
||||||
<div class="_buttons">
|
<div class="_buttons">
|
||||||
|
@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<MkSwitch v-for="ntype in notificationTypes" :key="ntype" v-model="typesMap[ntype].value">{{ i18n.ts._notification._types[ntype] }}</MkSwitch>
|
<MkSwitch v-for="ntype in notificationTypes" :key="ntype" v-model="typesMap[ntype].value">{{ i18n.ts._notification._types[ntype] }}</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</div>
|
||||||
</MkModalWindow>
|
</MkModalWindow>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
tag="div"
|
tag="div"
|
||||||
>
|
>
|
||||||
<template v-for="(notification, i) in notifications" :key="notification.id">
|
<template v-for="(notification, i) in notifications" :key="notification.id">
|
||||||
<MkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :class="$style.item" :note="notification.note" :withHardMute="true"/>
|
<MkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :class="$style.item" :note="notification.note" :withHardMute="true" :data-scroll-anchor="notification.id"/>
|
||||||
<XNotification v-else :class="$style.item" :notification="notification" :withTime="true" :full="true"/>
|
<XNotification v-else :class="$style.item" :notification="notification" :withTime="true" :full="true" :data-scroll-anchor="notification.id"/>
|
||||||
</template>
|
</template>
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div :class="$style.root">
|
<div :class="$style.root" class="_forceShrinkSpacer">
|
||||||
<StackingRouterView v-if="prefer.s['experimental.stackingRouterView']" :key="reloadCount" :router="windowRouter"/>
|
<StackingRouterView v-if="prefer.s['experimental.stackingRouterView']" :key="reloadCount" :router="windowRouter"/>
|
||||||
<RouterView v-else :key="reloadCount" :router="windowRouter"/>
|
<RouterView v-else :key="reloadCount" :router="windowRouter"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -125,7 +125,6 @@ provideMetadataReceiver((metadataGetter) => {
|
||||||
provideReactiveMetadata(pageMetadata);
|
provideReactiveMetadata(pageMetadata);
|
||||||
provide('shouldOmitHeaderTitle', true);
|
provide('shouldOmitHeaderTitle', true);
|
||||||
provide('shouldHeaderThin', true);
|
provide('shouldHeaderThin', true);
|
||||||
provide(DI.forceSpacerMin, true);
|
|
||||||
|
|
||||||
const contextmenu = computed(() => ([{
|
const contextmenu = computed(() => ([{
|
||||||
icon: 'ti ti-player-eject',
|
icon: 'ti ti-player-eject',
|
||||||
|
|
|
@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
>
|
>
|
||||||
<template #header>{{ i18n.ts.authentication }}</template>
|
<template #header>{{ i18n.ts.authentication }}</template>
|
||||||
|
|
||||||
<MkSpacer :marginMin="20" :marginMax="28">
|
<div class="_spacer" style="--MI_SPACER-min: 20px; --MI_SPACER-max: 28px;">
|
||||||
<div style="padding: 0 0 16px 0; text-align: center;">
|
<div style="padding: 0 0 16px 0; text-align: center;">
|
||||||
<img src="/client-assets/locked_with_key_3d.png" alt="🔐" style="display: block; margin: 0 auto; width: 48px;">
|
<img src="/client-assets/locked_with_key_3d.png" alt="🔐" style="display: block; margin: 0 auto; width: 48px;">
|
||||||
<div style="margin-top: 16px;">{{ i18n.ts.authenticationRequiredToContinue }}</div>
|
<div style="margin-top: 16px;">{{ i18n.ts.authenticationRequiredToContinue }}</div>
|
||||||
|
@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkButton :disabled="(password ?? '') == '' || ($i.twoFactorEnabled && (token ?? '') == '')" type="submit" primary rounded style="margin: 0 auto;"><i class="ti ti-lock-open"></i> {{ i18n.ts.continue }}</MkButton>
|
<MkButton :disabled="(password ?? '') == '' || ($i.twoFactorEnabled && (token ?? '') == '')" type="submit" primary rounded style="margin: 0 auto;"><i class="ti ti-lock-open"></i> {{ i18n.ts.continue }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</MkSpacer>
|
</div>
|
||||||
</MkModalWindow>
|
</MkModalWindow>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -879,7 +879,7 @@ async function post(ev?: MouseEvent) {
|
||||||
|
|
||||||
if (postAccount.value) {
|
if (postAccount.value) {
|
||||||
const storedAccounts = await getAccounts();
|
const storedAccounts = await getAccounts();
|
||||||
token = storedAccounts.find(x => x.user.id === postAccount.value?.id)?.token;
|
token = storedAccounts.find(x => x.id === postAccount.value?.id)?.token;
|
||||||
}
|
}
|
||||||
|
|
||||||
posting.value = true;
|
posting.value = true;
|
||||||
|
|
|
@ -182,7 +182,6 @@ if (!mock) {
|
||||||
.root {
|
.root {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
height: 42px;
|
height: 42px;
|
||||||
margin: 2px;
|
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
|
|
@ -106,7 +106,7 @@ watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumbe
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 4px -2px 0 -2px;
|
gap: 4px;
|
||||||
|
|
||||||
&:empty {
|
&:empty {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #header>:{{ name }}:</template>
|
<template #header>:{{ name }}:</template>
|
||||||
|
|
||||||
<div style="display: flex; flex-direction: column; min-height: 100%;">
|
<div style="display: flex; flex-direction: column; min-height: 100%;">
|
||||||
<MkSpacer :marginMin="20" :marginMax="28" style="flex-grow: 1;">
|
<div class="_spacer" style="--MI_SPACER-min: 20px; --MI_SPACER-max: 28px; flex-grow: 1;">
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<div v-if="imgUrl != null" :class="$style.imgs">
|
<div v-if="imgUrl != null" :class="$style.imgs">
|
||||||
<div style="background: #000;" :class="$style.imgContainer">
|
<div style="background: #000;" :class="$style.imgContainer">
|
||||||
|
@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #value>{{ license }}</template>
|
<template #value>{{ license }}</template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</div>
|
||||||
<div :class="$style.footer">
|
<div :class="$style.footer">
|
||||||
<MkButton primary rounded style="margin: 0 auto;" @click="done">
|
<MkButton primary rounded style="margin: 0 auto;" @click="done">
|
||||||
<i class="ti ti-plus"></i> {{ i18n.ts.import }}
|
<i class="ti ti-plus"></i> {{ i18n.ts.import }}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue