Compare commits
	
		
			20 Commits
		
	
	
		
			2024.9.0-a
			...
			2024.10.0-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					e97b7fe2a1 | ||
| 
						 | 
					87617dca39 | ||
| 
						 | 
					9dc058189e | ||
| 
						 | 
					83db116c46 | ||
| 
						 | 
					d3e2b59f53 | ||
| 
						 | 
					1074d625ed | ||
| 
						 | 
					6dde457452 | ||
| 
						 | 
					a25d83f249 | ||
| 
						 | 
					6fd4de246c | ||
| 
						 | 
					e9519b02fb | ||
| 
						 | 
					b6578861ac | ||
| 
						 | 
					ca8cc015b0 | ||
| 
						 | 
					4f34a4e4d8 | ||
| 
						 | 
					781e64aa7f | ||
| 
						 | 
					d6e1f022d4 | ||
| 
						 | 
					2ea49703f6 | ||
| 
						 | 
					1184436461 | ||
| 
						 | 
					0871156780 | ||
| 
						 | 
					088707c114 | ||
| 
						 | 
					15f2e1425c | 
@@ -21,6 +21,7 @@ jobs:
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        with:
 | 
			
		||||
          submodules: true
 | 
			
		||||
          persist-credentials: false
 | 
			
		||||
          ref: refs/pull/${{ github.event.pull_request.number }}/merge
 | 
			
		||||
 | 
			
		||||
      - name: setup pnpm
 | 
			
		||||
@@ -57,7 +58,7 @@ jobs:
 | 
			
		||||
          name: generated-misskey-js
 | 
			
		||||
          path: packages/misskey-js/generator/built/autogen
 | 
			
		||||
 | 
			
		||||
  # pull_request_target safety: permissions: read-all, and there are no secrets used in this job
 | 
			
		||||
  # pull_request_target safety: permissions: read-all, and no user codes are executed
 | 
			
		||||
  get-actual-misskey-js:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    permissions:
 | 
			
		||||
@@ -68,6 +69,7 @@ jobs:
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        with:
 | 
			
		||||
          submodules: true
 | 
			
		||||
          persist-credentials: false
 | 
			
		||||
          ref: refs/pull/${{ github.event.pull_request.number }}/merge
 | 
			
		||||
 | 
			
		||||
      - name: Upload From Merged
 | 
			
		||||
@@ -131,3 +133,7 @@ jobs:
 | 
			
		||||
          mode: delete
 | 
			
		||||
          message: "Thank you!"
 | 
			
		||||
          create_if_not_exists: false
 | 
			
		||||
 | 
			
		||||
      - name: Make failure if changes are detected
 | 
			
		||||
        if: steps.check-changes.outputs.changes == 'true'
 | 
			
		||||
        run: exit 1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,3 +1,15 @@
 | 
			
		||||
## 2024.10.0
 | 
			
		||||
 | 
			
		||||
### General
 | 
			
		||||
- Enhance: セキュリティ向上のため、サインイン時もCAPTCHAを求めるようになりました
 | 
			
		||||
 | 
			
		||||
### Client
 | 
			
		||||
- Enhance: フォロワーへのメッセージ欄のデザイン改良
 | 
			
		||||
 | 
			
		||||
### Server
 | 
			
		||||
- Enhance: セキュリティ向上のため、ログイン時にメール通知を行うように
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 2024.9.0
 | 
			
		||||
 | 
			
		||||
### General
 | 
			
		||||
@@ -22,6 +34,7 @@
 | 
			
		||||
- Enhance: Play編集画面の項目の並びを少しリデザイン
 | 
			
		||||
- Enhance: 各種メニューをドロワー表示するかどうか設定可能に
 | 
			
		||||
- Enhance: AiScriptのMk:C:containerのオプションに`borderStyle`と`borderRadius`を追加
 | 
			
		||||
- Enhance: CWでも絵文字をクリックしてメニューを表示できるように
 | 
			
		||||
- Fix: サーバーメトリクスが2つ以上あるとリロード直後の表示がおかしくなる問題を修正
 | 
			
		||||
- Fix: コントロールパネル内のAp requests内のチャートの表示がおかしかった問題を修正
 | 
			
		||||
- Fix: 月の違う同じ日はセパレータが表示されないのを修正
 | 
			
		||||
 
 | 
			
		||||
@@ -451,7 +451,6 @@ or: "অথবা"
 | 
			
		||||
language: "ভাষা"
 | 
			
		||||
uiLanguage: "UI এর ভাষা"
 | 
			
		||||
aboutX: "{x} সম্পর্কে"
 | 
			
		||||
disableDrawer: "ড্রয়ার মেনু প্রদর্শন করবেন না"
 | 
			
		||||
noHistory: "কোনো ইতিহাস নেই"
 | 
			
		||||
signinHistory: "প্রবেশ করার ইতিহাস"
 | 
			
		||||
doing: "প্রক্রিয়া করছে..."
 | 
			
		||||
 
 | 
			
		||||
@@ -509,7 +509,6 @@ uiLanguage: "Idioma de l'interfície"
 | 
			
		||||
aboutX: "Respecte a {x}"
 | 
			
		||||
emojiStyle: "Estil d'emoji"
 | 
			
		||||
native: "Nadiu"
 | 
			
		||||
disableDrawer: "No mostrar els menús en calaixos"
 | 
			
		||||
showNoteActionsOnlyHover: "Només mostra accions de la nota en passar amb el cursor"
 | 
			
		||||
showReactionsCount: "Mostra el nombre de reaccions a les publicacions"
 | 
			
		||||
noHistory: "No hi ha un registre previ"
 | 
			
		||||
 
 | 
			
		||||
@@ -471,7 +471,6 @@ uiLanguage: "Jazyk uživatelského rozhraní"
 | 
			
		||||
aboutX: "O {x}"
 | 
			
		||||
emojiStyle: "Styl emoji"
 | 
			
		||||
native: "Výchozí"
 | 
			
		||||
disableDrawer: "Nepoužívat šuplíkové menu"
 | 
			
		||||
showNoteActionsOnlyHover: "Zobrazit akce poznámky jenom při naběhnutí myši"
 | 
			
		||||
noHistory: "Žádná historie"
 | 
			
		||||
signinHistory: "Historie přihlášení"
 | 
			
		||||
 
 | 
			
		||||
@@ -491,7 +491,6 @@ uiLanguage: "Sprache der Benutzeroberfläche"
 | 
			
		||||
aboutX: "Über {x}"
 | 
			
		||||
emojiStyle: "Emoji-Stil"
 | 
			
		||||
native: "Nativ"
 | 
			
		||||
disableDrawer: "Keine ausfahrbaren Menüs verwenden"
 | 
			
		||||
showNoteActionsOnlyHover: "Notizmenü nur bei Mouseover anzeigen"
 | 
			
		||||
noHistory: "Kein Verlauf gefunden"
 | 
			
		||||
signinHistory: "Anmeldungsverlauf"
 | 
			
		||||
 
 | 
			
		||||
@@ -509,7 +509,6 @@ uiLanguage: "User interface language"
 | 
			
		||||
aboutX: "About {x}"
 | 
			
		||||
emojiStyle: "Emoji style"
 | 
			
		||||
native: "Native"
 | 
			
		||||
disableDrawer: "Don't use drawer-style menus"
 | 
			
		||||
showNoteActionsOnlyHover: "Only show note actions on hover"
 | 
			
		||||
showReactionsCount: "See the number of reactions in notes"
 | 
			
		||||
noHistory: "No history available"
 | 
			
		||||
 
 | 
			
		||||
@@ -502,7 +502,6 @@ uiLanguage: "Idioma de visualización de la interfaz"
 | 
			
		||||
aboutX: "Acerca de {x}"
 | 
			
		||||
emojiStyle: "Estilo de emoji"
 | 
			
		||||
native: "Nativo"
 | 
			
		||||
disableDrawer: "No mostrar los menús en cajones"
 | 
			
		||||
showNoteActionsOnlyHover: "Mostrar acciones de la nota sólo al pasar el cursor"
 | 
			
		||||
showReactionsCount: "Mostrar el número de reacciones en las notas"
 | 
			
		||||
noHistory: "No hay datos en el historial"
 | 
			
		||||
 
 | 
			
		||||
@@ -493,7 +493,6 @@ uiLanguage: "Langue d’affichage de l’interface"
 | 
			
		||||
aboutX: "À propos de {x}"
 | 
			
		||||
emojiStyle: "Style des émojis"
 | 
			
		||||
native: "Natif"
 | 
			
		||||
disableDrawer: "Les menus ne s'affichent pas dans le tiroir"
 | 
			
		||||
showNoteActionsOnlyHover: "Afficher les actions de note uniquement au survol"
 | 
			
		||||
showReactionsCount: "Afficher le nombre de réactions des notes"
 | 
			
		||||
noHistory: "Pas d'historique"
 | 
			
		||||
 
 | 
			
		||||
@@ -504,7 +504,6 @@ uiLanguage: "Bahasa antarmuka pengguna"
 | 
			
		||||
aboutX: "Tentang {x}"
 | 
			
		||||
emojiStyle: "Gaya emoji"
 | 
			
		||||
native: "Native"
 | 
			
		||||
disableDrawer: "Jangan gunakan menu bergaya laci"
 | 
			
		||||
showNoteActionsOnlyHover: "Hanya tampilkan aksi catatan saat ditunjuk"
 | 
			
		||||
showReactionsCount: "Lihat jumlah reaksi dalam catatan"
 | 
			
		||||
noHistory: "Tidak ada riwayat"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								locales/index.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								locales/index.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -5148,6 +5148,10 @@ export interface Locale extends ILocale {
 | 
			
		||||
     * パスキーの検証に成功しましたが、パスワードレスログインが無効になっています。
 | 
			
		||||
     */
 | 
			
		||||
    "passkeyVerificationSucceededButPasswordlessLoginDisabled": string;
 | 
			
		||||
    /**
 | 
			
		||||
     * フォロワーへのメッセージ
 | 
			
		||||
     */
 | 
			
		||||
    "messageToFollower": string;
 | 
			
		||||
    "_delivery": {
 | 
			
		||||
        /**
 | 
			
		||||
         * 配信状態
 | 
			
		||||
@@ -9281,6 +9285,10 @@ export interface Locale extends ILocale {
 | 
			
		||||
         * {x}のエクスポートが完了しました
 | 
			
		||||
         */
 | 
			
		||||
        "exportOfXCompleted": ParameterizedString<"x">;
 | 
			
		||||
        /**
 | 
			
		||||
         * ログインがありました
 | 
			
		||||
         */
 | 
			
		||||
        "login": string;
 | 
			
		||||
        "_types": {
 | 
			
		||||
            /**
 | 
			
		||||
             * すべて
 | 
			
		||||
@@ -9338,6 +9346,10 @@ export interface Locale extends ILocale {
 | 
			
		||||
             * エクスポートが完了した
 | 
			
		||||
             */
 | 
			
		||||
            "exportCompleted": string;
 | 
			
		||||
            /**
 | 
			
		||||
             * ログイン
 | 
			
		||||
             */
 | 
			
		||||
            "login": string;
 | 
			
		||||
            /**
 | 
			
		||||
             * 通知のテスト
 | 
			
		||||
             */
 | 
			
		||||
 
 | 
			
		||||
@@ -334,6 +334,7 @@ renameFolder: "Rinomina cartella"
 | 
			
		||||
deleteFolder: "Elimina cartella"
 | 
			
		||||
folder: "Cartella"
 | 
			
		||||
addFile: "Allega"
 | 
			
		||||
showFile: "Visualizza file"
 | 
			
		||||
emptyDrive: "Il Drive è vuoto"
 | 
			
		||||
emptyFolder: "La cartella è vuota"
 | 
			
		||||
unableToDelete: "Eliminazione impossibile"
 | 
			
		||||
@@ -509,7 +510,10 @@ uiLanguage: "Lingua di visualizzazione dell'interfaccia"
 | 
			
		||||
aboutX: "Informazioni su {x}"
 | 
			
		||||
emojiStyle: "Stile emoji"
 | 
			
		||||
native: "Nativo"
 | 
			
		||||
disableDrawer: "Non mostrare il menù sul drawer"
 | 
			
		||||
menuStyle: "Stile menu"
 | 
			
		||||
style: "Stile"
 | 
			
		||||
drawer: "Drawer"
 | 
			
		||||
popup: "Popup"
 | 
			
		||||
showNoteActionsOnlyHover: "Mostra le azioni delle Note solo al passaggio del mouse"
 | 
			
		||||
showReactionsCount: "Visualizza il numero di reazioni su una nota"
 | 
			
		||||
noHistory: "Nessuna cronologia"
 | 
			
		||||
@@ -1270,6 +1274,13 @@ genEmbedCode: "Ottieni il codice di incorporamento"
 | 
			
		||||
noteOfThisUser: "Elenco di Note di questo profilo"
 | 
			
		||||
clipNoteLimitExceeded: "Non è possibile aggiungere ulteriori Note a questa Clip."
 | 
			
		||||
performance: "Prestazioni"
 | 
			
		||||
modified: "Modificato"
 | 
			
		||||
discard: "Scarta"
 | 
			
		||||
thereAreNChanges: "Ci sono {n} cambiamenti"
 | 
			
		||||
signinWithPasskey: "Accedi con passkey"
 | 
			
		||||
unknownWebAuthnKey: "Questa è una passkey sconosciuta."
 | 
			
		||||
passkeyVerificationFailed: "La verifica della passkey non è riuscita."
 | 
			
		||||
passkeyVerificationSucceededButPasswordlessLoginDisabled: "La verifica della passkey è riuscita, ma l'accesso senza password è disabilitato."
 | 
			
		||||
_delivery:
 | 
			
		||||
  status: "Stato della consegna"
 | 
			
		||||
  stop: "Sospensione"
 | 
			
		||||
@@ -2375,6 +2386,7 @@ _notification:
 | 
			
		||||
  renotedBySomeUsers: "{n} Rinota"
 | 
			
		||||
  followedBySomeUsers: "{n} follower"
 | 
			
		||||
  flushNotification: "Azzera le notifiche"
 | 
			
		||||
  exportOfXCompleted: "Abbiamo completato l'esportazione di {x}"
 | 
			
		||||
  _types:
 | 
			
		||||
    all: "Tutto"
 | 
			
		||||
    note: "Nuove Note"
 | 
			
		||||
@@ -2389,6 +2401,8 @@ _notification:
 | 
			
		||||
    followRequestAccepted: "Richiesta di follow accettata"
 | 
			
		||||
    roleAssigned: "Ruolo concesso"
 | 
			
		||||
    achievementEarned: "Risultato raggiunto"
 | 
			
		||||
    exportCompleted: "Esportazione completata"
 | 
			
		||||
    test: "Prova la notifica"
 | 
			
		||||
    app: "Notifiche da applicazioni"
 | 
			
		||||
  _actions:
 | 
			
		||||
    followBack: "Segui"
 | 
			
		||||
 
 | 
			
		||||
@@ -1283,6 +1283,7 @@ signinWithPasskey: "パスキーでログイン"
 | 
			
		||||
unknownWebAuthnKey: "登録されていないパスキーです。"
 | 
			
		||||
passkeyVerificationFailed: "パスキーの検証に失敗しました。"
 | 
			
		||||
passkeyVerificationSucceededButPasswordlessLoginDisabled: "パスキーの検証に成功しましたが、パスワードレスログインが無効になっています。"
 | 
			
		||||
messageToFollower: "フォロワーへのメッセージ"
 | 
			
		||||
 | 
			
		||||
_delivery:
 | 
			
		||||
  status: "配信状態"
 | 
			
		||||
@@ -2450,6 +2451,7 @@ _notification:
 | 
			
		||||
  followedBySomeUsers: "{n}人にフォローされました"
 | 
			
		||||
  flushNotification: "通知の履歴をリセットする"
 | 
			
		||||
  exportOfXCompleted: "{x}のエクスポートが完了しました"
 | 
			
		||||
  login: "ログインがありました"
 | 
			
		||||
 | 
			
		||||
  _types:
 | 
			
		||||
    all: "すべて"
 | 
			
		||||
@@ -2466,6 +2468,7 @@ _notification:
 | 
			
		||||
    roleAssigned: "ロールが付与された"
 | 
			
		||||
    achievementEarned: "実績の獲得"
 | 
			
		||||
    exportCompleted: "エクスポートが完了した"
 | 
			
		||||
    login: "ログイン"
 | 
			
		||||
    test: "通知のテスト"
 | 
			
		||||
    app: "連携アプリからの通知"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -509,7 +509,6 @@ uiLanguage: "UIの表示言語"
 | 
			
		||||
aboutX: "{x}について"
 | 
			
		||||
emojiStyle: "絵文字のスタイル"
 | 
			
		||||
native: "ネイティブ"
 | 
			
		||||
disableDrawer: "メニューをドロワーで表示せえへん"
 | 
			
		||||
showNoteActionsOnlyHover: "ノートの操作部をホバー時のみ表示するで"
 | 
			
		||||
showReactionsCount: "ノートのリアクション数を表示する"
 | 
			
		||||
noHistory: "履歴はないわ。"
 | 
			
		||||
 
 | 
			
		||||
@@ -476,7 +476,6 @@ uiLanguage: "UI 표시 언어"
 | 
			
		||||
aboutX: "{x}에 대해서"
 | 
			
		||||
emojiStyle: "이모지 모양"
 | 
			
		||||
native: "기본"
 | 
			
		||||
disableDrawer: "드로어 메뉴 쓰지 않기"
 | 
			
		||||
showNoteActionsOnlyHover: "마우스 올맀을 때만 노트 액션 버턴 보이기"
 | 
			
		||||
noHistory: "기록이 없십니다"
 | 
			
		||||
signinHistory: "로그인 기록"
 | 
			
		||||
 
 | 
			
		||||
@@ -236,6 +236,8 @@ silencedInstances: "사일런스한 서버"
 | 
			
		||||
silencedInstancesDescription: "사일런스하려는 서버의 호스트명을 한 줄에 하나씩 입력합니다. 사일런스된 서버에 소속된 유저는 모두 '사일런스'된 상태로 취급되며, 이 서버로부터의 팔로우가 프로필 설정과 무관하게 승인제로 변경되고, 팔로워가 아닌 로컬 유저에게는 멘션할 수 없게 됩니다. 정지된 서버에는 적용되지 않습니다."
 | 
			
		||||
mediaSilencedInstances: "미디어를 사일런스한 서버"
 | 
			
		||||
mediaSilencedInstancesDescription: "미디어를 사일런스 하려는 서버의 호스트를 한 줄에 하나씩 입력합니다. 미디어가 사일런스된 서버의 유저가 업로드한 파일은 모두 민감한 미디어로 처리되며, 커스텀 이모지를 사용할 수 없게 됩니다. 또한, 차단한 인스턴스에는 적용되지 않습니다."
 | 
			
		||||
federationAllowedHosts: "연합을 허가하는 서버"
 | 
			
		||||
federationAllowedHostsDescription: "연합을 허가하는 서버의 호스트를 엔터로 구분해서 설정합니다."
 | 
			
		||||
muteAndBlock: "뮤트 및 차단"
 | 
			
		||||
mutedUsers: "뮤트한 유저"
 | 
			
		||||
blockedUsers: "차단한 유저"
 | 
			
		||||
@@ -334,6 +336,7 @@ renameFolder: "폴더 이름 바꾸기"
 | 
			
		||||
deleteFolder: "폴더 삭제"
 | 
			
		||||
folder: "폴더"
 | 
			
		||||
addFile: "파일 추가"
 | 
			
		||||
showFile: "파일 표시하기"
 | 
			
		||||
emptyDrive: "드라이브가 비어 있습니다"
 | 
			
		||||
emptyFolder: "폴더가 비어 있습니다"
 | 
			
		||||
unableToDelete: "삭제할 수 없습니다"
 | 
			
		||||
@@ -509,7 +512,10 @@ uiLanguage: "UI 표시 언어"
 | 
			
		||||
aboutX: "{x}에 대하여"
 | 
			
		||||
emojiStyle: "이모지 스타일"
 | 
			
		||||
native: "기본"
 | 
			
		||||
disableDrawer: "드로어 메뉴를 사용하지 않기"
 | 
			
		||||
menuStyle: "메뉴 스타일"
 | 
			
		||||
style: "스타일"
 | 
			
		||||
drawer: "서랍"
 | 
			
		||||
popup: "팝업"
 | 
			
		||||
showNoteActionsOnlyHover: "마우스가 올라간 때에만 노트 동작 버튼을 표시하기"
 | 
			
		||||
showReactionsCount: "노트의 반응 수를 표시하기"
 | 
			
		||||
noHistory: "기록이 없습니다"
 | 
			
		||||
@@ -1273,6 +1279,10 @@ performance: "퍼포먼스"
 | 
			
		||||
modified: "변경 있음"
 | 
			
		||||
discard: "파기"
 | 
			
		||||
thereAreNChanges: "{n}건 변경이 있습니다."
 | 
			
		||||
signinWithPasskey: "패스키로 로그인"
 | 
			
		||||
unknownWebAuthnKey: "등록되지 않은 패스키입니다."
 | 
			
		||||
passkeyVerificationFailed: "패스키 검증을 실패했습니다."
 | 
			
		||||
passkeyVerificationSucceededButPasswordlessLoginDisabled: "패스키를 검증했으나, 비밀번호 없이 로그인하기가 꺼져 있습니다."
 | 
			
		||||
_delivery:
 | 
			
		||||
  status: "전송 상태"
 | 
			
		||||
  stop: "정지됨"
 | 
			
		||||
@@ -2240,6 +2250,9 @@ _profile:
 | 
			
		||||
  changeBanner: "배너 이미지 변경"
 | 
			
		||||
  verifiedLinkDescription: "내용에 자신의 프로필로 향하는 링크가 포함된 페이지의 URL을 삽입하면 소유자 인증 마크가 표시됩니다."
 | 
			
		||||
  avatarDecorationMax: "최대 {max}개까지 장식을 할 수 있습니다."
 | 
			
		||||
  followedMessage: "팔로우 받았을 때 메시지"
 | 
			
		||||
  followedMessageDescription: "팔로우 받았을 때 상대방에게 보여줄 단문 메시지를 설정할 수 있습니다."
 | 
			
		||||
  followedMessageDescriptionForLockedAccount: "팔로우를 승인제로 한 경우, 팔로우 요청을 수락했을 때 보여줍니다."
 | 
			
		||||
_exportOrImport:
 | 
			
		||||
  allNotes: "모든 노트"
 | 
			
		||||
  favoritedNotes: "즐겨찾기한 노트"
 | 
			
		||||
@@ -2378,6 +2391,7 @@ _notification:
 | 
			
		||||
  renotedBySomeUsers: "{n}명이 리노트했습니다"
 | 
			
		||||
  followedBySomeUsers: "{n}명에게 팔로우됨"
 | 
			
		||||
  flushNotification: "알림 이력을 초기화"
 | 
			
		||||
  exportOfXCompleted: "{x} 추출에 성공했습니다."
 | 
			
		||||
  _types:
 | 
			
		||||
    all: "전부"
 | 
			
		||||
    note: "사용자의 새 글"
 | 
			
		||||
@@ -2392,6 +2406,8 @@ _notification:
 | 
			
		||||
    followRequestAccepted: "팔로우 요청이 승인되었을 때"
 | 
			
		||||
    roleAssigned: "역할이 부여 됨"
 | 
			
		||||
    achievementEarned: "도전 과제 획득"
 | 
			
		||||
    exportCompleted: "추출을 성공함"
 | 
			
		||||
    test: "알림 테스트"
 | 
			
		||||
    app: "연동된 앱을 통한 알림"
 | 
			
		||||
  _actions:
 | 
			
		||||
    followBack: "팔로우"
 | 
			
		||||
 
 | 
			
		||||
@@ -492,7 +492,6 @@ uiLanguage: "Język wyświetlania UI"
 | 
			
		||||
aboutX: "O {x}"
 | 
			
		||||
emojiStyle: "Styl emoji"
 | 
			
		||||
native: "Natywny"
 | 
			
		||||
disableDrawer: "Nie używaj menu w stylu szuflady"
 | 
			
		||||
showNoteActionsOnlyHover: "Pokazuj akcje notatek tylko po najechaniu myszką"
 | 
			
		||||
showReactionsCount: "Wyświetl liczbę reakcji na notatkę"
 | 
			
		||||
noHistory: "Brak historii"
 | 
			
		||||
 
 | 
			
		||||
@@ -509,7 +509,6 @@ uiLanguage: "Idioma de exibição da interface "
 | 
			
		||||
aboutX: "Sobre {x}"
 | 
			
		||||
emojiStyle: "Estilo de emojis"
 | 
			
		||||
native: "Nativo"
 | 
			
		||||
disableDrawer: "Não mostrar o menu em formato de gaveta"
 | 
			
		||||
showNoteActionsOnlyHover: "Exibir as ações da nota somente ao passar o cursor sobre ela"
 | 
			
		||||
showReactionsCount: "Ver o número de reações nas notas"
 | 
			
		||||
noHistory: "Ainda não há histórico"
 | 
			
		||||
 
 | 
			
		||||
@@ -453,7 +453,6 @@ or: "Sau"
 | 
			
		||||
language: "Limbă"
 | 
			
		||||
uiLanguage: "Limba interfeței"
 | 
			
		||||
aboutX: "Despre {x}"
 | 
			
		||||
disableDrawer: "Nu folosi meniuri în stil sertar"
 | 
			
		||||
noHistory: "Nu există istoric"
 | 
			
		||||
signinHistory: "Istoric autentificări"
 | 
			
		||||
doing: "Se procesează..."
 | 
			
		||||
 
 | 
			
		||||
@@ -503,7 +503,6 @@ uiLanguage: "Язык интерфейса"
 | 
			
		||||
aboutX: "Описание {x}"
 | 
			
		||||
emojiStyle: "Стиль эмодзи"
 | 
			
		||||
native: "Системные"
 | 
			
		||||
disableDrawer: "Не использовать выдвижные меню"
 | 
			
		||||
showNoteActionsOnlyHover: "Показывать кнопки у заметок только при наведении"
 | 
			
		||||
showReactionsCount: "Видеть количество реакций на заметках"
 | 
			
		||||
noHistory: "История пока пуста"
 | 
			
		||||
 
 | 
			
		||||
@@ -454,7 +454,6 @@ uiLanguage: "Jazyk používateľského prostredia"
 | 
			
		||||
aboutX: "O {x}"
 | 
			
		||||
emojiStyle: "Štýl emoji"
 | 
			
		||||
native: "Natívne"
 | 
			
		||||
disableDrawer: "Nepoužívať šuflíkové menu"
 | 
			
		||||
showNoteActionsOnlyHover: "Ovládacie prvky poznámky sa zobrazujú len po nabehnutí myši"
 | 
			
		||||
noHistory: "Žiadna história"
 | 
			
		||||
signinHistory: "História prihlásení"
 | 
			
		||||
 
 | 
			
		||||
@@ -509,7 +509,6 @@ uiLanguage: "ภาษาอินเทอร์เฟซผู้ใช้ง
 | 
			
		||||
aboutX: "เกี่ยวกับ {x}"
 | 
			
		||||
emojiStyle: "สไตล์ของเอโมจิ"
 | 
			
		||||
native: "ภาษาแม่"
 | 
			
		||||
disableDrawer: "ไม่แสดงเมนูในรูปแบบลิ้นชัก"
 | 
			
		||||
showNoteActionsOnlyHover: "แสดงการดำเนินการโน้ตเมื่อโฮเวอร์(วางเมาส์เหนือ)เท่านั้น"
 | 
			
		||||
showReactionsCount: "แสดงจำนวนรีแอกชั่นในโน้ต"
 | 
			
		||||
noHistory: "ไม่มีประวัติ"
 | 
			
		||||
 
 | 
			
		||||
@@ -452,7 +452,6 @@ language: "Мова"
 | 
			
		||||
uiLanguage: "Мова інтерфейсу"
 | 
			
		||||
aboutX: "Про {x}"
 | 
			
		||||
native: "місцевий"
 | 
			
		||||
disableDrawer: "Не використовувати висувні меню"
 | 
			
		||||
noHistory: "Історія порожня"
 | 
			
		||||
signinHistory: "Історія входів"
 | 
			
		||||
enableAdvancedMfm: "Увімкнути розширений MFM"
 | 
			
		||||
 
 | 
			
		||||
@@ -471,7 +471,6 @@ uiLanguage: "Interfeys tili"
 | 
			
		||||
aboutX: "{x} haqida"
 | 
			
		||||
emojiStyle: "Emoji ko'rinishi"
 | 
			
		||||
native: "Mahalliy"
 | 
			
		||||
disableDrawer: "Slayd menyusidan foydalanmang"
 | 
			
		||||
showNoteActionsOnlyHover: "Eslatma amallarini faqat sichqonchani olib borganda ko‘rsatish"
 | 
			
		||||
noHistory: "Tarix yo'q"
 | 
			
		||||
signinHistory: "kirish tarixi"
 | 
			
		||||
 
 | 
			
		||||
@@ -486,7 +486,6 @@ uiLanguage: "Ngôn ngữ giao diện"
 | 
			
		||||
aboutX: "Giới thiệu {x}"
 | 
			
		||||
emojiStyle: "Kiểu cách Emoji"
 | 
			
		||||
native: "Bản xứ"
 | 
			
		||||
disableDrawer: "Không dùng menu thanh bên"
 | 
			
		||||
showNoteActionsOnlyHover: "Chỉ hiển thị các hành động ghi chú khi di chuột"
 | 
			
		||||
noHistory: "Không có dữ liệu"
 | 
			
		||||
signinHistory: "Lịch sử đăng nhập"
 | 
			
		||||
 
 | 
			
		||||
@@ -334,6 +334,7 @@ renameFolder: "重命名文件夹"
 | 
			
		||||
deleteFolder: "删除文件夹"
 | 
			
		||||
folder: "文件夹"
 | 
			
		||||
addFile: "添加文件"
 | 
			
		||||
showFile: "显示文件"
 | 
			
		||||
emptyDrive: "网盘中无文件"
 | 
			
		||||
emptyFolder: "此文件夹中无文件"
 | 
			
		||||
unableToDelete: "无法删除"
 | 
			
		||||
@@ -509,7 +510,9 @@ uiLanguage: "显示语言"
 | 
			
		||||
aboutX: "关于 {x}"
 | 
			
		||||
emojiStyle: "表情符号的样式"
 | 
			
		||||
native: "原生"
 | 
			
		||||
disableDrawer: "不显示抽屉菜单"
 | 
			
		||||
menuStyle: "菜单样式"
 | 
			
		||||
style: "样式"
 | 
			
		||||
popup: "弹窗"
 | 
			
		||||
showNoteActionsOnlyHover: "仅在悬停时显示帖子操作"
 | 
			
		||||
showReactionsCount: "显示帖子的回应数"
 | 
			
		||||
noHistory: "没有历史记录"
 | 
			
		||||
@@ -1270,6 +1273,10 @@ genEmbedCode: "生成嵌入代码"
 | 
			
		||||
noteOfThisUser: "此用户的帖子"
 | 
			
		||||
clipNoteLimitExceeded: "无法再往此便签内添加更多帖子"
 | 
			
		||||
performance: "性能"
 | 
			
		||||
signinWithPasskey: "使用通行密钥登录"
 | 
			
		||||
unknownWebAuthnKey: "此通行密钥未注册。"
 | 
			
		||||
passkeyVerificationFailed: "验证通行密钥失败。"
 | 
			
		||||
passkeyVerificationSucceededButPasswordlessLoginDisabled: "通行密钥验证成功,但账户未开启无密码登录。"
 | 
			
		||||
_delivery:
 | 
			
		||||
  status: "投递状态"
 | 
			
		||||
  stop: "停止投递"
 | 
			
		||||
@@ -2375,6 +2382,7 @@ _notification:
 | 
			
		||||
  renotedBySomeUsers: "{n} 人转发了"
 | 
			
		||||
  followedBySomeUsers: "被 {n} 人关注"
 | 
			
		||||
  flushNotification: "重置通知历史"
 | 
			
		||||
  exportOfXCompleted: "已完成 {x} 个导出"
 | 
			
		||||
  _types:
 | 
			
		||||
    all: "全部"
 | 
			
		||||
    note: "用户的新帖子"
 | 
			
		||||
@@ -2389,6 +2397,8 @@ _notification:
 | 
			
		||||
    followRequestAccepted: "关注请求已通过"
 | 
			
		||||
    roleAssigned: "授予的角色"
 | 
			
		||||
    achievementEarned: "取得的成就"
 | 
			
		||||
    exportCompleted: "已完成导出"
 | 
			
		||||
    test: "测试通知"
 | 
			
		||||
    app: "关联应用的通知"
 | 
			
		||||
  _actions:
 | 
			
		||||
    followBack: "回关"
 | 
			
		||||
 
 | 
			
		||||
@@ -236,6 +236,8 @@ silencedInstances: "被禁言的伺服器"
 | 
			
		||||
silencedInstancesDescription: "設定要禁言的伺服器主機名稱,以換行分隔。隸屬於禁言伺服器的所有帳戶都將被視為「禁言帳戶」,只能發出「追隨請求」,而且無法提及未追隨的本地帳戶。這不會影響已封鎖的實例。"
 | 
			
		||||
mediaSilencedInstances: "媒體被禁言的伺服器"
 | 
			
		||||
mediaSilencedInstancesDescription: "設定您想要對媒體設定禁言的伺服器,以換行符號區隔。來自被媒體禁言的伺服器所屬帳戶的所有檔案都會被視為敏感檔案,且自訂表情符號不能使用。被封鎖的伺服器不受影響。"
 | 
			
		||||
federationAllowedHosts: "允許聯邦通訊的伺服器"
 | 
			
		||||
federationAllowedHostsDescription: "設定允許聯邦通訊的伺服器主機,以換行符號分隔。"
 | 
			
		||||
muteAndBlock: "靜音和封鎖"
 | 
			
		||||
mutedUsers: "被靜音的使用者"
 | 
			
		||||
blockedUsers: "被封鎖的使用者"
 | 
			
		||||
@@ -334,6 +336,7 @@ renameFolder: "重新命名資料夾"
 | 
			
		||||
deleteFolder: "刪除資料夾"
 | 
			
		||||
folder: "資料夾"
 | 
			
		||||
addFile: "加入附件"
 | 
			
		||||
showFile: "瀏覽文件"
 | 
			
		||||
emptyDrive: "雲端硬碟為空"
 | 
			
		||||
emptyFolder: "資料夾為空"
 | 
			
		||||
unableToDelete: "無法刪除"
 | 
			
		||||
@@ -509,7 +512,10 @@ uiLanguage: "介面語言"
 | 
			
		||||
aboutX: "關於{x}"
 | 
			
		||||
emojiStyle: "表情符號的風格"
 | 
			
		||||
native: "原生"
 | 
			
		||||
disableDrawer: "不顯示下拉式選單"
 | 
			
		||||
menuStyle: "選單風格"
 | 
			
		||||
style: "風格"
 | 
			
		||||
drawer: "側邊欄"
 | 
			
		||||
popup: "彈出式視窗"
 | 
			
		||||
showNoteActionsOnlyHover: "僅在游標停留時顯示貼文的操作選項"
 | 
			
		||||
showReactionsCount: "顯示貼文的反應數目"
 | 
			
		||||
noHistory: "沒有歷史紀錄"
 | 
			
		||||
@@ -1273,6 +1279,10 @@ performance: "性能"
 | 
			
		||||
modified: "已變更"
 | 
			
		||||
discard: "取消"
 | 
			
		||||
thereAreNChanges: "有 {n} 處的變更"
 | 
			
		||||
signinWithPasskey: "使用密碼金鑰登入"
 | 
			
		||||
unknownWebAuthnKey: "未註冊的金鑰。"
 | 
			
		||||
passkeyVerificationFailed: "驗證金鑰失敗。"
 | 
			
		||||
passkeyVerificationSucceededButPasswordlessLoginDisabled: "雖然驗證金鑰成功,但是無密碼登入的方式是停用的。"
 | 
			
		||||
_delivery:
 | 
			
		||||
  status: "傳送狀態"
 | 
			
		||||
  stop: "停止發送"
 | 
			
		||||
@@ -2240,6 +2250,9 @@ _profile:
 | 
			
		||||
  changeBanner: "變更橫幅圖像"
 | 
			
		||||
  verifiedLinkDescription: "如果輸入包含您個人資料的網站 URL,欄位旁邊將出現驗證圖示。"
 | 
			
		||||
  avatarDecorationMax: "最多可以設置 {max} 個裝飾。"
 | 
			
		||||
  followedMessage: "被追隨時的訊息"
 | 
			
		||||
  followedMessageDescription: "可以設定被追隨時顯示給對方的訊息。"
 | 
			
		||||
  followedMessageDescriptionForLockedAccount: "如果追隨是需要審核的話,在允許追隨請求之後顯示。"
 | 
			
		||||
_exportOrImport:
 | 
			
		||||
  allNotes: "所有貼文"
 | 
			
		||||
  favoritedNotes: "「我的最愛」貼文"
 | 
			
		||||
@@ -2378,6 +2391,7 @@ _notification:
 | 
			
		||||
  renotedBySomeUsers: "{n}人做了轉發"
 | 
			
		||||
  followedBySomeUsers: "被{n}人追隨了"
 | 
			
		||||
  flushNotification: "重置通知歷史紀錄"
 | 
			
		||||
  exportOfXCompleted: "{x} 的匯出已完成。"
 | 
			
		||||
  _types:
 | 
			
		||||
    all: "全部 "
 | 
			
		||||
    note: "使用者的最新貼文"
 | 
			
		||||
@@ -2392,6 +2406,8 @@ _notification:
 | 
			
		||||
    followRequestAccepted: "追隨請求已接受"
 | 
			
		||||
    roleAssigned: "已授予角色"
 | 
			
		||||
    achievementEarned: "獲得成就"
 | 
			
		||||
    exportCompleted: "已完成匯出。"
 | 
			
		||||
    test: "通知測試"
 | 
			
		||||
    app: "應用程式通知"
 | 
			
		||||
  _actions:
 | 
			
		||||
    followBack: "追隨回去"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "misskey",
 | 
			
		||||
	"version": "2024.9.0-alpha.12",
 | 
			
		||||
	"version": "2024.10.0-alpha.0",
 | 
			
		||||
	"codename": "nasubi",
 | 
			
		||||
	"repository": {
 | 
			
		||||
		"type": "git",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								packages/backend/assets/tabler-badges/login-2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								packages/backend/assets/tabler-badges/login-2.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 3.7 KiB  | 
@@ -67,24 +67,24 @@
 | 
			
		||||
	"dependencies": {
 | 
			
		||||
		"@aws-sdk/client-s3": "3.620.0",
 | 
			
		||||
		"@aws-sdk/lib-storage": "3.620.0",
 | 
			
		||||
		"@bull-board/api": "5.23.0",
 | 
			
		||||
		"@bull-board/fastify": "5.23.0",
 | 
			
		||||
		"@bull-board/ui": "5.23.0",
 | 
			
		||||
		"@bull-board/api": "6.0.0",
 | 
			
		||||
		"@bull-board/fastify": "6.0.0",
 | 
			
		||||
		"@bull-board/ui": "6.0.0",
 | 
			
		||||
		"@discordapp/twemoji": "15.1.0",
 | 
			
		||||
		"@fastify/accepts": "5.0.0",
 | 
			
		||||
		"@fastify/cookie": "10.0.0",
 | 
			
		||||
		"@fastify/cors": "10.0.0",
 | 
			
		||||
		"@fastify/express": "4.0.0",
 | 
			
		||||
		"@fastify/accepts": "5.0.1",
 | 
			
		||||
		"@fastify/cookie": "10.0.1",
 | 
			
		||||
		"@fastify/cors": "10.0.1",
 | 
			
		||||
		"@fastify/express": "4.0.1",
 | 
			
		||||
		"@fastify/http-proxy": "10.0.0",
 | 
			
		||||
		"@fastify/multipart": "9.0.0",
 | 
			
		||||
		"@fastify/static": "8.0.0",
 | 
			
		||||
		"@fastify/view": "10.0.0",
 | 
			
		||||
		"@fastify/multipart": "9.0.1",
 | 
			
		||||
		"@fastify/static": "8.0.1",
 | 
			
		||||
		"@fastify/view": "10.0.1",
 | 
			
		||||
		"@misskey-dev/sharp-read-bmp": "1.2.0",
 | 
			
		||||
		"@misskey-dev/summaly": "5.1.0",
 | 
			
		||||
		"@napi-rs/canvas": "0.1.56",
 | 
			
		||||
		"@nestjs/common": "10.4.3",
 | 
			
		||||
		"@nestjs/core": "10.4.3",
 | 
			
		||||
		"@nestjs/testing": "10.4.3",
 | 
			
		||||
		"@nestjs/common": "10.4.4",
 | 
			
		||||
		"@nestjs/core": "10.4.4",
 | 
			
		||||
		"@nestjs/testing": "10.4.4",
 | 
			
		||||
		"@peertube/http-signature": "1.7.0",
 | 
			
		||||
		"@sentry/node": "8.20.0",
 | 
			
		||||
		"@sentry/profiling-node": "8.20.0",
 | 
			
		||||
@@ -149,7 +149,7 @@
 | 
			
		||||
		"oauth2orize": "1.12.0",
 | 
			
		||||
		"oauth2orize-pkce": "0.1.2",
 | 
			
		||||
		"os-utils": "0.0.14",
 | 
			
		||||
		"otpauth": "9.3.2",
 | 
			
		||||
		"otpauth": "9.3.4",
 | 
			
		||||
		"parse5": "7.1.2",
 | 
			
		||||
		"pg": "8.13.0",
 | 
			
		||||
		"pkce-challenge": "4.1.0",
 | 
			
		||||
@@ -187,7 +187,7 @@
 | 
			
		||||
	},
 | 
			
		||||
	"devDependencies": {
 | 
			
		||||
		"@jest/globals": "29.7.0",
 | 
			
		||||
		"@nestjs/platform-express": "10.4.3",
 | 
			
		||||
		"@nestjs/platform-express": "10.4.4",
 | 
			
		||||
		"@simplewebauthn/types": "10.0.0",
 | 
			
		||||
		"@swc/jest": "0.2.36",
 | 
			
		||||
		"@types/accepts": "1.3.7",
 | 
			
		||||
 
 | 
			
		||||
@@ -3,12 +3,12 @@
 | 
			
		||||
 * SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { userExportableEntities } from '@/types.js';
 | 
			
		||||
import { MiUser } from './User.js';
 | 
			
		||||
import { MiNote } from './Note.js';
 | 
			
		||||
import { MiAccessToken } from './AccessToken.js';
 | 
			
		||||
import { MiRole } from './Role.js';
 | 
			
		||||
import { MiDriveFile } from './DriveFile.js';
 | 
			
		||||
import { userExportableEntities } from '@/types.js';
 | 
			
		||||
 | 
			
		||||
export type MiNotification = {
 | 
			
		||||
	type: 'note';
 | 
			
		||||
@@ -86,6 +86,10 @@ export type MiNotification = {
 | 
			
		||||
	createdAt: string;
 | 
			
		||||
	exportedEntity: typeof userExportableEntities[number];
 | 
			
		||||
	fileId: MiDriveFile['id'];
 | 
			
		||||
} | {
 | 
			
		||||
	type: 'login';
 | 
			
		||||
	id: string;
 | 
			
		||||
	createdAt: string;
 | 
			
		||||
} | {
 | 
			
		||||
	type: 'app';
 | 
			
		||||
	id: string;
 | 
			
		||||
 
 | 
			
		||||
@@ -322,6 +322,16 @@ export const packedNotificationSchema = {
 | 
			
		||||
				format: 'id',
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}, {
 | 
			
		||||
		type: 'object',
 | 
			
		||||
		properties: {
 | 
			
		||||
			...baseSchema.properties,
 | 
			
		||||
			type: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				optional: false, nullable: false,
 | 
			
		||||
				enum: ['login'],
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}, {
 | 
			
		||||
		type: 'object',
 | 
			
		||||
		properties: {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ import * as OTPAuth from 'otpauth';
 | 
			
		||||
import { IsNull } from 'typeorm';
 | 
			
		||||
import { DI } from '@/di-symbols.js';
 | 
			
		||||
import type {
 | 
			
		||||
	MiMeta,
 | 
			
		||||
	SigninsRepository,
 | 
			
		||||
	UserProfilesRepository,
 | 
			
		||||
	UsersRepository,
 | 
			
		||||
@@ -20,6 +21,8 @@ import { IdService } from '@/core/IdService.js';
 | 
			
		||||
import { bindThis } from '@/decorators.js';
 | 
			
		||||
import { WebAuthnService } from '@/core/WebAuthnService.js';
 | 
			
		||||
import { UserAuthService } from '@/core/UserAuthService.js';
 | 
			
		||||
import { CaptchaService } from '@/core/CaptchaService.js';
 | 
			
		||||
import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
 | 
			
		||||
import { RateLimiterService } from './RateLimiterService.js';
 | 
			
		||||
import { SigninService } from './SigninService.js';
 | 
			
		||||
import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
 | 
			
		||||
@@ -31,6 +34,9 @@ export class SigninApiService {
 | 
			
		||||
		@Inject(DI.config)
 | 
			
		||||
		private config: Config,
 | 
			
		||||
 | 
			
		||||
		@Inject(DI.meta)
 | 
			
		||||
		private meta: MiMeta,
 | 
			
		||||
 | 
			
		||||
		@Inject(DI.usersRepository)
 | 
			
		||||
		private usersRepository: UsersRepository,
 | 
			
		||||
 | 
			
		||||
@@ -45,6 +51,7 @@ export class SigninApiService {
 | 
			
		||||
		private signinService: SigninService,
 | 
			
		||||
		private userAuthService: UserAuthService,
 | 
			
		||||
		private webAuthnService: WebAuthnService,
 | 
			
		||||
		private captchaService: CaptchaService,
 | 
			
		||||
	) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -56,6 +63,10 @@ export class SigninApiService {
 | 
			
		||||
				password: string;
 | 
			
		||||
				token?: string;
 | 
			
		||||
				credential?: AuthenticationResponseJSON;
 | 
			
		||||
				'hcaptcha-response'?: string;
 | 
			
		||||
				'g-recaptcha-response'?: string;
 | 
			
		||||
				'turnstile-response'?: string;
 | 
			
		||||
				'm-captcha-response'?: string;
 | 
			
		||||
			};
 | 
			
		||||
		}>,
 | 
			
		||||
		reply: FastifyReply,
 | 
			
		||||
@@ -139,6 +150,32 @@ export class SigninApiService {
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		if (!profile.twoFactorEnabled) {
 | 
			
		||||
			if (process.env.NODE_ENV !== 'test') {
 | 
			
		||||
				if (this.meta.enableHcaptcha && this.meta.hcaptchaSecretKey) {
 | 
			
		||||
					await this.captchaService.verifyHcaptcha(this.meta.hcaptchaSecretKey, body['hcaptcha-response']).catch(err => {
 | 
			
		||||
						throw new FastifyReplyError(400, err);
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (this.meta.enableMcaptcha && this.meta.mcaptchaSecretKey && this.meta.mcaptchaSitekey && this.meta.mcaptchaInstanceUrl) {
 | 
			
		||||
					await this.captchaService.verifyMcaptcha(this.meta.mcaptchaSecretKey, this.meta.mcaptchaSitekey, this.meta.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => {
 | 
			
		||||
						throw new FastifyReplyError(400, err);
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (this.meta.enableRecaptcha && this.meta.recaptchaSecretKey) {
 | 
			
		||||
					await this.captchaService.verifyRecaptcha(this.meta.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => {
 | 
			
		||||
						throw new FastifyReplyError(400, err);
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (this.meta.enableTurnstile && this.meta.turnstileSecretKey) {
 | 
			
		||||
					await this.captchaService.verifyTurnstile(this.meta.turnstileSecretKey, body['turnstile-response']).catch(err => {
 | 
			
		||||
						throw new FastifyReplyError(400, err);
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (same) {
 | 
			
		||||
				return this.signinService.signin(request, reply, user);
 | 
			
		||||
			} else {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,12 +5,14 @@
 | 
			
		||||
 | 
			
		||||
import { Inject, Injectable } from '@nestjs/common';
 | 
			
		||||
import { DI } from '@/di-symbols.js';
 | 
			
		||||
import type { SigninsRepository } from '@/models/_.js';
 | 
			
		||||
import type { SigninsRepository, UserProfilesRepository } from '@/models/_.js';
 | 
			
		||||
import { IdService } from '@/core/IdService.js';
 | 
			
		||||
import type { MiLocalUser } from '@/models/User.js';
 | 
			
		||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
 | 
			
		||||
import { SigninEntityService } from '@/core/entities/SigninEntityService.js';
 | 
			
		||||
import { bindThis } from '@/decorators.js';
 | 
			
		||||
import { EmailService } from '@/core/EmailService.js';
 | 
			
		||||
import { NotificationService } from '@/core/NotificationService.js';
 | 
			
		||||
import type { FastifyRequest, FastifyReply } from 'fastify';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
@@ -19,7 +21,12 @@ export class SigninService {
 | 
			
		||||
		@Inject(DI.signinsRepository)
 | 
			
		||||
		private signinsRepository: SigninsRepository,
 | 
			
		||||
 | 
			
		||||
		@Inject(DI.userProfilesRepository)
 | 
			
		||||
		private userProfilesRepository: UserProfilesRepository,
 | 
			
		||||
 | 
			
		||||
		private signinEntityService: SigninEntityService,
 | 
			
		||||
		private emailService: EmailService,
 | 
			
		||||
		private notificationService: NotificationService,
 | 
			
		||||
		private idService: IdService,
 | 
			
		||||
		private globalEventService: GlobalEventService,
 | 
			
		||||
	) {
 | 
			
		||||
@@ -28,7 +35,8 @@ export class SigninService {
 | 
			
		||||
	@bindThis
 | 
			
		||||
	public signin(request: FastifyRequest, reply: FastifyReply, user: MiLocalUser) {
 | 
			
		||||
		setImmediate(async () => {
 | 
			
		||||
			// Append signin history
 | 
			
		||||
			this.notificationService.createNotification(user.id, 'login', {});
 | 
			
		||||
 | 
			
		||||
			const record = await this.signinsRepository.insertOne({
 | 
			
		||||
				id: this.idService.gen(),
 | 
			
		||||
				userId: user.id,
 | 
			
		||||
@@ -37,8 +45,14 @@ export class SigninService {
 | 
			
		||||
				success: true,
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			// Publish signin event
 | 
			
		||||
			this.globalEventService.publishMainStream(user.id, 'signin', await this.signinEntityService.pack(record));
 | 
			
		||||
 | 
			
		||||
			const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
 | 
			
		||||
			if (profile.email && profile.emailVerified) {
 | 
			
		||||
				this.emailService.sendEmail(profile.email, 'New login / ログインがありました',
 | 
			
		||||
					'There is a new login. If you do not recognize this login, update the security status of your account, including changing your password. / 新しいログインがありました。このログインに心当たりがない場合は、パスワードを変更するなど、アカウントのセキュリティ状態を更新してください。',
 | 
			
		||||
					'There is a new login. If you do not recognize this login, update the security status of your account, including changing your password. / 新しいログインがありました。このログインに心当たりがない場合は、パスワードを変更するなど、アカウントのセキュリティ状態を更新してください。');
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		reply.code(200);
 | 
			
		||||
 
 | 
			
		||||
@@ -256,7 +256,7 @@ export class ClientServerService {
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		bullBoardServerAdapter.setBasePath(bullBoardPath);
 | 
			
		||||
		//(fastify.register as any)(bullBoardServerAdapter.registerPlugin(), { prefix: bullBoardPath });
 | 
			
		||||
		(fastify.register as any)(bullBoardServerAdapter.registerPlugin(), { prefix: bullBoardPath });
 | 
			
		||||
		//#endregion
 | 
			
		||||
 | 
			
		||||
		fastify.register(fastifyView, {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@
 | 
			
		||||
 * roleAssigned - ロールが付与された
 | 
			
		||||
 * achievementEarned - 実績を獲得
 | 
			
		||||
 * exportCompleted - エクスポートが完了
 | 
			
		||||
 * login - ログイン
 | 
			
		||||
 * app - アプリ通知
 | 
			
		||||
 * test - テスト通知(サーバー側)
 | 
			
		||||
 */
 | 
			
		||||
@@ -34,6 +35,7 @@ export const notificationTypes = [
 | 
			
		||||
	'roleAssigned',
 | 
			
		||||
	'achievementEarned',
 | 
			
		||||
	'exportCompleted',
 | 
			
		||||
	'login',
 | 
			
		||||
	'app',
 | 
			
		||||
	'test',
 | 
			
		||||
] as const;
 | 
			
		||||
 
 | 
			
		||||
@@ -180,7 +180,6 @@ describe('Webリソース', () => {
 | 
			
		||||
		}));
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	/* queueは一時的に無効化されている
 | 
			
		||||
	describe.each([{ path: '/queue' }])('$path', ({ path }) => {
 | 
			
		||||
		test('はログインしないとGETできない。', async () => await notOk({
 | 
			
		||||
			path,
 | 
			
		||||
@@ -198,7 +197,6 @@ describe('Webリソース', () => {
 | 
			
		||||
			cookie: cookie(alice),
 | 
			
		||||
		}));
 | 
			
		||||
	});
 | 
			
		||||
	*/
 | 
			
		||||
 | 
			
		||||
	describe.each([{ path: '/streaming' }])('$path', ({ path }) => {
 | 
			
		||||
		test('はGETできない。', async () => await notOk({
 | 
			
		||||
 
 | 
			
		||||
@@ -91,6 +91,11 @@ export function getConfig(): UserConfig {
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			preprocessorOptions: {
 | 
			
		||||
				scss: {
 | 
			
		||||
					api: 'modern-compiler',
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		define: {
 | 
			
		||||
 
 | 
			
		||||
@@ -68,6 +68,7 @@ export const notificationTypes = [
 | 
			
		||||
	'roleAssigned',
 | 
			
		||||
	'achievementEarned',
 | 
			
		||||
	'exportCompleted',
 | 
			
		||||
	'login',
 | 
			
		||||
	'test',
 | 
			
		||||
	'app',
 | 
			
		||||
] as const;
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,7 @@
 | 
			
		||||
		"rollup": "4.22.5",
 | 
			
		||||
		"sanitize-html": "2.13.0",
 | 
			
		||||
		"sass": "1.79.3",
 | 
			
		||||
		"shiki": "1.12.0",
 | 
			
		||||
		"shiki": "1.21.0",
 | 
			
		||||
		"strict-event-emitter-types": "2.0.0",
 | 
			
		||||
		"textarea-caret": "3.1.0",
 | 
			
		||||
		"three": "0.169.0",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										100
									
								
								packages/frontend/src/components/MkFukidashi.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								packages/frontend/src/components/MkFukidashi.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
<!--
 | 
			
		||||
SPDX-FileCopyrightText: syuilo and other misskey contributors
 | 
			
		||||
SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div
 | 
			
		||||
	:class="[
 | 
			
		||||
		$style.root,
 | 
			
		||||
		tail === 'left' ? $style.left : $style.right,
 | 
			
		||||
		negativeMargin === true && $style.negativeMergin,
 | 
			
		||||
		shadow === true && $style.shadow,
 | 
			
		||||
	]"
 | 
			
		||||
>
 | 
			
		||||
	<div :class="$style.bg">
 | 
			
		||||
		<svg v-if="tail !== 'none'" :class="$style.tail" version="1.1" viewBox="0 0 14.597 14.58" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
			<g transform="translate(-173.71 -87.184)">
 | 
			
		||||
				<path d="m188.19 87.657c-1.469 2.3218-3.9315 3.8312-6.667 4.0865-2.2309-1.7379-4.9781-2.6816-7.8061-2.6815h-5.1e-4v12.702h12.702v-5.1e-4c2e-5 -1.9998-0.47213-3.9713-1.378-5.754 2.0709-1.6834 3.2732-4.2102 3.273-6.8791-6e-5 -0.49375-0.0413-0.98662-0.1235-1.4735z" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke-width=".33225" style="paint-order:stroke fill markers"/>
 | 
			
		||||
			</g>
 | 
			
		||||
		</svg>
 | 
			
		||||
		<div :class="$style.content">
 | 
			
		||||
			<slot></slot>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
withDefaults(defineProps<{
 | 
			
		||||
	tail?: 'left' | 'right' | 'none';
 | 
			
		||||
	negativeMargin?: boolean;
 | 
			
		||||
	shadow?: boolean;
 | 
			
		||||
}>(), {
 | 
			
		||||
	tail: 'right',
 | 
			
		||||
	negativeMargin: false,
 | 
			
		||||
	shadow: false,
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style module lang="scss">
 | 
			
		||||
.root {
 | 
			
		||||
	--fukidashi-radius: var(--radius);
 | 
			
		||||
	--fukidashi-bg: var(--panel);
 | 
			
		||||
 | 
			
		||||
	position: relative;
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
	min-height: calc(var(--fukidashi-radius) * 2);
 | 
			
		||||
	padding-top: calc(var(--fukidashi-radius) * .13);
 | 
			
		||||
 | 
			
		||||
	&.shadow {
 | 
			
		||||
		filter: drop-shadow(0 4px 32px var(--shadow));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.left {
 | 
			
		||||
		padding-left: calc(var(--fukidashi-radius) * .13);
 | 
			
		||||
 | 
			
		||||
		&.negativeMergin {
 | 
			
		||||
			margin-left: calc(calc(var(--fukidashi-radius) * .13) * -1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.right {
 | 
			
		||||
		padding-right: calc(var(--fukidashi-radius) * .13);
 | 
			
		||||
 | 
			
		||||
		&.negativeMergin {
 | 
			
		||||
			margin-right: calc(calc(var(--fukidashi-radius) * .13) * -1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bg {
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	height: 100%;
 | 
			
		||||
	background: var(--fukidashi-bg);
 | 
			
		||||
	border-radius: var(--fukidashi-radius);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.content {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	padding: 8px 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tail {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	display: block;
 | 
			
		||||
	width: calc(var(--fukidashi-radius) * 1.13);
 | 
			
		||||
	height: auto;
 | 
			
		||||
	fill: var(--fukidashi-bg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.left .tail {
 | 
			
		||||
	left: 0;
 | 
			
		||||
	transform: rotateY(180deg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.right .tail {
 | 
			
		||||
	right: 0;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<MkA v-user-preview="canonical" :class="[$style.root, { [$style.isMe]: isMe }]" :to="url" :style="{ background: bgCss }" :behavior="navigationBehavior">
 | 
			
		||||
<MkA v-user-preview="canonical" :class="[$style.root, { [$style.isMe]: isMe }]" :to="url" :behavior="navigationBehavior">
 | 
			
		||||
	<img :class="$style.icon" :src="avatarUrl" alt="">
 | 
			
		||||
	<span>
 | 
			
		||||
		<span>@{{ username }}</span>
 | 
			
		||||
@@ -16,7 +16,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { toUnicode } from 'punycode';
 | 
			
		||||
import { computed } from 'vue';
 | 
			
		||||
import tinycolor from 'tinycolor2';
 | 
			
		||||
import { host as localHost } from '@@/js/config.js';
 | 
			
		||||
import { $i } from '@/account.js';
 | 
			
		||||
import { defaultStore } from '@/store.js';
 | 
			
		||||
@@ -37,11 +36,7 @@ const isMe = $i && (
 | 
			
		||||
	`@${props.username}@${toUnicode(props.host)}` === `@${$i.username}@${toUnicode(localHost)}`.toLowerCase()
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue(isMe ? '--mentionMe' : '--mention'));
 | 
			
		||||
bg.setAlpha(0.1);
 | 
			
		||||
const bgCss = bg.toRgbString();
 | 
			
		||||
 | 
			
		||||
const avatarUrl = computed(() => defaultStore.state.disableShowingAnimatedImages
 | 
			
		||||
const avatarUrl = computed(() => defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar
 | 
			
		||||
	? getStaticImageUrl(`/avatar/@${props.username}@${props.host}`)
 | 
			
		||||
	: `/avatar/@${props.username}@${props.host}`,
 | 
			
		||||
);
 | 
			
		||||
@@ -53,9 +48,11 @@ const avatarUrl = computed(() => defaultStore.state.disableShowingAnimatedImages
 | 
			
		||||
	padding: 4px 8px 4px 4px;
 | 
			
		||||
	border-radius: 999px;
 | 
			
		||||
	color: var(--mention);
 | 
			
		||||
	background: color(from var(--mention) srgb r g b / 0.1);
 | 
			
		||||
 | 
			
		||||
	&.isMe {
 | 
			
		||||
		color: var(--mentionMe);
 | 
			
		||||
		background: color(from var(--mentionMe) srgb r g b / 0.1);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -53,7 +53,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
			<MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
 | 
			
		||||
			<div style="container-type: inline-size;">
 | 
			
		||||
				<p v-if="appearNote.cw != null" :class="$style.cw">
 | 
			
		||||
					<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/>
 | 
			
		||||
					<Mfm
 | 
			
		||||
						v-if="appearNote.cw != ''"
 | 
			
		||||
						:text="appearNote.cw"
 | 
			
		||||
						:author="appearNote.user"
 | 
			
		||||
						:nyaize="'respect'"
 | 
			
		||||
						:enableEmojiMenu="true"
 | 
			
		||||
						:enableEmojiMenuReaction="true"
 | 
			
		||||
					/>
 | 
			
		||||
					<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll" style="margin: 4px 0;"/>
 | 
			
		||||
				</p>
 | 
			
		||||
				<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
		</header>
 | 
			
		||||
		<div :class="$style.noteContent">
 | 
			
		||||
			<p v-if="appearNote.cw != null" :class="$style.cw">
 | 
			
		||||
				<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/>
 | 
			
		||||
				<Mfm
 | 
			
		||||
					v-if="appearNote.cw != ''"
 | 
			
		||||
					:text="appearNote.cw"
 | 
			
		||||
					:author="appearNote.user"
 | 
			
		||||
					:nyaize="'respect'"
 | 
			
		||||
					:enableEmojiMenu="true"
 | 
			
		||||
					:enableEmojiMenuReaction="true"
 | 
			
		||||
				/>
 | 
			
		||||
				<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll"/>
 | 
			
		||||
			</p>
 | 
			
		||||
			<div v-show="appearNote.cw == null || showContent">
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<header :class="$style.root">
 | 
			
		||||
	<component :is="defaultStore.state.enableCondensedLine ? 'MkCondensedLine' : 'div'" :minScale="0" style="min-width: 0;">
 | 
			
		||||
	<component :is="defaultStore.state.enableCondensedLine ? 'MkCondensedLine' : 'div'" :minScale="0.7" style="min-width: 0;">
 | 
			
		||||
		<div style="display: flex; white-space: nowrap; align-items: baseline;">
 | 
			
		||||
			<div v-if="mock" :class="$style.name">
 | 
			
		||||
				<MkUserName :user="note.user"/>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,13 +7,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
<div :class="$style.root">
 | 
			
		||||
	<div :class="$style.head">
 | 
			
		||||
		<MkAvatar v-if="['pollEnded', 'note'].includes(notification.type) && 'note' in notification" :class="$style.icon" :user="notification.note.user" link preview/>
 | 
			
		||||
		<MkAvatar v-else-if="['roleAssigned', 'achievementEarned'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/>
 | 
			
		||||
		<MkAvatar v-else-if="['roleAssigned', 'achievementEarned', 'exportCompleted', 'login'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/>
 | 
			
		||||
		<div v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'" :class="[$style.icon, $style.icon_reactionGroupHeart]"><i class="ti ti-heart" style="line-height: 1;"></i></div>
 | 
			
		||||
		<div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ti ti-plus" style="line-height: 1;"></i></div>
 | 
			
		||||
		<div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div>
 | 
			
		||||
		<img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/>
 | 
			
		||||
		<MkAvatar v-else-if="'user' in notification" :class="$style.icon" :user="notification.user" link preview/>
 | 
			
		||||
		<MkAvatar v-else-if="notification.type === 'exportCompleted'" :class="$style.icon" :user="$i" link preview/>
 | 
			
		||||
		<img v-else-if="'icon' in notification && notification.icon != null" :class="[$style.icon, $style.icon_app]" :src="notification.icon" alt=""/>
 | 
			
		||||
		<div
 | 
			
		||||
			:class="[$style.subIcon, {
 | 
			
		||||
@@ -27,6 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
				[$style.t_pollEnded]: notification.type === 'pollEnded',
 | 
			
		||||
				[$style.t_achievementEarned]: notification.type === 'achievementEarned',
 | 
			
		||||
				[$style.t_exportCompleted]: notification.type === 'exportCompleted',
 | 
			
		||||
				[$style.t_login]: notification.type === 'login',
 | 
			
		||||
				[$style.t_roleAssigned]: notification.type === 'roleAssigned' && notification.role.iconUrl == null,
 | 
			
		||||
			}]"
 | 
			
		||||
		>
 | 
			
		||||
@@ -40,6 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
			<i v-else-if="notification.type === 'pollEnded'" class="ti ti-chart-arrows"></i>
 | 
			
		||||
			<i v-else-if="notification.type === 'achievementEarned'" class="ti ti-medal"></i>
 | 
			
		||||
			<i v-else-if="notification.type === 'exportCompleted'" class="ti ti-archive"></i>
 | 
			
		||||
			<i v-else-if="notification.type === 'login'" class="ti ti-login-2"></i>
 | 
			
		||||
			<template v-else-if="notification.type === 'roleAssigned'">
 | 
			
		||||
				<img v-if="notification.role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="notification.role.iconUrl" alt=""/>
 | 
			
		||||
				<i v-else class="ti ti-badges"></i>
 | 
			
		||||
@@ -59,6 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
			<span v-else-if="notification.type === 'note'">{{ i18n.ts._notification.newNote }}: <MkUserName :user="notification.note.user"/></span>
 | 
			
		||||
			<span v-else-if="notification.type === 'roleAssigned'">{{ i18n.ts._notification.roleAssigned }}</span>
 | 
			
		||||
			<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
 | 
			
		||||
			<span v-else-if="notification.type === 'login'">{{ i18n.ts._notification.login }}</span>
 | 
			
		||||
			<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
 | 
			
		||||
			<span v-else-if="notification.type === 'exportCompleted'">{{ i18n.tsx._notification.exportOfXCompleted({ x: exportEntityName[notification.exportedEntity] }) }}</span>
 | 
			
		||||
			<MkA v-else-if="notification.type === 'follow' || notification.type === 'mention' || notification.type === 'reply' || notification.type === 'renote' || notification.type === 'quote' || notification.type === 'reaction' || notification.type === 'receiveFollowRequest' || notification.type === 'followRequestAccepted'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
 | 
			
		||||
@@ -225,6 +227,7 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
 | 
			
		||||
	--eventReactionHeart: var(--love);
 | 
			
		||||
	--eventReaction: #e99a0b;
 | 
			
		||||
	--eventAchievement: #cb9a11;
 | 
			
		||||
	--eventLogin: #007aff;
 | 
			
		||||
	--eventOther: #88a6b7;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -346,6 +349,12 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
 | 
			
		||||
	pointer-events: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.t_login {
 | 
			
		||||
	padding: 3px;
 | 
			
		||||
	background: var(--eventLogin);
 | 
			
		||||
	pointer-events: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tail {
 | 
			
		||||
	flex: 1;
 | 
			
		||||
	min-width: 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
<div v-show="props.modelValue.length != 0" :class="$style.root">
 | 
			
		||||
	<Sortable :modelValue="props.modelValue" :class="$style.files" itemKey="id" :animation="150" :delay="100" :delayOnTouchOnly="true" @update:modelValue="v => emit('update:modelValue', v)">
 | 
			
		||||
		<template #item="{element}">
 | 
			
		||||
			<div :class="$style.file" @click="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)">
 | 
			
		||||
			<div
 | 
			
		||||
				:class="$style.file"
 | 
			
		||||
				role="button"
 | 
			
		||||
				tabindex="0"
 | 
			
		||||
				@click="showFileMenu(element, $event)"
 | 
			
		||||
				@keydown.space.enter="showFileMenu(element, $event)"
 | 
			
		||||
				@contextmenu.prevent="showFileMenu(element, $event)"
 | 
			
		||||
			>
 | 
			
		||||
				<MkDriveFileThumbnail :data-id="element.id" :class="$style.thumbnail" :file="element" fit="cover"/>
 | 
			
		||||
				<div v-if="element.isSensitive" :class="$style.sensitive">
 | 
			
		||||
					<i class="ti ti-eye-exclamation" style="margin: auto;"></i>
 | 
			
		||||
@@ -133,7 +140,7 @@ async function crop(file: Misskey.entities.DriveFile): Promise<void> {
 | 
			
		||||
	emit('replaceFile', file, newFile);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent): void {
 | 
			
		||||
function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | KeyboardEvent): void {
 | 
			
		||||
	if (menuShowing) return;
 | 
			
		||||
 | 
			
		||||
	const isImage = file.type.startsWith('image/');
 | 
			
		||||
@@ -199,6 +206,10 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent): void {
 | 
			
		||||
	border-radius: 4px;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	cursor: move;
 | 
			
		||||
 | 
			
		||||
	&:focus-visible {
 | 
			
		||||
		outline-offset: 4px;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.thumbnail {
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
				<template #prefix><i class="ti ti-lock"></i></template>
 | 
			
		||||
				<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template>
 | 
			
		||||
			</MkInput>
 | 
			
		||||
			<MkButton type="submit" large primary rounded :disabled="signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
 | 
			
		||||
			<MkCaptcha v-if="instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" :class="$style.captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/>
 | 
			
		||||
			<MkCaptcha v-if="instance.enableMcaptcha" ref="mcaptcha" v-model="mCaptchaResponse" :class="$style.captcha" provider="mcaptcha" :sitekey="instance.mcaptchaSiteKey" :instanceUrl="instance.mcaptchaInstanceUrl"/>
 | 
			
		||||
			<MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" :class="$style.captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/>
 | 
			
		||||
			<MkCaptcha v-if="instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" :class="$style.captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/>
 | 
			
		||||
			<MkButton type="submit" large primary rounded :disabled="captchaFailed || signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div v-if="totpLogin" class="2fa-signin" :class="{ securityKeys: user && user.securityKeys }">
 | 
			
		||||
			<div v-if="user && user.securityKeys" class="twofa-group tap-group">
 | 
			
		||||
@@ -68,7 +72,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { defineAsyncComponent, ref } from 'vue';
 | 
			
		||||
import { computed, defineAsyncComponent, ref } from 'vue';
 | 
			
		||||
import { toUnicode } from 'punycode/';
 | 
			
		||||
import * as Misskey from 'misskey-js';
 | 
			
		||||
import { supported as webAuthnSupported, get as webAuthnRequest, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
 | 
			
		||||
@@ -85,6 +89,8 @@ import * as os from '@/os.js';
 | 
			
		||||
import { misskeyApi } from '@/scripts/misskey-api.js';
 | 
			
		||||
import { login } from '@/account.js';
 | 
			
		||||
import { i18n } from '@/i18n.js';
 | 
			
		||||
import { instance } from '@/instance.js';
 | 
			
		||||
import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue';
 | 
			
		||||
 | 
			
		||||
const signing = ref(false);
 | 
			
		||||
const user = ref<Misskey.entities.UserDetailed | null>(null);
 | 
			
		||||
@@ -98,6 +104,22 @@ const isBackupCode = ref(false);
 | 
			
		||||
const queryingKey = ref(false);
 | 
			
		||||
let credentialRequest: CredentialRequestOptions | null = null;
 | 
			
		||||
const passkey_context = ref('');
 | 
			
		||||
const hcaptcha = ref<Captcha | undefined>();
 | 
			
		||||
const mcaptcha = ref<Captcha | undefined>();
 | 
			
		||||
const recaptcha = ref<Captcha | undefined>();
 | 
			
		||||
const turnstile = ref<Captcha | undefined>();
 | 
			
		||||
const hCaptchaResponse = ref<string | null>(null);
 | 
			
		||||
const mCaptchaResponse = ref<string | null>(null);
 | 
			
		||||
const reCaptchaResponse = ref<string | null>(null);
 | 
			
		||||
const turnstileResponse = ref<string | null>(null);
 | 
			
		||||
 | 
			
		||||
const captchaFailed = computed((): boolean => {
 | 
			
		||||
	return (
 | 
			
		||||
		instance.enableHcaptcha && !hCaptchaResponse.value ||
 | 
			
		||||
		instance.enableMcaptcha && !mCaptchaResponse.value ||
 | 
			
		||||
		instance.enableRecaptcha && !reCaptchaResponse.value ||
 | 
			
		||||
		instance.enableTurnstile && !turnstileResponse.value);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
	(ev: 'login', v: any): void;
 | 
			
		||||
@@ -227,6 +249,10 @@ function onSubmit(): void {
 | 
			
		||||
		misskeyApi('signin', {
 | 
			
		||||
			username: username.value,
 | 
			
		||||
			password: password.value,
 | 
			
		||||
			'hcaptcha-response': hCaptchaResponse.value,
 | 
			
		||||
			'm-captcha-response': mCaptchaResponse.value,
 | 
			
		||||
			'g-recaptcha-response': reCaptchaResponse.value,
 | 
			
		||||
			'turnstile-response': turnstileResponse.value,
 | 
			
		||||
			token: user.value?.twoFactorEnabled ? token.value : undefined,
 | 
			
		||||
		}).then(res => {
 | 
			
		||||
			emit('login', res);
 | 
			
		||||
@@ -236,6 +262,11 @@ function onSubmit(): void {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function loginFailed(err: any): void {
 | 
			
		||||
	hcaptcha.value?.reset?.();
 | 
			
		||||
	mcaptcha.value?.reset?.();
 | 
			
		||||
	recaptcha.value?.reset?.();
 | 
			
		||||
	turnstile.value?.reset?.();
 | 
			
		||||
 | 
			
		||||
	switch (err.id) {
 | 
			
		||||
		case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
 | 
			
		||||
			os.alert({
 | 
			
		||||
 
 | 
			
		||||
@@ -81,10 +81,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
import { ref, computed } from 'vue';
 | 
			
		||||
import { toUnicode } from 'punycode/';
 | 
			
		||||
import * as Misskey from 'misskey-js';
 | 
			
		||||
import * as config from '@@/js/config.js';
 | 
			
		||||
import MkButton from './MkButton.vue';
 | 
			
		||||
import MkInput from './MkInput.vue';
 | 
			
		||||
import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue';
 | 
			
		||||
import * as config from '@@/js/config.js';
 | 
			
		||||
import * as os from '@/os.js';
 | 
			
		||||
import { misskeyApi } from '@/scripts/misskey-api.js';
 | 
			
		||||
import { login } from '@/account.js';
 | 
			
		||||
@@ -105,6 +105,7 @@ const emit = defineEmits<{
 | 
			
		||||
const host = toUnicode(config.host);
 | 
			
		||||
 | 
			
		||||
const hcaptcha = ref<Captcha | undefined>();
 | 
			
		||||
const mcaptcha = ref<Captcha | undefined>();
 | 
			
		||||
const recaptcha = ref<Captcha | undefined>();
 | 
			
		||||
const turnstile = ref<Captcha | undefined>();
 | 
			
		||||
 | 
			
		||||
@@ -281,6 +282,7 @@ async function onSubmit(): Promise<void> {
 | 
			
		||||
	} catch {
 | 
			
		||||
		submitting.value = false;
 | 
			
		||||
		hcaptcha.value?.reset?.();
 | 
			
		||||
		mcaptcha.value?.reset?.();
 | 
			
		||||
		recaptcha.value?.reset?.();
 | 
			
		||||
		turnstile.value?.reset?.();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<span :class="$style.container">
 | 
			
		||||
	<span ref="content" :class="$style.content">
 | 
			
		||||
	<span ref="content" :class="$style.content" :style="{ maxWidth: `${100 / minScale}%` }">
 | 
			
		||||
		<slot/>
 | 
			
		||||
	</span>
 | 
			
		||||
</span>
 | 
			
		||||
 
 | 
			
		||||
@@ -48,9 +48,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div v-if="user.followedMessage != null" class="followedMessage">
 | 
			
		||||
						<div style="border: solid 1px var(--love); border-radius: 6px; background: color-mix(in srgb, var(--love), transparent 90%); padding: 6px 8px;">
 | 
			
		||||
							<Mfm :text="user.followedMessage" :author="user"/>
 | 
			
		||||
						</div>
 | 
			
		||||
						<MkFukidashi class="fukidashi" :tail="narrow ? 'none' : 'left'" negativeMargin shadow>
 | 
			
		||||
							<div class="messageHeader">{{ i18n.ts.messageToFollower }}</div>
 | 
			
		||||
							<div><Mfm :text="user.followedMessage" :author="user"/></div>
 | 
			
		||||
						</MkFukidashi>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div v-if="user.roles.length > 0" class="roles">
 | 
			
		||||
						<span v-for="role in user.roles" :key="role.id" v-tooltip="role.description" class="role" :style="{ '--color': role.color }">
 | 
			
		||||
@@ -161,6 +162,7 @@ import * as Misskey from 'misskey-js';
 | 
			
		||||
import MkNote from '@/components/MkNote.vue';
 | 
			
		||||
import MkFollowButton from '@/components/MkFollowButton.vue';
 | 
			
		||||
import MkAccountMoved from '@/components/MkAccountMoved.vue';
 | 
			
		||||
import MkFukidashi from '@/components/MkFukidashi.vue';
 | 
			
		||||
import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
 | 
			
		||||
import MkTextarea from '@/components/MkTextarea.vue';
 | 
			
		||||
import MkOmit from '@/components/MkOmit.vue';
 | 
			
		||||
@@ -467,7 +469,18 @@ onUnmounted(() => {
 | 
			
		||||
 | 
			
		||||
				> .followedMessage {
 | 
			
		||||
					padding: 24px 24px 0 154px;
 | 
			
		||||
					font-size: 0.9em;
 | 
			
		||||
 | 
			
		||||
					> .fukidashi {
 | 
			
		||||
						display: block;
 | 
			
		||||
						--fukidashi-bg: color-mix(in srgb, var(--love), var(--panel) 85%);
 | 
			
		||||
						--fukidashi-radius: 16px;
 | 
			
		||||
						font-size: 0.9em;
 | 
			
		||||
 | 
			
		||||
						.messageHeader {
 | 
			
		||||
							opacity: 0.7;
 | 
			
		||||
							font-size: 0.85em;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				> .roles {
 | 
			
		||||
 
 | 
			
		||||
@@ -109,6 +109,11 @@ export function getConfig(): UserConfig {
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			preprocessorOptions: {
 | 
			
		||||
				scss: {
 | 
			
		||||
					api: 'modern-compiler',
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		define: {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
	"type": "module",
 | 
			
		||||
	"name": "misskey-js",
 | 
			
		||||
	"version": "2024.9.0-alpha.12",
 | 
			
		||||
	"version": "2024.10.0-alpha.0",
 | 
			
		||||
	"description": "Misskey SDK for JavaScript",
 | 
			
		||||
	"license": "MIT",
 | 
			
		||||
	"main": "./built/index.js",
 | 
			
		||||
 
 | 
			
		||||
@@ -4288,7 +4288,14 @@ export type components = {
 | 
			
		||||
      exportedEntity: 'antenna' | 'blocking' | 'clip' | 'customEmoji' | 'favorite' | 'following' | 'muting' | 'note' | 'userList';
 | 
			
		||||
      /** Format: id */
 | 
			
		||||
      fileId: string;
 | 
			
		||||
    }) | ({
 | 
			
		||||
    }) | {
 | 
			
		||||
      /** Format: id */
 | 
			
		||||
      id: string;
 | 
			
		||||
      /** Format: date-time */
 | 
			
		||||
      createdAt: string;
 | 
			
		||||
      /** @enum {string} */
 | 
			
		||||
      type: 'login';
 | 
			
		||||
    } | ({
 | 
			
		||||
      /** Format: id */
 | 
			
		||||
      id: string;
 | 
			
		||||
      /** Format: date-time */
 | 
			
		||||
@@ -5177,6 +5184,8 @@ export type operations = {
 | 
			
		||||
            urlPreviewRequireContentLength: boolean;
 | 
			
		||||
            urlPreviewUserAgent: string | null;
 | 
			
		||||
            urlPreviewSummaryProxyUrl: string | null;
 | 
			
		||||
            federation: string;
 | 
			
		||||
            federationHosts: string[];
 | 
			
		||||
          };
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
@@ -9428,6 +9437,9 @@ export type operations = {
 | 
			
		||||
          urlPreviewRequireContentLength?: boolean;
 | 
			
		||||
          urlPreviewUserAgent?: string | null;
 | 
			
		||||
          urlPreviewSummaryProxyUrl?: string | null;
 | 
			
		||||
          /** @enum {string} */
 | 
			
		||||
          federation?: 'all' | 'none' | 'specified';
 | 
			
		||||
          federationHosts?: string[];
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
@@ -18545,8 +18557,8 @@ export type operations = {
 | 
			
		||||
          untilId?: string;
 | 
			
		||||
          /** @default true */
 | 
			
		||||
          markAsRead?: boolean;
 | 
			
		||||
          includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
 | 
			
		||||
          excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
 | 
			
		||||
          includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
 | 
			
		||||
          excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
@@ -18613,8 +18625,8 @@ export type operations = {
 | 
			
		||||
          untilId?: string;
 | 
			
		||||
          /** @default true */
 | 
			
		||||
          markAsRead?: boolean;
 | 
			
		||||
          includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
 | 
			
		||||
          excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
 | 
			
		||||
          includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
 | 
			
		||||
          excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
 
 | 
			
		||||
@@ -210,6 +210,12 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
 | 
			
		||||
						tag: `achievement:${data.body.achievement}`,
 | 
			
		||||
					}];
 | 
			
		||||
 | 
			
		||||
				case 'login':
 | 
			
		||||
					return [i18n.ts._notification.login, {
 | 
			
		||||
						badge: iconUrl('login-2'),
 | 
			
		||||
						data,
 | 
			
		||||
					}];
 | 
			
		||||
 | 
			
		||||
				case 'exportCompleted': {
 | 
			
		||||
					const entityName = {
 | 
			
		||||
						antenna: i18n.ts.antennas,
 | 
			
		||||
 
 | 
			
		||||
@@ -50,4 +50,5 @@ export type BadgeNames =
 | 
			
		||||
	| 'quote'
 | 
			
		||||
	| 'repeat'
 | 
			
		||||
	| 'user-plus'
 | 
			
		||||
	| 'users';
 | 
			
		||||
	| 'users'
 | 
			
		||||
	| 'login-2';
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										793
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										793
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user