Compare commits
	
		
			168 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					7c3873887d | ||
| 
						 | 
					247ea4cf12 | ||
| 
						 | 
					0b7af5c669 | ||
| 
						 | 
					2b62a4e2e5 | ||
| 
						 | 
					65bfa3c0d6 | ||
| 
						 | 
					84db15694d | ||
| 
						 | 
					746189ba37 | ||
| 
						 | 
					74e845b3ac | ||
| 
						 | 
					90fe70540e | ||
| 
						 | 
					f28af75191 | ||
| 
						 | 
					924bb2bc70 | ||
| 
						 | 
					19d60f3d51 | ||
| 
						 | 
					6903476868 | ||
| 
						 | 
					cf0dccc209 | ||
| 
						 | 
					cfd959129d | ||
| 
						 | 
					819287951c | ||
| 
						 | 
					e136193925 | ||
| 
						 | 
					8c631864d9 | ||
| 
						 | 
					d7d0f6ae2e | ||
| 
						 | 
					b83b3fb9d1 | ||
| 
						 | 
					dfce5bc0af | ||
| 
						 | 
					3487ddabea | ||
| 
						 | 
					2dbff75e7a | ||
| 
						 | 
					02465ded9f | ||
| 
						 | 
					ffcd387945 | ||
| 
						 | 
					4806346707 | ||
| 
						 | 
					31c3f6abf7 | ||
| 
						 | 
					83e47fdd60 | ||
| 
						 | 
					340ce7fa4c | ||
| 
						 | 
					ac86fee9b4 | ||
| 
						 | 
					6dfa283d7a | ||
| 
						 | 
					0cce8a4d21 | ||
| 
						 | 
					1c6d9ab2ef | ||
| 
						 | 
					6ca265e579 | ||
| 
						 | 
					c612c4bf18 | ||
| 
						 | 
					481a791a60 | ||
| 
						 | 
					cb516c2943 | ||
| 
						 | 
					c0abd6f0c0 | ||
| 
						 | 
					47695ed685 | ||
| 
						 | 
					4ca8020ef5 | ||
| 
						 | 
					bfac83d5b8 | ||
| 
						 | 
					4cd2e55fd3 | ||
| 
						 | 
					61c7e7bc48 | ||
| 
						 | 
					bef41718e2 | ||
| 
						 | 
					5b4b52bb97 | ||
| 
						 | 
					8901b6d774 | ||
| 
						 | 
					e3a24e9215 | ||
| 
						 | 
					a515c1f53e | ||
| 
						 | 
					2e22874dec | ||
| 
						 | 
					30f0b1c30d | ||
| 
						 | 
					600aea4dbb | ||
| 
						 | 
					f5d53d784d | ||
| 
						 | 
					1061e1f7ae | ||
| 
						 | 
					1d5fc04aa6 | ||
| 
						 | 
					d1cf0c7998 | ||
| 
						 | 
					84218abf2b | ||
| 
						 | 
					5bebdb2511 | ||
| 
						 | 
					9c8e9b4165 | ||
| 
						 | 
					7b786bfde3 | ||
| 
						 | 
					42a08642a4 | ||
| 
						 | 
					e88f7ca7b2 | ||
| 
						 | 
					c26ed1421b | ||
| 
						 | 
					ed2f94a3c1 | ||
| 
						 | 
					daba7fe87c | ||
| 
						 | 
					afc9caf7bf | ||
| 
						 | 
					67697a7aa6 | ||
| 
						 | 
					1623d9e70c | ||
| 
						 | 
					c304351335 | ||
| 
						 | 
					c1520763c6 | ||
| 
						 | 
					4853bc9414 | ||
| 
						 | 
					e7c865f8e3 | ||
| 
						 | 
					46cb377bc2 | ||
| 
						 | 
					373a5ba3e1 | ||
| 
						 | 
					3bedef67c8 | ||
| 
						 | 
					17ea19ada8 | ||
| 
						 | 
					1f5b2285fd | ||
| 
						 | 
					17f0001966 | ||
| 
						 | 
					04ba09a6af | ||
| 
						 | 
					70d2744319 | ||
| 
						 | 
					6b2f0929ec | ||
| 
						 | 
					f2629bd3f2 | ||
| 
						 | 
					9e6c29c3c0 | ||
| 
						 | 
					abda973094 | ||
| 
						 | 
					86b08dd5bd | ||
| 
						 | 
					617e331f0f | ||
| 
						 | 
					cc438a9372 | ||
| 
						 | 
					b0fb218bfd | ||
| 
						 | 
					fc85a607e6 | ||
| 
						 | 
					fb244c45e3 | ||
| 
						 | 
					c123784c54 | ||
| 
						 | 
					342a5276fc | ||
| 
						 | 
					51a32846ee | ||
| 
						 | 
					35865429a8 | ||
| 
						 | 
					aadd5b95b8 | ||
| 
						 | 
					f9f2ca51ac | ||
| 
						 | 
					1cb93a8c10 | ||
| 
						 | 
					7e5dbb2ba5 | ||
| 
						 | 
					2772e3d80e | ||
| 
						 | 
					223c578734 | ||
| 
						 | 
					d01315dee2 | ||
| 
						 | 
					7dafb4ce4c | ||
| 
						 | 
					9671db9b14 | ||
| 
						 | 
					bec559f67c | ||
| 
						 | 
					14053c1394 | ||
| 
						 | 
					55e4b1c828 | ||
| 
						 | 
					dda3421159 | ||
| 
						 | 
					45e7488e60 | ||
| 
						 | 
					30c7bd66b7 | ||
| 
						 | 
					af4f5bdac0 | ||
| 
						 | 
					3d1a8cc341 | ||
| 
						 | 
					0e52fb2544 | ||
| 
						 | 
					e6d6c0a17c | ||
| 
						 | 
					cfd2d47e00 | ||
| 
						 | 
					83301a879d | ||
| 
						 | 
					d7881ba129 | ||
| 
						 | 
					b9fef1edf7 | ||
| 
						 | 
					2c606f7b23 | ||
| 
						 | 
					03797607ed | ||
| 
						 | 
					254b7f500d | ||
| 
						 | 
					51edd51bf2 | ||
| 
						 | 
					0d403f4a3f | ||
| 
						 | 
					0fa134addd | ||
| 
						 | 
					7002270084 | ||
| 
						 | 
					1c5452d047 | ||
| 
						 | 
					f0d62c07bf | ||
| 
						 | 
					496ca55bba | ||
| 
						 | 
					79cfba226b | ||
| 
						 | 
					f69b60dffe | ||
| 
						 | 
					513385133f | ||
| 
						 | 
					6f1e2f6636 | ||
| 
						 | 
					8ae94c034d | ||
| 
						 | 
					cd9696f25e | ||
| 
						 | 
					d62a6bab41 | ||
| 
						 | 
					20df002746 | ||
| 
						 | 
					fa6b01546e | ||
| 
						 | 
					91b37a6e52 | ||
| 
						 | 
					d8171d7c8b | ||
| 
						 | 
					fa96e2daf1 | ||
| 
						 | 
					87708c3b84 | ||
| 
						 | 
					6319023cc9 | ||
| 
						 | 
					efad9d1b60 | ||
| 
						 | 
					a1dea657fa | ||
| 
						 | 
					6b1b75717b | ||
| 
						 | 
					efe08e0bd3 | ||
| 
						 | 
					62892c4894 | ||
| 
						 | 
					0c2a62da11 | ||
| 
						 | 
					5bc9e9aadd | ||
| 
						 | 
					112c33d35b | ||
| 
						 | 
					864da3030f | ||
| 
						 | 
					f2e719b361 | ||
| 
						 | 
					6aab515389 | ||
| 
						 | 
					819b535ab0 | ||
| 
						 | 
					60e95ac2ac | ||
| 
						 | 
					9b94ddff0a | ||
| 
						 | 
					174f8022eb | ||
| 
						 | 
					ddc3c5ba68 | ||
| 
						 | 
					a7e6b766be | ||
| 
						 | 
					befc35a3ac | ||
| 
						 | 
					2e9bbf389e | ||
| 
						 | 
					80b5fda292 | ||
| 
						 | 
					c48cbd95f6 | ||
| 
						 | 
					931bdc6aac | ||
| 
						 | 
					7f81506c8b | ||
| 
						 | 
					b4b9e76c8d | ||
| 
						 | 
					e5a3dcf868 | ||
| 
						 | 
					825648535c | ||
| 
						 | 
					5cbc908ba3 | ||
| 
						 | 
					895cf53ee1 | 
@@ -186,6 +186,7 @@ common:
 | 
			
		||||
    stack-left: "左に重ねる"
 | 
			
		||||
    pop-right: "右に出す"
 | 
			
		||||
  dev: "アプリの作成に失敗しました。再度お試しください。"
 | 
			
		||||
  ai-chan-kawaii: "藍ちゃかわいい"
 | 
			
		||||
auth/views/form.vue:
 | 
			
		||||
  share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
 | 
			
		||||
  permission-ask: "このアプリは次の権限を要求しています:"
 | 
			
		||||
@@ -874,7 +875,7 @@ common/views/components/mute-and-block.vue:
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  no-muted-users: "ミュートしているユーザーはいません"
 | 
			
		||||
  no-blocked-users: "ブロックしているユーザーはいません"
 | 
			
		||||
desktop/views/components/settings.password.vue:
 | 
			
		||||
common/views/components/password-settings.vue:
 | 
			
		||||
  reset: "パスワードを変更する"
 | 
			
		||||
  enter-current-password: "現在のパスワードを入力してください"
 | 
			
		||||
  enter-new-password: "新しいパスワードを入力してください"
 | 
			
		||||
@@ -946,41 +947,86 @@ desktop/views/components/users-list-item.vue:
 | 
			
		||||
desktop/views/components/window.vue:
 | 
			
		||||
  popout: "ポップアウト"
 | 
			
		||||
  close: "閉じる"
 | 
			
		||||
desktop/views/pages/admin/admin.vue:
 | 
			
		||||
admin/views/index.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  instance: "インスタンス"
 | 
			
		||||
  emoji: "カスタム絵文字"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  update: "更新"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
desktop/views/pages/admin/admin.dashboard.vue:
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  all-users: "全てのユーザー"
 | 
			
		||||
  original-users: "このインスタンスのユーザー"
 | 
			
		||||
  all-notes: "全ての投稿"
 | 
			
		||||
  original-notes: "このインスタンスの投稿"
 | 
			
		||||
  accounts: "アカウント"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
  invite: "招待"
 | 
			
		||||
  banner-url: "Banner URL"
 | 
			
		||||
  disableRegistration: "Disable new user registration"
 | 
			
		||||
  disableLocalTimeline: "Disable the local timeline"
 | 
			
		||||
desktop/views/pages/admin/admin.suspend-user.vue:
 | 
			
		||||
admin/views/charts.vue:
 | 
			
		||||
  title: "チャート"
 | 
			
		||||
  per-day: "1日ごと"
 | 
			
		||||
  per-hour: "1時間ごと"
 | 
			
		||||
  federation: "フェデレーション"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  network: "ネットワーク"
 | 
			
		||||
  charts:
 | 
			
		||||
    federation-instances: "インスタンスの増減"
 | 
			
		||||
    federation-instances-total: "インスタンスの積算"
 | 
			
		||||
    notes: "投稿の増減 (統合)"
 | 
			
		||||
    local-notes: "投稿の増減 (ローカル)"
 | 
			
		||||
    remote-notes: "投稿の増減 (リモート)"
 | 
			
		||||
    notes-total: "投稿の積算"
 | 
			
		||||
    users: "ユーザーの増減"
 | 
			
		||||
    users-total: "ユーザーの積算"
 | 
			
		||||
    drive: "ドライブ使用量の増減"
 | 
			
		||||
    drive-total: "ドライブ使用量の積算"
 | 
			
		||||
    drive-files: "ドライブのファイル数の増減"
 | 
			
		||||
    drive-files-total: "ドライブのファイル数の積算"
 | 
			
		||||
    network-requests: "リクエスト"
 | 
			
		||||
    network-time: "応答時間"
 | 
			
		||||
    network-usage: "通信量"
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  suspend-user: "ユーザーの凍結"
 | 
			
		||||
  suspend: "凍結"
 | 
			
		||||
  suspended: "凍結しました"
 | 
			
		||||
desktop/views/pages/admin/admin.unsuspend-user.vue:
 | 
			
		||||
  unsuspend-user: "ユーザーの凍結の解除"
 | 
			
		||||
  unsuspend: "凍結の解除"
 | 
			
		||||
  unsuspended: "凍結を解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.verify-user.vue:
 | 
			
		||||
  verify-user: "ユーザーの公式アカウント設定"
 | 
			
		||||
  verify: "公式アカウントにする"
 | 
			
		||||
  verified: "公式アカウントにしました"
 | 
			
		||||
desktop/views/pages/admin/admin.unverify-user.vue:
 | 
			
		||||
  unverify-user: "ユーザーの公式アカウント解除"
 | 
			
		||||
  unverify: "公式アカウントを解除する"
 | 
			
		||||
  unverified: "公式アカウントを解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.announcements.vue:
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "絵文字の登録"
 | 
			
		||||
    name: "絵文字名"
 | 
			
		||||
    name-desc: "a~z 0~9 _ の文字が使えます。"
 | 
			
		||||
    aliases: "エイリアス"
 | 
			
		||||
    aliases-desc: "スペースで区切って複数設定できます。"
 | 
			
		||||
    url: "絵文字画像URL"
 | 
			
		||||
    add: "追加"
 | 
			
		||||
  emojis:
 | 
			
		||||
    title: "絵文字一覧"
 | 
			
		||||
    update: "更新"
 | 
			
		||||
    remove: "削除"
 | 
			
		||||
admin/views/announcements.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
desktop/views/pages/admin/admin.hashtags.vue:
 | 
			
		||||
  save: "保存"
 | 
			
		||||
  remove: "削除"
 | 
			
		||||
  add: "追加"
 | 
			
		||||
  title: "タイトル"
 | 
			
		||||
  text: "内容"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
desktop/views/pages/deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-only: "メディア投稿のみ"
 | 
			
		||||
@@ -1328,6 +1374,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
  mark-as-read-all-unread-notes: "すべての投稿を既読にする"
 | 
			
		||||
  password: "パスワード"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
 
 | 
			
		||||
@@ -186,6 +186,7 @@ common:
 | 
			
		||||
    stack-left: "Nach links schichten"
 | 
			
		||||
    pop-right: "Rechts andocken"
 | 
			
		||||
  dev: "Fehler beim Erstellen der Applikation. Bitte versuche es erneut."
 | 
			
		||||
  ai-chan-kawaii: "藍ちゃかわいい"
 | 
			
		||||
auth/views/form.vue:
 | 
			
		||||
  share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
 | 
			
		||||
  permission-ask: "このアプリは次の権限を要求しています:"
 | 
			
		||||
@@ -874,13 +875,13 @@ common/views/components/mute-and-block.vue:
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  no-muted-users: "ミュートしているユーザーはいません"
 | 
			
		||||
  no-blocked-users: "ブロックしているユーザーはいません"
 | 
			
		||||
desktop/views/components/settings.password.vue:
 | 
			
		||||
  reset: "Passwort ändern"
 | 
			
		||||
  enter-current-password: "Derzeitiges Passwort eingeben"
 | 
			
		||||
  enter-new-password: "Neues Passwort eingeben"
 | 
			
		||||
  enter-new-password-again: "Neues Passwort erneut eingeben"
 | 
			
		||||
  not-match: "Passwörter stimmen nicht überein."
 | 
			
		||||
  changed: "Passwort geändert"
 | 
			
		||||
common/views/components/password-settings.vue:
 | 
			
		||||
  reset: "パスワードを変更する"
 | 
			
		||||
  enter-current-password: "現在のパスワードを入力してください"
 | 
			
		||||
  enter-new-password: "新しいパスワードを入力してください"
 | 
			
		||||
  enter-new-password-again: "もう一度新しいパスワードを入力してください"
 | 
			
		||||
  not-match: "新しいパスワードが一致しません"
 | 
			
		||||
  changed: "パスワードを変更しました"
 | 
			
		||||
desktop/views/components/sub-note-content.vue:
 | 
			
		||||
  private: "この投稿は非公開です"
 | 
			
		||||
  deleted: "この投稿は削除されました"
 | 
			
		||||
@@ -946,41 +947,86 @@ desktop/views/components/users-list-item.vue:
 | 
			
		||||
desktop/views/components/window.vue:
 | 
			
		||||
  popout: "ポップアウト"
 | 
			
		||||
  close: "閉じる"
 | 
			
		||||
desktop/views/pages/admin/admin.vue:
 | 
			
		||||
admin/views/index.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  instance: "インスタンス"
 | 
			
		||||
  emoji: "カスタム絵文字"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  update: "更新"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
desktop/views/pages/admin/admin.dashboard.vue:
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  all-users: "全てのユーザー"
 | 
			
		||||
  original-users: "このインスタンスのユーザー"
 | 
			
		||||
  all-notes: "全ての投稿"
 | 
			
		||||
  original-notes: "このインスタンスの投稿"
 | 
			
		||||
  accounts: "アカウント"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
  invite: "招待"
 | 
			
		||||
  banner-url: "Banner URL"
 | 
			
		||||
  disableRegistration: "Disable new user registration"
 | 
			
		||||
  disableLocalTimeline: "Disable the local timeline"
 | 
			
		||||
desktop/views/pages/admin/admin.suspend-user.vue:
 | 
			
		||||
admin/views/charts.vue:
 | 
			
		||||
  title: "チャート"
 | 
			
		||||
  per-day: "1日ごと"
 | 
			
		||||
  per-hour: "1時間ごと"
 | 
			
		||||
  federation: "フェデレーション"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  network: "ネットワーク"
 | 
			
		||||
  charts:
 | 
			
		||||
    federation-instances: "インスタンスの増減"
 | 
			
		||||
    federation-instances-total: "インスタンスの積算"
 | 
			
		||||
    notes: "投稿の増減 (統合)"
 | 
			
		||||
    local-notes: "投稿の増減 (ローカル)"
 | 
			
		||||
    remote-notes: "投稿の増減 (リモート)"
 | 
			
		||||
    notes-total: "投稿の積算"
 | 
			
		||||
    users: "ユーザーの増減"
 | 
			
		||||
    users-total: "ユーザーの積算"
 | 
			
		||||
    drive: "ドライブ使用量の増減"
 | 
			
		||||
    drive-total: "ドライブ使用量の積算"
 | 
			
		||||
    drive-files: "ドライブのファイル数の増減"
 | 
			
		||||
    drive-files-total: "ドライブのファイル数の積算"
 | 
			
		||||
    network-requests: "リクエスト"
 | 
			
		||||
    network-time: "応答時間"
 | 
			
		||||
    network-usage: "通信量"
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  suspend-user: "ユーザーの凍結"
 | 
			
		||||
  suspend: "凍結"
 | 
			
		||||
  suspended: "凍結しました"
 | 
			
		||||
desktop/views/pages/admin/admin.unsuspend-user.vue:
 | 
			
		||||
  unsuspend-user: "ユーザーの凍結の解除"
 | 
			
		||||
  unsuspend: "凍結の解除"
 | 
			
		||||
  unsuspended: "凍結を解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.verify-user.vue:
 | 
			
		||||
  verify-user: "ユーザーの公式アカウント設定"
 | 
			
		||||
  verify: "公式アカウントにする"
 | 
			
		||||
  verified: "公式アカウントにしました"
 | 
			
		||||
desktop/views/pages/admin/admin.unverify-user.vue:
 | 
			
		||||
  unverify-user: "ユーザーの公式アカウント解除"
 | 
			
		||||
  unverify: "公式アカウントを解除する"
 | 
			
		||||
  unverified: "公式アカウントを解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.announcements.vue:
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "絵文字の登録"
 | 
			
		||||
    name: "絵文字名"
 | 
			
		||||
    name-desc: "a~z 0~9 _ の文字が使えます。"
 | 
			
		||||
    aliases: "エイリアス"
 | 
			
		||||
    aliases-desc: "スペースで区切って複数設定できます。"
 | 
			
		||||
    url: "絵文字画像URL"
 | 
			
		||||
    add: "追加"
 | 
			
		||||
  emojis:
 | 
			
		||||
    title: "絵文字一覧"
 | 
			
		||||
    update: "更新"
 | 
			
		||||
    remove: "削除"
 | 
			
		||||
admin/views/announcements.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
desktop/views/pages/admin/admin.hashtags.vue:
 | 
			
		||||
  save: "保存"
 | 
			
		||||
  remove: "削除"
 | 
			
		||||
  add: "追加"
 | 
			
		||||
  title: "タイトル"
 | 
			
		||||
  text: "内容"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
desktop/views/pages/deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-only: "メディア投稿のみ"
 | 
			
		||||
@@ -1328,6 +1374,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
  mark-as-read-all-unread-notes: "すべての投稿を既読にする"
 | 
			
		||||
  password: "パスワード"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
 
 | 
			
		||||
@@ -186,6 +186,7 @@ common:
 | 
			
		||||
    stack-left: "Stack to the left"
 | 
			
		||||
    pop-right: "Dock on the right"
 | 
			
		||||
  dev: "Failed to create the application. Please try again."
 | 
			
		||||
  ai-chan-kawaii: "Ai-chan kawaii!"
 | 
			
		||||
auth/views/form.vue:
 | 
			
		||||
  share-access: "Would you <b>allow</b> <i>{{ app.name }}</i> to access your account?"
 | 
			
		||||
  permission-ask: "This application requires the following permissions:"
 | 
			
		||||
@@ -249,7 +250,7 @@ common/views/components/games/reversi/reversi.room.vue:
 | 
			
		||||
  this-game-is-started-soon: "The game will begin in seconds"
 | 
			
		||||
  waiting-for-other: "Waiting for the opponent"
 | 
			
		||||
  waiting-for-me: "Waiting for the your preparation"
 | 
			
		||||
  waiting-for-both: "Prepareing"
 | 
			
		||||
  waiting-for-both: "Preparing"
 | 
			
		||||
  cancel: "Cancel"
 | 
			
		||||
  ready: "Ready"
 | 
			
		||||
  cancel-ready: "Cancel \"Ready\""
 | 
			
		||||
@@ -874,13 +875,13 @@ common/views/components/mute-and-block.vue:
 | 
			
		||||
  block: "Blocking"
 | 
			
		||||
  no-muted-users: "No muted users"
 | 
			
		||||
  no-blocked-users: "No blocked users"
 | 
			
		||||
desktop/views/components/settings.password.vue:
 | 
			
		||||
common/views/components/password-settings.vue:
 | 
			
		||||
  reset: "Change password"
 | 
			
		||||
  enter-current-password: "Enter the current password"
 | 
			
		||||
  enter-new-password: "Enter the new password"
 | 
			
		||||
  enter-new-password-again: "Enter new password again"
 | 
			
		||||
  enter-new-password-again: "Enter the new password again"
 | 
			
		||||
  not-match: "The new passwords do not match"
 | 
			
		||||
  changed: "Password updated"
 | 
			
		||||
  changed: "Password changed"
 | 
			
		||||
desktop/views/components/sub-note-content.vue:
 | 
			
		||||
  private: "This post is private"
 | 
			
		||||
  deleted: "This post has been deleted"
 | 
			
		||||
@@ -946,41 +947,86 @@ desktop/views/components/users-list-item.vue:
 | 
			
		||||
desktop/views/components/window.vue:
 | 
			
		||||
  popout: "Pop-out"
 | 
			
		||||
  close: "Close"
 | 
			
		||||
desktop/views/pages/admin/admin.vue:
 | 
			
		||||
admin/views/index.vue:
 | 
			
		||||
  dashboard: "Dashboard"
 | 
			
		||||
  instance: "Instance"
 | 
			
		||||
  emoji: "Emoji"
 | 
			
		||||
  users: "Users"
 | 
			
		||||
  update: "Updates"
 | 
			
		||||
  update: "Update"
 | 
			
		||||
  announcements: "Announcements"
 | 
			
		||||
  hashtags: "Hashtags"
 | 
			
		||||
desktop/views/pages/admin/admin.dashboard.vue:
 | 
			
		||||
  back-to-misskey: "Back to Misskey"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "Dashboard"
 | 
			
		||||
  all-users: "All Users"
 | 
			
		||||
  original-users: "Users on this instance"
 | 
			
		||||
  all-notes: "All the posts"
 | 
			
		||||
  original-notes: "Posts on this instance"
 | 
			
		||||
  accounts: "Accounts"
 | 
			
		||||
  notes: "Notes"
 | 
			
		||||
  drive: "Drive"
 | 
			
		||||
  instances: "Instances"
 | 
			
		||||
  this-instance: "This instance"
 | 
			
		||||
  federated: "Federated"
 | 
			
		||||
  invite: "Invite"
 | 
			
		||||
  banner-url: "Banner URL"
 | 
			
		||||
  disableRegistration: "Disable new user registration"
 | 
			
		||||
  disableLocalTimeline: "Disable the local timeline"
 | 
			
		||||
desktop/views/pages/admin/admin.suspend-user.vue:
 | 
			
		||||
admin/views/charts.vue:
 | 
			
		||||
  title: "Chart"
 | 
			
		||||
  per-day: "per Day"
 | 
			
		||||
  per-hour: "per Hour"
 | 
			
		||||
  federation: "Federation"
 | 
			
		||||
  notes: "Posts"
 | 
			
		||||
  users: "Users"
 | 
			
		||||
  drive: "Drive"
 | 
			
		||||
  network: "Network"
 | 
			
		||||
  charts:
 | 
			
		||||
    federation-instances: "The number of instances: increase/decrease"
 | 
			
		||||
    federation-instances-total: "Total number of instances"
 | 
			
		||||
    notes: "The number of posts: increase/decrease (Combined)"
 | 
			
		||||
    local-notes: "The number of posts: increase/decrease (Local)"
 | 
			
		||||
    remote-notes: "The number of posts: increase/decrease (Remote)"
 | 
			
		||||
    notes-total: "Total posts"
 | 
			
		||||
    users: "The number of users: increase/decrease"
 | 
			
		||||
    users-total: "Total users"
 | 
			
		||||
    drive: "Capacity used as the storage: increase/decrease"
 | 
			
		||||
    drive-total: "Total usage of Drive"
 | 
			
		||||
    drive-files: "The number of files on the storage: increase/decrease"
 | 
			
		||||
    drive-files-total: "Total number of files on Drive"
 | 
			
		||||
    network-requests: "Requests"
 | 
			
		||||
    network-time: "Response time"
 | 
			
		||||
    network-usage: "Traffic"
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  suspend-user: "Suspend a user"
 | 
			
		||||
  suspend: "Suspend"
 | 
			
		||||
  suspended: "Successfully suspended."
 | 
			
		||||
desktop/views/pages/admin/admin.unsuspend-user.vue:
 | 
			
		||||
  unsuspend-user: "Unsuspend users"
 | 
			
		||||
  unsuspend: "Unsuspend"
 | 
			
		||||
  unsuspended: "The user has successfully unsuspended."
 | 
			
		||||
desktop/views/pages/admin/admin.verify-user.vue:
 | 
			
		||||
  verify-user: "User account verification settings"
 | 
			
		||||
  verify: "Verify account"
 | 
			
		||||
  verified: "The account is now being verified"
 | 
			
		||||
desktop/views/pages/admin/admin.unverify-user.vue:
 | 
			
		||||
  unverify-user: "User account unverification settings"
 | 
			
		||||
  unverify: "Unverify account"
 | 
			
		||||
  unverified: "The account is now being unverified"
 | 
			
		||||
desktop/views/pages/admin/admin.announcements.vue:
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "Add emoji"
 | 
			
		||||
    name: "Emoji name"
 | 
			
		||||
    name-desc: "You can use the characters a~z 0~9 _"
 | 
			
		||||
    aliases: "Aliases"
 | 
			
		||||
    aliases-desc: "You can add more than one, separated by spaces."
 | 
			
		||||
    url: "Image URL"
 | 
			
		||||
    add: "Add"
 | 
			
		||||
  emojis:
 | 
			
		||||
    title: "Emojis"
 | 
			
		||||
    update: "Update"
 | 
			
		||||
    remove: "Remove"
 | 
			
		||||
admin/views/announcements.vue:
 | 
			
		||||
  announcements: "Announcements"
 | 
			
		||||
desktop/views/pages/admin/admin.hashtags.vue:
 | 
			
		||||
  save: "Save"
 | 
			
		||||
  remove: "Remove"
 | 
			
		||||
  add: "Add"
 | 
			
		||||
  title: "Title"
 | 
			
		||||
  text: "Content"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
desktop/views/pages/deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-only: "Only media posts"
 | 
			
		||||
@@ -1328,6 +1374,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  sound: "Sounds"
 | 
			
		||||
  enable-sounds: "Enable sounds"
 | 
			
		||||
  mark-as-read-all-unread-notes: "Mark all posts as read"
 | 
			
		||||
  password: "Password"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "Follows you"
 | 
			
		||||
  following: "Following"
 | 
			
		||||
 
 | 
			
		||||
@@ -186,6 +186,7 @@ common:
 | 
			
		||||
    stack-left: "A la izqda."
 | 
			
		||||
    pop-right: "A la dcha."
 | 
			
		||||
  dev: "アプリの作成に失敗しました。再度お試しください。"
 | 
			
		||||
  ai-chan-kawaii: "藍ちゃかわいい"
 | 
			
		||||
auth/views/form.vue:
 | 
			
		||||
  share-access: "¿Deseas <b>permitir</b> a <i>{{ app.name }}</i> acceder a tu cuenta?"
 | 
			
		||||
  permission-ask: "La aplicación requiere los siguientes permisos:"
 | 
			
		||||
@@ -874,13 +875,13 @@ common/views/components/mute-and-block.vue:
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  no-muted-users: "ミュートしているユーザーはいません"
 | 
			
		||||
  no-blocked-users: "ブロックしているユーザーはいません"
 | 
			
		||||
desktop/views/components/settings.password.vue:
 | 
			
		||||
  reset: "Cambiar contraseña"
 | 
			
		||||
  enter-current-password: "Ingresar contraseña actual"
 | 
			
		||||
  enter-new-password: "Ingresar nueva contraseña"
 | 
			
		||||
  enter-new-password-again: "Ingresar nueva contraseña de nuevo"
 | 
			
		||||
  not-match: "Las nuevas contraseñas no se corresponden consigo mismas"
 | 
			
		||||
  changed: "Contraseña actualizada"
 | 
			
		||||
common/views/components/password-settings.vue:
 | 
			
		||||
  reset: "パスワードを変更する"
 | 
			
		||||
  enter-current-password: "現在のパスワードを入力してください"
 | 
			
		||||
  enter-new-password: "新しいパスワードを入力してください"
 | 
			
		||||
  enter-new-password-again: "もう一度新しいパスワードを入力してください"
 | 
			
		||||
  not-match: "新しいパスワードが一致しません"
 | 
			
		||||
  changed: "パスワードを変更しました"
 | 
			
		||||
desktop/views/components/sub-note-content.vue:
 | 
			
		||||
  private: "この投稿は非公開です"
 | 
			
		||||
  deleted: "この投稿は削除されました"
 | 
			
		||||
@@ -946,41 +947,86 @@ desktop/views/components/users-list-item.vue:
 | 
			
		||||
desktop/views/components/window.vue:
 | 
			
		||||
  popout: "ポップアウト"
 | 
			
		||||
  close: "閉じる"
 | 
			
		||||
desktop/views/pages/admin/admin.vue:
 | 
			
		||||
admin/views/index.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  instance: "インスタンス"
 | 
			
		||||
  emoji: "カスタム絵文字"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  update: "更新"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
desktop/views/pages/admin/admin.dashboard.vue:
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  all-users: "全てのユーザー"
 | 
			
		||||
  original-users: "このインスタンスのユーザー"
 | 
			
		||||
  all-notes: "全ての投稿"
 | 
			
		||||
  original-notes: "このインスタンスの投稿"
 | 
			
		||||
  accounts: "アカウント"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
  invite: "招待"
 | 
			
		||||
  banner-url: "Banner URL"
 | 
			
		||||
  disableRegistration: "Disable new user registration"
 | 
			
		||||
  disableLocalTimeline: "Disable the local timeline"
 | 
			
		||||
desktop/views/pages/admin/admin.suspend-user.vue:
 | 
			
		||||
admin/views/charts.vue:
 | 
			
		||||
  title: "チャート"
 | 
			
		||||
  per-day: "1日ごと"
 | 
			
		||||
  per-hour: "1時間ごと"
 | 
			
		||||
  federation: "フェデレーション"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  network: "ネットワーク"
 | 
			
		||||
  charts:
 | 
			
		||||
    federation-instances: "インスタンスの増減"
 | 
			
		||||
    federation-instances-total: "インスタンスの積算"
 | 
			
		||||
    notes: "投稿の増減 (統合)"
 | 
			
		||||
    local-notes: "投稿の増減 (ローカル)"
 | 
			
		||||
    remote-notes: "投稿の増減 (リモート)"
 | 
			
		||||
    notes-total: "投稿の積算"
 | 
			
		||||
    users: "ユーザーの増減"
 | 
			
		||||
    users-total: "ユーザーの積算"
 | 
			
		||||
    drive: "ドライブ使用量の増減"
 | 
			
		||||
    drive-total: "ドライブ使用量の積算"
 | 
			
		||||
    drive-files: "ドライブのファイル数の増減"
 | 
			
		||||
    drive-files-total: "ドライブのファイル数の積算"
 | 
			
		||||
    network-requests: "リクエスト"
 | 
			
		||||
    network-time: "応答時間"
 | 
			
		||||
    network-usage: "通信量"
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  suspend-user: "ユーザーの凍結"
 | 
			
		||||
  suspend: "凍結"
 | 
			
		||||
  suspended: "凍結しました"
 | 
			
		||||
desktop/views/pages/admin/admin.unsuspend-user.vue:
 | 
			
		||||
  unsuspend-user: "ユーザーの凍結の解除"
 | 
			
		||||
  unsuspend: "凍結の解除"
 | 
			
		||||
  unsuspended: "凍結を解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.verify-user.vue:
 | 
			
		||||
  verify-user: "ユーザーの公式アカウント設定"
 | 
			
		||||
  verify: "公式アカウントにする"
 | 
			
		||||
  verified: "公式アカウントにしました"
 | 
			
		||||
desktop/views/pages/admin/admin.unverify-user.vue:
 | 
			
		||||
  unverify-user: "ユーザーの公式アカウント解除"
 | 
			
		||||
  unverify: "公式アカウントを解除する"
 | 
			
		||||
  unverified: "公式アカウントを解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.announcements.vue:
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "絵文字の登録"
 | 
			
		||||
    name: "絵文字名"
 | 
			
		||||
    name-desc: "a~z 0~9 _ の文字が使えます。"
 | 
			
		||||
    aliases: "エイリアス"
 | 
			
		||||
    aliases-desc: "スペースで区切って複数設定できます。"
 | 
			
		||||
    url: "絵文字画像URL"
 | 
			
		||||
    add: "追加"
 | 
			
		||||
  emojis:
 | 
			
		||||
    title: "絵文字一覧"
 | 
			
		||||
    update: "更新"
 | 
			
		||||
    remove: "削除"
 | 
			
		||||
admin/views/announcements.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
desktop/views/pages/admin/admin.hashtags.vue:
 | 
			
		||||
  save: "保存"
 | 
			
		||||
  remove: "削除"
 | 
			
		||||
  add: "追加"
 | 
			
		||||
  title: "タイトル"
 | 
			
		||||
  text: "内容"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
desktop/views/pages/deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-only: "メディア投稿のみ"
 | 
			
		||||
@@ -1328,6 +1374,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
  mark-as-read-all-unread-notes: "すべての投稿を既読にする"
 | 
			
		||||
  password: "パスワード"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
 
 | 
			
		||||
@@ -186,6 +186,7 @@ common:
 | 
			
		||||
    stack-left: "Vers la gauche"
 | 
			
		||||
    pop-right: "Vers la droite"
 | 
			
		||||
  dev: "Échec lors de la création de l’application. Veuillez réessayer."
 | 
			
		||||
  ai-chan-kawaii: "藍ちゃかわいい"
 | 
			
		||||
auth/views/form.vue:
 | 
			
		||||
  share-access: "Désirez-vous <b>autoriser</b> <i>{{ app.name }}</i> à avoir accès à votre compte ?"
 | 
			
		||||
  permission-ask: "Cette application nécessite les autorisations suivantes :"
 | 
			
		||||
@@ -444,7 +445,7 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  is-cat: "Ce compte est un Chat"
 | 
			
		||||
  is-bot: "Ce compte est un Bot"
 | 
			
		||||
  is-locked: "Demandes d’abonnements requièrent l’approbation"
 | 
			
		||||
  careful-bot: "Botからのフォローだけ承認制にする"
 | 
			
		||||
  careful-bot: "Les demandes d’abonnements venant de Bots requièrent l’approbation"
 | 
			
		||||
  advanced: "Avancé"
 | 
			
		||||
  privacy: "Vie privée"
 | 
			
		||||
  save: "Mettre à jour le profil"
 | 
			
		||||
@@ -501,7 +502,7 @@ common/views/widgets/tips.vue:
 | 
			
		||||
  tips-line14: "ホームのカスタマイズ中、ウィジェットを右クリックしてデザインを変更できます"
 | 
			
		||||
  tips-line17: "Vous pouvez mettre un texte en surbrillance en le mettant entre ** **"
 | 
			
		||||
  tips-line19: "Plusieurs fenêtres peuvent être détachées en dehors du navigateur."
 | 
			
		||||
  tips-line20: "カレンダーウィジェットのパーセンテージは、経過の割合を示しています"
 | 
			
		||||
  tips-line20: "Pourcentage sur le widget calendrier qui indique le pourcentage de temps passé"
 | 
			
		||||
  tips-line21: "Vous pouvez aussi utiliser l'API pour développer des Bots."
 | 
			
		||||
  tips-line23: "Mayu est mignone avec ses sourcils."
 | 
			
		||||
  tips-line24: "Misskey a vu le jour en 2014"
 | 
			
		||||
@@ -548,7 +549,7 @@ desktop/views/components/charts.vue:
 | 
			
		||||
  drive: "Drive"
 | 
			
		||||
  network: "Réseau"
 | 
			
		||||
  charts:
 | 
			
		||||
    federation-instances: "インスタンスの増減"
 | 
			
		||||
    federation-instances: "Nombre d’instances : augmentation/diminution"
 | 
			
		||||
    federation-instances-total: "Nombre total d’instances"
 | 
			
		||||
    notes: "投稿の増減 (統合)"
 | 
			
		||||
    local-notes: "投稿の増減 (ローカル)"
 | 
			
		||||
@@ -781,7 +782,7 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  timeline: "Chronologie"
 | 
			
		||||
  show-my-renotes: "Afficher mes republications dans le fil"
 | 
			
		||||
  show-renoted-my-notes: "Afficher mes republications dans les fils"
 | 
			
		||||
  show-local-renotes: "ローカルの投稿のRenoteをタイムラインに表示する"
 | 
			
		||||
  show-local-renotes: "Afficher les partages locaux sur les fils"
 | 
			
		||||
  show-maps: "Afficher la carte"
 | 
			
		||||
  deck-column-align: "デッキのカラムの位置"
 | 
			
		||||
  deck-column-align-center: "Centrer"
 | 
			
		||||
@@ -865,21 +866,21 @@ common/views/components/api-settings.vue:
 | 
			
		||||
desktop/views/components/settings.apps.vue:
 | 
			
		||||
  no-apps: "Aucune application autorisée"
 | 
			
		||||
common/views/components/drive-settings.vue:
 | 
			
		||||
  max: "容量"
 | 
			
		||||
  max: "Maximale"
 | 
			
		||||
  in-use: "utilisé"
 | 
			
		||||
  stats: "Statistiques"
 | 
			
		||||
common/views/components/mute-and-block.vue:
 | 
			
		||||
  mute-and-block: "ミュートとブロック"
 | 
			
		||||
  mute: "ミュート"
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  no-muted-users: "ミュートしているユーザーはいません"
 | 
			
		||||
  no-blocked-users: "ブロックしているユーザーはいません"
 | 
			
		||||
desktop/views/components/settings.password.vue:
 | 
			
		||||
  reset: "Changer votre mot de passe"
 | 
			
		||||
  mute-and-block: "Silencer / Bloquer"
 | 
			
		||||
  mute: "Mettre en sourdine"
 | 
			
		||||
  block: "En cours blocage"
 | 
			
		||||
  no-muted-users: "Aucun utilisateur·rice n’est mis·e en sourdine"
 | 
			
		||||
  no-blocked-users: "Aucun utilisateur·rice n’est bloqué·e"
 | 
			
		||||
common/views/components/password-settings.vue:
 | 
			
		||||
  reset: "Modifier le mot de passe"
 | 
			
		||||
  enter-current-password: "Entrez votre mot de passe actuel"
 | 
			
		||||
  enter-new-password: "Entrez votre nouveau mot de passe"
 | 
			
		||||
  enter-new-password: "Saisissez le nouveau mot de passe"
 | 
			
		||||
  enter-new-password-again: "Entrez à nouveau le nouveau mot de passe"
 | 
			
		||||
  not-match: "Le nouveau mot de passe ne correspond pas."
 | 
			
		||||
  not-match: "Les nouveaux mots de passe ne sont pas identiques"
 | 
			
		||||
  changed: "Mot de passe modifié avec succès"
 | 
			
		||||
desktop/views/components/sub-note-content.vue:
 | 
			
		||||
  private: "cette publication est privée"
 | 
			
		||||
@@ -946,41 +947,86 @@ desktop/views/components/users-list-item.vue:
 | 
			
		||||
desktop/views/components/window.vue:
 | 
			
		||||
  popout: "ポップアウト"
 | 
			
		||||
  close: "Fermer"
 | 
			
		||||
desktop/views/pages/admin/admin.vue:
 | 
			
		||||
admin/views/index.vue:
 | 
			
		||||
  dashboard: "Tableau de bord"
 | 
			
		||||
  instance: "Instance"
 | 
			
		||||
  emoji: "Emoji"
 | 
			
		||||
  users: "Utilisateur·rice·s"
 | 
			
		||||
  update: "Mises à jour"
 | 
			
		||||
  update: "Mise à jour"
 | 
			
		||||
  announcements: "Annonces"
 | 
			
		||||
  hashtags: "Hashtags"
 | 
			
		||||
desktop/views/pages/admin/admin.dashboard.vue:
 | 
			
		||||
  back-to-misskey: "Retour vers Misskey"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "Tableau de bord"
 | 
			
		||||
  all-users: "Toutes les utilisateurrices"
 | 
			
		||||
  original-users: "Utilisateur·rice·s sur cette instance"
 | 
			
		||||
  all-notes: "Toutes les publications"
 | 
			
		||||
  original-notes: "Publications sur cette instance"
 | 
			
		||||
  invite: "Invitation"
 | 
			
		||||
  accounts: "Comptes"
 | 
			
		||||
  notes: "Notes"
 | 
			
		||||
  drive: "Lecteur"
 | 
			
		||||
  instances: "Instances"
 | 
			
		||||
  this-instance: "Cette instance"
 | 
			
		||||
  federated: "Fédérées"
 | 
			
		||||
  invite: "Inviter"
 | 
			
		||||
  banner-url: "URL de la bannière"
 | 
			
		||||
  disableRegistration: "Désactiver l’enregistrement de nouveaux utilisateurs·rices"
 | 
			
		||||
  disableRegistration: "Désactiver l’enregistrement de nouveaux utilisateur·rice·s"
 | 
			
		||||
  disableLocalTimeline: "Désactiver le fil local"
 | 
			
		||||
desktop/views/pages/admin/admin.suspend-user.vue:
 | 
			
		||||
admin/views/charts.vue:
 | 
			
		||||
  title: "Graph"
 | 
			
		||||
  per-day: "par jour"
 | 
			
		||||
  per-hour: "par heure"
 | 
			
		||||
  federation: "Fédération"
 | 
			
		||||
  notes: "Publications"
 | 
			
		||||
  users: "Utilisateur·rice·s"
 | 
			
		||||
  drive: "Lecteur"
 | 
			
		||||
  network: "Réseau"
 | 
			
		||||
  charts:
 | 
			
		||||
    federation-instances: "Nombre d’instances : augmentation/diminution"
 | 
			
		||||
    federation-instances-total: "Nombre total d’instances"
 | 
			
		||||
    notes: "投稿の増減 (統合)"
 | 
			
		||||
    local-notes: "投稿の増減 (ローカル)"
 | 
			
		||||
    remote-notes: "投稿の増減 (リモート)"
 | 
			
		||||
    notes-total: "Total des publications"
 | 
			
		||||
    users: "Nombre d’utilisateur·rice·s : augmentation/diminution"
 | 
			
		||||
    users-total: "Nombre total des utilisateur·rice·s"
 | 
			
		||||
    drive: "ドライブ使用量の増減"
 | 
			
		||||
    drive-total: "Utilisation totale du lecteur"
 | 
			
		||||
    drive-files: "ドライブのファイル数の増減"
 | 
			
		||||
    drive-files-total: "Nombre total de fichiers sur le lecteur"
 | 
			
		||||
    network-requests: "Requêtes"
 | 
			
		||||
    network-time: "Temps de réponse"
 | 
			
		||||
    network-usage: "Traffic"
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  suspend-user: "Suspendre un·e utilisateur·rice"
 | 
			
		||||
  suspend: "Suspendre"
 | 
			
		||||
  suspended: "Suspendu avec succès"
 | 
			
		||||
desktop/views/pages/admin/admin.unsuspend-user.vue:
 | 
			
		||||
  suspended: "Suspendu·e avec succès."
 | 
			
		||||
  unsuspend-user: "Lever la suspension d’utilisateur·rice·s"
 | 
			
		||||
  unsuspend: "Suspension levée"
 | 
			
		||||
  unsuspended: "La suspension de l’utilisateur·rice a été levée avec succès"
 | 
			
		||||
desktop/views/pages/admin/admin.verify-user.vue:
 | 
			
		||||
  verify-user: "Paramètres de vérification du compte utilisateur"
 | 
			
		||||
  verify: "Vérification du compte"
 | 
			
		||||
  verified: "Le compte a été vérifié"
 | 
			
		||||
desktop/views/pages/admin/admin.unverify-user.vue:
 | 
			
		||||
  verify: "公式アカウントにする"
 | 
			
		||||
  verified: "公式アカウントにしました"
 | 
			
		||||
  unverify-user: "ユーザーの公式アカウント解除"
 | 
			
		||||
  unverify: "Ôter la vérification du compte"
 | 
			
		||||
  unverified: "Ce compte n'est pas vérifié"
 | 
			
		||||
desktop/views/pages/admin/admin.announcements.vue:
 | 
			
		||||
  unverified: "Ce compte n'est plus vérifié"
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "Ajouter un émoji"
 | 
			
		||||
    name: "Nom de l’émoji"
 | 
			
		||||
    name-desc: "a~z 0~9 _ の文字が使えます。"
 | 
			
		||||
    aliases: "Aliases"
 | 
			
		||||
    aliases-desc: "Vous pouvez définir plus d’un, séparés par des espaces."
 | 
			
		||||
    url: "URL de l’image"
 | 
			
		||||
    add: "Ajouter"
 | 
			
		||||
  emojis:
 | 
			
		||||
    title: "絵文字一覧"
 | 
			
		||||
    update: "更新"
 | 
			
		||||
    remove: "削除"
 | 
			
		||||
admin/views/announcements.vue:
 | 
			
		||||
  announcements: "Annonces"
 | 
			
		||||
desktop/views/pages/admin/admin.hashtags.vue:
 | 
			
		||||
  save: "Enregistrer"
 | 
			
		||||
  remove: "Supprimer"
 | 
			
		||||
  add: "Ajouter"
 | 
			
		||||
  title: "Titre"
 | 
			
		||||
  text: "Contenu"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Tags cachés"
 | 
			
		||||
desktop/views/pages/deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-only: "Les publications médias uniquement"
 | 
			
		||||
@@ -1328,6 +1374,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  sound: "Sons"
 | 
			
		||||
  enable-sounds: "Activer les sons"
 | 
			
		||||
  mark-as-read-all-unread-notes: "Marquer toutes les publications comme lues"
 | 
			
		||||
  password: "Mot de Passe"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "Vous suit"
 | 
			
		||||
  following: "Abonnements"
 | 
			
		||||
 
 | 
			
		||||
@@ -186,6 +186,7 @@ common:
 | 
			
		||||
    stack-left: "左に重ねる"
 | 
			
		||||
    pop-right: "右に出す"
 | 
			
		||||
  dev: "アプリの作成に失敗しました。再度お試しください。"
 | 
			
		||||
  ai-chan-kawaii: "藍ちゃかわいい"
 | 
			
		||||
auth/views/form.vue:
 | 
			
		||||
  share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
 | 
			
		||||
  permission-ask: "このアプリは次の権限を要求しています:"
 | 
			
		||||
@@ -874,7 +875,7 @@ common/views/components/mute-and-block.vue:
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  no-muted-users: "ミュートしているユーザーはいません"
 | 
			
		||||
  no-blocked-users: "ブロックしているユーザーはいません"
 | 
			
		||||
desktop/views/components/settings.password.vue:
 | 
			
		||||
common/views/components/password-settings.vue:
 | 
			
		||||
  reset: "パスワードを変更する"
 | 
			
		||||
  enter-current-password: "現在のパスワードを入力してください"
 | 
			
		||||
  enter-new-password: "新しいパスワードを入力してください"
 | 
			
		||||
@@ -946,41 +947,86 @@ desktop/views/components/users-list-item.vue:
 | 
			
		||||
desktop/views/components/window.vue:
 | 
			
		||||
  popout: "ポップアウト"
 | 
			
		||||
  close: "閉じる"
 | 
			
		||||
desktop/views/pages/admin/admin.vue:
 | 
			
		||||
admin/views/index.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  instance: "インスタンス"
 | 
			
		||||
  emoji: "カスタム絵文字"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  update: "更新"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
desktop/views/pages/admin/admin.dashboard.vue:
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  all-users: "全てのユーザー"
 | 
			
		||||
  original-users: "このインスタンスのユーザー"
 | 
			
		||||
  all-notes: "全ての投稿"
 | 
			
		||||
  original-notes: "このインスタンスの投稿"
 | 
			
		||||
  accounts: "アカウント"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
  invite: "招待"
 | 
			
		||||
  banner-url: "Banner URL"
 | 
			
		||||
  disableRegistration: "Disable new user registration"
 | 
			
		||||
  disableLocalTimeline: "Disable the local timeline"
 | 
			
		||||
desktop/views/pages/admin/admin.suspend-user.vue:
 | 
			
		||||
admin/views/charts.vue:
 | 
			
		||||
  title: "チャート"
 | 
			
		||||
  per-day: "1日ごと"
 | 
			
		||||
  per-hour: "1時間ごと"
 | 
			
		||||
  federation: "フェデレーション"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  network: "ネットワーク"
 | 
			
		||||
  charts:
 | 
			
		||||
    federation-instances: "インスタンスの増減"
 | 
			
		||||
    federation-instances-total: "インスタンスの積算"
 | 
			
		||||
    notes: "投稿の増減 (統合)"
 | 
			
		||||
    local-notes: "投稿の増減 (ローカル)"
 | 
			
		||||
    remote-notes: "投稿の増減 (リモート)"
 | 
			
		||||
    notes-total: "投稿の積算"
 | 
			
		||||
    users: "ユーザーの増減"
 | 
			
		||||
    users-total: "ユーザーの積算"
 | 
			
		||||
    drive: "ドライブ使用量の増減"
 | 
			
		||||
    drive-total: "ドライブ使用量の積算"
 | 
			
		||||
    drive-files: "ドライブのファイル数の増減"
 | 
			
		||||
    drive-files-total: "ドライブのファイル数の積算"
 | 
			
		||||
    network-requests: "リクエスト"
 | 
			
		||||
    network-time: "応答時間"
 | 
			
		||||
    network-usage: "通信量"
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  suspend-user: "ユーザーの凍結"
 | 
			
		||||
  suspend: "凍結"
 | 
			
		||||
  suspended: "凍結しました"
 | 
			
		||||
desktop/views/pages/admin/admin.unsuspend-user.vue:
 | 
			
		||||
  unsuspend-user: "ユーザーの凍結の解除"
 | 
			
		||||
  unsuspend: "凍結の解除"
 | 
			
		||||
  unsuspended: "凍結を解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.verify-user.vue:
 | 
			
		||||
  verify-user: "ユーザーの公式アカウント設定"
 | 
			
		||||
  verify: "公式アカウントにする"
 | 
			
		||||
  verified: "公式アカウントにしました"
 | 
			
		||||
desktop/views/pages/admin/admin.unverify-user.vue:
 | 
			
		||||
  unverify-user: "ユーザーの公式アカウント解除"
 | 
			
		||||
  unverify: "公式アカウントを解除する"
 | 
			
		||||
  unverified: "公式アカウントを解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.announcements.vue:
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "絵文字の登録"
 | 
			
		||||
    name: "絵文字名"
 | 
			
		||||
    name-desc: "a~z 0~9 _ の文字が使えます。"
 | 
			
		||||
    aliases: "エイリアス"
 | 
			
		||||
    aliases-desc: "スペースで区切って複数設定できます。"
 | 
			
		||||
    url: "絵文字画像URL"
 | 
			
		||||
    add: "追加"
 | 
			
		||||
  emojis:
 | 
			
		||||
    title: "絵文字一覧"
 | 
			
		||||
    update: "更新"
 | 
			
		||||
    remove: "削除"
 | 
			
		||||
admin/views/announcements.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
desktop/views/pages/admin/admin.hashtags.vue:
 | 
			
		||||
  save: "保存"
 | 
			
		||||
  remove: "削除"
 | 
			
		||||
  add: "追加"
 | 
			
		||||
  title: "タイトル"
 | 
			
		||||
  text: "内容"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
desktop/views/pages/deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-only: "メディア投稿のみ"
 | 
			
		||||
@@ -1328,6 +1374,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
  mark-as-read-all-unread-notes: "すべての投稿を既読にする"
 | 
			
		||||
  password: "パスワード"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
 
 | 
			
		||||
@@ -201,6 +201,7 @@ common:
 | 
			
		||||
    pop-right: "右に出す"
 | 
			
		||||
 | 
			
		||||
  dev: "アプリの作成に失敗しました。再度お試しください。"
 | 
			
		||||
  ai-chan-kawaii: "藍ちゃかわいい"
 | 
			
		||||
 | 
			
		||||
auth/views/form.vue:
 | 
			
		||||
  share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
 | 
			
		||||
@@ -981,7 +982,7 @@ common/views/components/mute-and-block.vue:
 | 
			
		||||
  no-muted-users: "ミュートしているユーザーはいません"
 | 
			
		||||
  no-blocked-users: "ブロックしているユーザーはいません"
 | 
			
		||||
 | 
			
		||||
desktop/views/components/settings.password.vue:
 | 
			
		||||
common/views/components/password-settings.vue:
 | 
			
		||||
  reset: "パスワードを変更する"
 | 
			
		||||
  enter-current-password: "現在のパスワードを入力してください"
 | 
			
		||||
  enter-new-password: "新しいパスワードを入力してください"
 | 
			
		||||
@@ -1069,48 +1070,92 @@ desktop/views/components/window.vue:
 | 
			
		||||
  popout: "ポップアウト"
 | 
			
		||||
  close: "閉じる"
 | 
			
		||||
 | 
			
		||||
desktop/views/pages/admin/admin.vue:
 | 
			
		||||
admin/views/index.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  instance: "インスタンス"
 | 
			
		||||
  emoji: "カスタム絵文字"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  update: "更新"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
 | 
			
		||||
desktop/views/pages/admin/admin.dashboard.vue:
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  all-users: "全てのユーザー"
 | 
			
		||||
  original-users: "このインスタンスのユーザー"
 | 
			
		||||
  all-notes: "全ての投稿"
 | 
			
		||||
  original-notes: "このインスタンスの投稿"
 | 
			
		||||
  accounts: "アカウント"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
  invite: "招待"
 | 
			
		||||
  banner-url: "Banner URL"
 | 
			
		||||
  disableRegistration: "Disable new user registration"
 | 
			
		||||
  disableLocalTimeline: "Disable the local timeline"
 | 
			
		||||
 | 
			
		||||
desktop/views/pages/admin/admin.suspend-user.vue:
 | 
			
		||||
admin/views/charts.vue:
 | 
			
		||||
  title: "チャート"
 | 
			
		||||
  per-day: "1日ごと"
 | 
			
		||||
  per-hour: "1時間ごと"
 | 
			
		||||
  federation: "フェデレーション"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  network: "ネットワーク"
 | 
			
		||||
  charts:
 | 
			
		||||
    federation-instances: "インスタンスの増減"
 | 
			
		||||
    federation-instances-total: "インスタンスの積算"
 | 
			
		||||
    notes: "投稿の増減 (統合)"
 | 
			
		||||
    local-notes: "投稿の増減 (ローカル)"
 | 
			
		||||
    remote-notes: "投稿の増減 (リモート)"
 | 
			
		||||
    notes-total: "投稿の積算"
 | 
			
		||||
    users: "ユーザーの増減"
 | 
			
		||||
    users-total: "ユーザーの積算"
 | 
			
		||||
    drive: "ドライブ使用量の増減"
 | 
			
		||||
    drive-total: "ドライブ使用量の積算"
 | 
			
		||||
    drive-files: "ドライブのファイル数の増減"
 | 
			
		||||
    drive-files-total: "ドライブのファイル数の積算"
 | 
			
		||||
    network-requests: "リクエスト"
 | 
			
		||||
    network-time: "応答時間"
 | 
			
		||||
    network-usage: "通信量"
 | 
			
		||||
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  suspend-user: "ユーザーの凍結"
 | 
			
		||||
  suspend: "凍結"
 | 
			
		||||
  suspended: "凍結しました"
 | 
			
		||||
 | 
			
		||||
desktop/views/pages/admin/admin.unsuspend-user.vue:
 | 
			
		||||
  unsuspend-user: "ユーザーの凍結の解除"
 | 
			
		||||
  unsuspend: "凍結の解除"
 | 
			
		||||
  unsuspended: "凍結を解除しました"
 | 
			
		||||
 | 
			
		||||
desktop/views/pages/admin/admin.verify-user.vue:
 | 
			
		||||
  verify-user: "ユーザーの公式アカウント設定"
 | 
			
		||||
  verify: "公式アカウントにする"
 | 
			
		||||
  verified: "公式アカウントにしました"
 | 
			
		||||
 | 
			
		||||
desktop/views/pages/admin/admin.unverify-user.vue:
 | 
			
		||||
  unverify-user: "ユーザーの公式アカウント解除"
 | 
			
		||||
  unverify: "公式アカウントを解除する"
 | 
			
		||||
  unverified: "公式アカウントを解除しました"
 | 
			
		||||
 | 
			
		||||
desktop/views/pages/admin/admin.announcements.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "絵文字の登録"
 | 
			
		||||
    name: "絵文字名"
 | 
			
		||||
    name-desc: "a~z 0~9 _ の文字が使えます。"
 | 
			
		||||
    aliases: "エイリアス"
 | 
			
		||||
    aliases-desc: "スペースで区切って複数設定できます。"
 | 
			
		||||
    url: "絵文字画像URL"
 | 
			
		||||
    add: "追加"
 | 
			
		||||
  emojis:
 | 
			
		||||
    title: "絵文字一覧"
 | 
			
		||||
    update: "更新"
 | 
			
		||||
    remove: "削除"
 | 
			
		||||
 | 
			
		||||
desktop/views/pages/admin/admin.hashtags.vue:
 | 
			
		||||
admin/views/announcements.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  save: "保存"
 | 
			
		||||
  remove: "削除"
 | 
			
		||||
  add: "追加"
 | 
			
		||||
  title: "タイトル"
 | 
			
		||||
  text: "内容"
 | 
			
		||||
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
 | 
			
		||||
desktop/views/pages/deck/deck.tl-column.vue:
 | 
			
		||||
@@ -1529,6 +1574,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
  mark-as-read-all-unread-notes: "すべての投稿を既読にする"
 | 
			
		||||
  password: "パスワード"
 | 
			
		||||
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
 
 | 
			
		||||
@@ -186,6 +186,7 @@ common:
 | 
			
		||||
    stack-left: "左に重ねんで!"
 | 
			
		||||
    pop-right: "右に出すで!"
 | 
			
		||||
  dev: "アプリの作成あかんかったわ。もっぺんやってみて。"
 | 
			
		||||
  ai-chan-kawaii: "藍ちゃかわいい"
 | 
			
		||||
auth/views/form.vue:
 | 
			
		||||
  share-access: "<i>{{ app.name }}</i>があんさんのアカウントにアクセスすんのを<b>許可</b>してもええか?"
 | 
			
		||||
  permission-ask: "このアプリは次の権限を要求してんで:"
 | 
			
		||||
@@ -874,13 +875,13 @@ common/views/components/mute-and-block.vue:
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  no-muted-users: "ミュートしているユーザーはいません"
 | 
			
		||||
  no-blocked-users: "ブロックしているユーザーはいません"
 | 
			
		||||
desktop/views/components/settings.password.vue:
 | 
			
		||||
common/views/components/password-settings.vue:
 | 
			
		||||
  reset: "パスワードを変更する"
 | 
			
		||||
  enter-current-password: "今のパスワードを入れてや"
 | 
			
		||||
  enter-new-password: "さらのパスワード入れてや"
 | 
			
		||||
  enter-new-password-again: "もういっぺんさらのパスワードを入れてや"
 | 
			
		||||
  not-match: "パスワードがおうとらん"
 | 
			
		||||
  changed: "パスワード変えたわ"
 | 
			
		||||
  enter-current-password: "現在のパスワードを入力してください"
 | 
			
		||||
  enter-new-password: "新しいパスワードを入力してください"
 | 
			
		||||
  enter-new-password-again: "もう一度新しいパスワードを入力してください"
 | 
			
		||||
  not-match: "新しいパスワードが一致しません"
 | 
			
		||||
  changed: "パスワードを変更しました"
 | 
			
		||||
desktop/views/components/sub-note-content.vue:
 | 
			
		||||
  private: "この投稿は見せられへんわ"
 | 
			
		||||
  deleted: "この投稿なんか無くなってもうたわ"
 | 
			
		||||
@@ -946,41 +947,86 @@ desktop/views/components/users-list-item.vue:
 | 
			
		||||
desktop/views/components/window.vue:
 | 
			
		||||
  popout: "ポップアウト"
 | 
			
		||||
  close: "さいなら"
 | 
			
		||||
desktop/views/pages/admin/admin.vue:
 | 
			
		||||
admin/views/index.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  instance: "インスタンス"
 | 
			
		||||
  emoji: "カスタム絵文字"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  update: "更新"
 | 
			
		||||
  announcements: "知っといてや"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
desktop/views/pages/admin/admin.dashboard.vue:
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  all-users: "知り合い全員や"
 | 
			
		||||
  original-users: "ここの人らだけ"
 | 
			
		||||
  all-notes: "全ての投稿"
 | 
			
		||||
  original-notes: "このインスタンスの投稿"
 | 
			
		||||
  invite: "来てや"
 | 
			
		||||
  accounts: "アカウント"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
  invite: "招待"
 | 
			
		||||
  banner-url: "Banner URL"
 | 
			
		||||
  disableRegistration: "Disable new user registration"
 | 
			
		||||
  disableLocalTimeline: "Disable the local timeline"
 | 
			
		||||
desktop/views/pages/admin/admin.suspend-user.vue:
 | 
			
		||||
admin/views/charts.vue:
 | 
			
		||||
  title: "チャート"
 | 
			
		||||
  per-day: "1日ごと"
 | 
			
		||||
  per-hour: "1時間ごと"
 | 
			
		||||
  federation: "フェデレーション"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  network: "ネットワーク"
 | 
			
		||||
  charts:
 | 
			
		||||
    federation-instances: "インスタンスの増減"
 | 
			
		||||
    federation-instances-total: "インスタンスの積算"
 | 
			
		||||
    notes: "投稿の増減 (統合)"
 | 
			
		||||
    local-notes: "投稿の増減 (ローカル)"
 | 
			
		||||
    remote-notes: "投稿の増減 (リモート)"
 | 
			
		||||
    notes-total: "投稿の積算"
 | 
			
		||||
    users: "ユーザーの増減"
 | 
			
		||||
    users-total: "ユーザーの積算"
 | 
			
		||||
    drive: "ドライブ使用量の増減"
 | 
			
		||||
    drive-total: "ドライブ使用量の積算"
 | 
			
		||||
    drive-files: "ドライブのファイル数の増減"
 | 
			
		||||
    drive-files-total: "ドライブのファイル数の積算"
 | 
			
		||||
    network-requests: "リクエスト"
 | 
			
		||||
    network-time: "応答時間"
 | 
			
		||||
    network-usage: "通信量"
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  suspend-user: "ユーザーの凍結"
 | 
			
		||||
  suspend: "凍結"
 | 
			
		||||
  suspended: "凍結したで"
 | 
			
		||||
desktop/views/pages/admin/admin.unsuspend-user.vue:
 | 
			
		||||
  suspended: "凍結しました"
 | 
			
		||||
  unsuspend-user: "ユーザーの凍結の解除"
 | 
			
		||||
  unsuspend: "凍結の解除"
 | 
			
		||||
  unsuspended: "凍結を解除したで"
 | 
			
		||||
desktop/views/pages/admin/admin.verify-user.vue:
 | 
			
		||||
  unsuspended: "凍結を解除しました"
 | 
			
		||||
  verify-user: "ユーザーの公式アカウント設定"
 | 
			
		||||
  verify: "公式アカウントにする"
 | 
			
		||||
  verified: "公式アカウントにしたで"
 | 
			
		||||
desktop/views/pages/admin/admin.unverify-user.vue:
 | 
			
		||||
  unverify-user: "ユーザーの公式アカウントにせーへん"
 | 
			
		||||
  unverify: "公式アカウントにはさせへんで"
 | 
			
		||||
  unverified: "公式アカウントを解除したで"
 | 
			
		||||
desktop/views/pages/admin/admin.announcements.vue:
 | 
			
		||||
  announcements: "知っといてや"
 | 
			
		||||
desktop/views/pages/admin/admin.hashtags.vue:
 | 
			
		||||
  verified: "公式アカウントにしました"
 | 
			
		||||
  unverify-user: "ユーザーの公式アカウント解除"
 | 
			
		||||
  unverify: "公式アカウントを解除する"
 | 
			
		||||
  unverified: "公式アカウントを解除しました"
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "絵文字の登録"
 | 
			
		||||
    name: "絵文字名"
 | 
			
		||||
    name-desc: "a~z 0~9 _ の文字が使えます。"
 | 
			
		||||
    aliases: "エイリアス"
 | 
			
		||||
    aliases-desc: "スペースで区切って複数設定できます。"
 | 
			
		||||
    url: "絵文字画像URL"
 | 
			
		||||
    add: "追加"
 | 
			
		||||
  emojis:
 | 
			
		||||
    title: "絵文字一覧"
 | 
			
		||||
    update: "更新"
 | 
			
		||||
    remove: "削除"
 | 
			
		||||
admin/views/announcements.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  save: "保存"
 | 
			
		||||
  remove: "削除"
 | 
			
		||||
  add: "追加"
 | 
			
		||||
  title: "タイトル"
 | 
			
		||||
  text: "内容"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
desktop/views/pages/deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-only: "メディア投稿だけや"
 | 
			
		||||
@@ -1328,6 +1374,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enable-sounds: "サウンド鳴らす"
 | 
			
		||||
  mark-as-read-all-unread-notes: "全部もう読んだわ"
 | 
			
		||||
  password: "パスワード"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされとるで"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
 
 | 
			
		||||
@@ -186,6 +186,7 @@ common:
 | 
			
		||||
    stack-left: "左に重ねる"
 | 
			
		||||
    pop-right: "右に出す"
 | 
			
		||||
  dev: "アプリの作成に失敗しました。再度お試しください。"
 | 
			
		||||
  ai-chan-kawaii: "藍ちゃかわいい"
 | 
			
		||||
auth/views/form.vue:
 | 
			
		||||
  share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
 | 
			
		||||
  permission-ask: "このアプリは次の権限を要求しています:"
 | 
			
		||||
@@ -874,7 +875,7 @@ common/views/components/mute-and-block.vue:
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  no-muted-users: "ミュートしているユーザーはいません"
 | 
			
		||||
  no-blocked-users: "ブロックしているユーザーはいません"
 | 
			
		||||
desktop/views/components/settings.password.vue:
 | 
			
		||||
common/views/components/password-settings.vue:
 | 
			
		||||
  reset: "パスワードを変更する"
 | 
			
		||||
  enter-current-password: "現在のパスワードを入力してください"
 | 
			
		||||
  enter-new-password: "新しいパスワードを入力してください"
 | 
			
		||||
@@ -946,41 +947,86 @@ desktop/views/components/users-list-item.vue:
 | 
			
		||||
desktop/views/components/window.vue:
 | 
			
		||||
  popout: "ポップアウト"
 | 
			
		||||
  close: "閉じる"
 | 
			
		||||
desktop/views/pages/admin/admin.vue:
 | 
			
		||||
admin/views/index.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  instance: "インスタンス"
 | 
			
		||||
  emoji: "カスタム絵文字"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  update: "更新"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
desktop/views/pages/admin/admin.dashboard.vue:
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  all-users: "全てのユーザー"
 | 
			
		||||
  original-users: "このインスタンスのユーザー"
 | 
			
		||||
  all-notes: "全ての投稿"
 | 
			
		||||
  original-notes: "このインスタンスの投稿"
 | 
			
		||||
  accounts: "アカウント"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
  invite: "招待"
 | 
			
		||||
  banner-url: "Banner URL"
 | 
			
		||||
  disableRegistration: "Disable new user registration"
 | 
			
		||||
  disableLocalTimeline: "Disable the local timeline"
 | 
			
		||||
desktop/views/pages/admin/admin.suspend-user.vue:
 | 
			
		||||
admin/views/charts.vue:
 | 
			
		||||
  title: "チャート"
 | 
			
		||||
  per-day: "1日ごと"
 | 
			
		||||
  per-hour: "1時間ごと"
 | 
			
		||||
  federation: "フェデレーション"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  network: "ネットワーク"
 | 
			
		||||
  charts:
 | 
			
		||||
    federation-instances: "インスタンスの増減"
 | 
			
		||||
    federation-instances-total: "インスタンスの積算"
 | 
			
		||||
    notes: "投稿の増減 (統合)"
 | 
			
		||||
    local-notes: "投稿の増減 (ローカル)"
 | 
			
		||||
    remote-notes: "投稿の増減 (リモート)"
 | 
			
		||||
    notes-total: "投稿の積算"
 | 
			
		||||
    users: "ユーザーの増減"
 | 
			
		||||
    users-total: "ユーザーの積算"
 | 
			
		||||
    drive: "ドライブ使用量の増減"
 | 
			
		||||
    drive-total: "ドライブ使用量の積算"
 | 
			
		||||
    drive-files: "ドライブのファイル数の増減"
 | 
			
		||||
    drive-files-total: "ドライブのファイル数の積算"
 | 
			
		||||
    network-requests: "リクエスト"
 | 
			
		||||
    network-time: "応答時間"
 | 
			
		||||
    network-usage: "通信量"
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  suspend-user: "ユーザーの凍結"
 | 
			
		||||
  suspend: "凍結"
 | 
			
		||||
  suspended: "凍結しました"
 | 
			
		||||
desktop/views/pages/admin/admin.unsuspend-user.vue:
 | 
			
		||||
  unsuspend-user: "ユーザーの凍結の解除"
 | 
			
		||||
  unsuspend: "凍結の解除"
 | 
			
		||||
  unsuspended: "凍結を解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.verify-user.vue:
 | 
			
		||||
  verify-user: "ユーザーの公式アカウント設定"
 | 
			
		||||
  verify: "公式アカウントにする"
 | 
			
		||||
  verified: "公式アカウントにしました"
 | 
			
		||||
desktop/views/pages/admin/admin.unverify-user.vue:
 | 
			
		||||
  unverify-user: "ユーザーの公式アカウント解除"
 | 
			
		||||
  unverify: "公式アカウントを解除する"
 | 
			
		||||
  unverified: "公式アカウントを解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.announcements.vue:
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "絵文字の登録"
 | 
			
		||||
    name: "絵文字名"
 | 
			
		||||
    name-desc: "a~z 0~9 _ の文字が使えます。"
 | 
			
		||||
    aliases: "エイリアス"
 | 
			
		||||
    aliases-desc: "スペースで区切って複数設定できます。"
 | 
			
		||||
    url: "絵文字画像URL"
 | 
			
		||||
    add: "追加"
 | 
			
		||||
  emojis:
 | 
			
		||||
    title: "絵文字一覧"
 | 
			
		||||
    update: "更新"
 | 
			
		||||
    remove: "削除"
 | 
			
		||||
admin/views/announcements.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
desktop/views/pages/admin/admin.hashtags.vue:
 | 
			
		||||
  save: "保存"
 | 
			
		||||
  remove: "削除"
 | 
			
		||||
  add: "追加"
 | 
			
		||||
  title: "タイトル"
 | 
			
		||||
  text: "内容"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
desktop/views/pages/deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-only: "メディア投稿のみ"
 | 
			
		||||
@@ -1328,6 +1374,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
  mark-as-read-all-unread-notes: "すべての投稿を既読にする"
 | 
			
		||||
  password: "パスワード"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
 
 | 
			
		||||
@@ -186,6 +186,7 @@ common:
 | 
			
		||||
    stack-left: "左に重ねる"
 | 
			
		||||
    pop-right: "右に出す"
 | 
			
		||||
  dev: "アプリの作成に失敗しました。再度お試しください。"
 | 
			
		||||
  ai-chan-kawaii: "藍ちゃかわいい"
 | 
			
		||||
auth/views/form.vue:
 | 
			
		||||
  share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
 | 
			
		||||
  permission-ask: "このアプリは次の権限を要求しています:"
 | 
			
		||||
@@ -874,13 +875,13 @@ common/views/components/mute-and-block.vue:
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  no-muted-users: "ミュートしているユーザーはいません"
 | 
			
		||||
  no-blocked-users: "ブロックしているユーザーはいません"
 | 
			
		||||
desktop/views/components/settings.password.vue:
 | 
			
		||||
  reset: "Wachtwoord wijzigen"
 | 
			
		||||
  enter-current-password: "Voer je huidige wachtwoord in"
 | 
			
		||||
  enter-new-password: "Voer je nieuwe wachtwoord in"
 | 
			
		||||
  enter-new-password-again: "Voer je nieuwe wachtwoord nogmaals in"
 | 
			
		||||
  not-match: "Het nieuwe wachtwoord komt niet overeen"
 | 
			
		||||
  changed: "Wachtwoord bijgewerkt"
 | 
			
		||||
common/views/components/password-settings.vue:
 | 
			
		||||
  reset: "パスワードを変更する"
 | 
			
		||||
  enter-current-password: "現在のパスワードを入力してください"
 | 
			
		||||
  enter-new-password: "新しいパスワードを入力してください"
 | 
			
		||||
  enter-new-password-again: "もう一度新しいパスワードを入力してください"
 | 
			
		||||
  not-match: "新しいパスワードが一致しません"
 | 
			
		||||
  changed: "パスワードを変更しました"
 | 
			
		||||
desktop/views/components/sub-note-content.vue:
 | 
			
		||||
  private: "この投稿は非公開です"
 | 
			
		||||
  deleted: "この投稿は削除されました"
 | 
			
		||||
@@ -946,41 +947,86 @@ desktop/views/components/users-list-item.vue:
 | 
			
		||||
desktop/views/components/window.vue:
 | 
			
		||||
  popout: "Uitvouwen"
 | 
			
		||||
  close: "Sluiten"
 | 
			
		||||
desktop/views/pages/admin/admin.vue:
 | 
			
		||||
admin/views/index.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  instance: "インスタンス"
 | 
			
		||||
  emoji: "カスタム絵文字"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  update: "更新"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
desktop/views/pages/admin/admin.dashboard.vue:
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  all-users: "全てのユーザー"
 | 
			
		||||
  original-users: "このインスタンスのユーザー"
 | 
			
		||||
  all-notes: "全ての投稿"
 | 
			
		||||
  original-notes: "このインスタンスの投稿"
 | 
			
		||||
  accounts: "アカウント"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
  invite: "招待"
 | 
			
		||||
  banner-url: "Banner URL"
 | 
			
		||||
  disableRegistration: "Disable new user registration"
 | 
			
		||||
  disableLocalTimeline: "Disable the local timeline"
 | 
			
		||||
desktop/views/pages/admin/admin.suspend-user.vue:
 | 
			
		||||
admin/views/charts.vue:
 | 
			
		||||
  title: "チャート"
 | 
			
		||||
  per-day: "1日ごと"
 | 
			
		||||
  per-hour: "1時間ごと"
 | 
			
		||||
  federation: "フェデレーション"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  network: "ネットワーク"
 | 
			
		||||
  charts:
 | 
			
		||||
    federation-instances: "インスタンスの増減"
 | 
			
		||||
    federation-instances-total: "インスタンスの積算"
 | 
			
		||||
    notes: "投稿の増減 (統合)"
 | 
			
		||||
    local-notes: "投稿の増減 (ローカル)"
 | 
			
		||||
    remote-notes: "投稿の増減 (リモート)"
 | 
			
		||||
    notes-total: "投稿の積算"
 | 
			
		||||
    users: "ユーザーの増減"
 | 
			
		||||
    users-total: "ユーザーの積算"
 | 
			
		||||
    drive: "ドライブ使用量の増減"
 | 
			
		||||
    drive-total: "ドライブ使用量の積算"
 | 
			
		||||
    drive-files: "ドライブのファイル数の増減"
 | 
			
		||||
    drive-files-total: "ドライブのファイル数の積算"
 | 
			
		||||
    network-requests: "リクエスト"
 | 
			
		||||
    network-time: "応答時間"
 | 
			
		||||
    network-usage: "通信量"
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  suspend-user: "ユーザーの凍結"
 | 
			
		||||
  suspend: "凍結"
 | 
			
		||||
  suspended: "凍結しました"
 | 
			
		||||
desktop/views/pages/admin/admin.unsuspend-user.vue:
 | 
			
		||||
  unsuspend-user: "ユーザーの凍結の解除"
 | 
			
		||||
  unsuspend: "凍結の解除"
 | 
			
		||||
  unsuspended: "凍結を解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.verify-user.vue:
 | 
			
		||||
  verify-user: "ユーザーの公式アカウント設定"
 | 
			
		||||
  verify: "公式アカウントにする"
 | 
			
		||||
  verified: "公式アカウントにしました"
 | 
			
		||||
desktop/views/pages/admin/admin.unverify-user.vue:
 | 
			
		||||
  unverify-user: "ユーザーの公式アカウント解除"
 | 
			
		||||
  unverify: "公式アカウントを解除する"
 | 
			
		||||
  unverified: "公式アカウントを解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.announcements.vue:
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "絵文字の登録"
 | 
			
		||||
    name: "絵文字名"
 | 
			
		||||
    name-desc: "a~z 0~9 _ の文字が使えます。"
 | 
			
		||||
    aliases: "エイリアス"
 | 
			
		||||
    aliases-desc: "スペースで区切って複数設定できます。"
 | 
			
		||||
    url: "絵文字画像URL"
 | 
			
		||||
    add: "追加"
 | 
			
		||||
  emojis:
 | 
			
		||||
    title: "絵文字一覧"
 | 
			
		||||
    update: "更新"
 | 
			
		||||
    remove: "削除"
 | 
			
		||||
admin/views/announcements.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
desktop/views/pages/admin/admin.hashtags.vue:
 | 
			
		||||
  save: "保存"
 | 
			
		||||
  remove: "削除"
 | 
			
		||||
  add: "追加"
 | 
			
		||||
  title: "タイトル"
 | 
			
		||||
  text: "内容"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
desktop/views/pages/deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-only: "メディア投稿のみ"
 | 
			
		||||
@@ -1328,6 +1374,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
  mark-as-read-all-unread-notes: "すべての投稿を既読にする"
 | 
			
		||||
  password: "パスワード"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "Volgt jou"
 | 
			
		||||
  following: "Volgend"
 | 
			
		||||
 
 | 
			
		||||
@@ -186,6 +186,7 @@ common:
 | 
			
		||||
    stack-left: "左に重ねる"
 | 
			
		||||
    pop-right: "Til høyre"
 | 
			
		||||
  dev: "アプリの作成に失敗しました。再度お試しください。"
 | 
			
		||||
  ai-chan-kawaii: "藍ちゃかわいい"
 | 
			
		||||
auth/views/form.vue:
 | 
			
		||||
  share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
 | 
			
		||||
  permission-ask: "このアプリは次の権限を要求しています:"
 | 
			
		||||
@@ -874,7 +875,7 @@ common/views/components/mute-and-block.vue:
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  no-muted-users: "ミュートしているユーザーはいません"
 | 
			
		||||
  no-blocked-users: "ブロックしているユーザーはいません"
 | 
			
		||||
desktop/views/components/settings.password.vue:
 | 
			
		||||
common/views/components/password-settings.vue:
 | 
			
		||||
  reset: "パスワードを変更する"
 | 
			
		||||
  enter-current-password: "現在のパスワードを入力してください"
 | 
			
		||||
  enter-new-password: "新しいパスワードを入力してください"
 | 
			
		||||
@@ -946,41 +947,86 @@ desktop/views/components/users-list-item.vue:
 | 
			
		||||
desktop/views/components/window.vue:
 | 
			
		||||
  popout: "ポップアウト"
 | 
			
		||||
  close: "Lukk"
 | 
			
		||||
desktop/views/pages/admin/admin.vue:
 | 
			
		||||
admin/views/index.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  users: "Brukere"
 | 
			
		||||
  update: "Oppdater"
 | 
			
		||||
  instance: "インスタンス"
 | 
			
		||||
  emoji: "カスタム絵文字"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  update: "更新"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
desktop/views/pages/admin/admin.dashboard.vue:
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  all-users: "全てのユーザー"
 | 
			
		||||
  original-users: "このインスタンスのユーザー"
 | 
			
		||||
  all-notes: "全ての投稿"
 | 
			
		||||
  original-notes: "このインスタンスの投稿"
 | 
			
		||||
  invite: "Inviter"
 | 
			
		||||
  accounts: "アカウント"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
  invite: "招待"
 | 
			
		||||
  banner-url: "Banner URL"
 | 
			
		||||
  disableRegistration: "Disable new user registration"
 | 
			
		||||
  disableLocalTimeline: "Disable the local timeline"
 | 
			
		||||
desktop/views/pages/admin/admin.suspend-user.vue:
 | 
			
		||||
admin/views/charts.vue:
 | 
			
		||||
  title: "チャート"
 | 
			
		||||
  per-day: "1日ごと"
 | 
			
		||||
  per-hour: "1時間ごと"
 | 
			
		||||
  federation: "フェデレーション"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  network: "ネットワーク"
 | 
			
		||||
  charts:
 | 
			
		||||
    federation-instances: "インスタンスの増減"
 | 
			
		||||
    federation-instances-total: "インスタンスの積算"
 | 
			
		||||
    notes: "投稿の増減 (統合)"
 | 
			
		||||
    local-notes: "投稿の増減 (ローカル)"
 | 
			
		||||
    remote-notes: "投稿の増減 (リモート)"
 | 
			
		||||
    notes-total: "投稿の積算"
 | 
			
		||||
    users: "ユーザーの増減"
 | 
			
		||||
    users-total: "ユーザーの積算"
 | 
			
		||||
    drive: "ドライブ使用量の増減"
 | 
			
		||||
    drive-total: "ドライブ使用量の積算"
 | 
			
		||||
    drive-files: "ドライブのファイル数の増減"
 | 
			
		||||
    drive-files-total: "ドライブのファイル数の積算"
 | 
			
		||||
    network-requests: "リクエスト"
 | 
			
		||||
    network-time: "応答時間"
 | 
			
		||||
    network-usage: "通信量"
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  suspend-user: "ユーザーの凍結"
 | 
			
		||||
  suspend: "Suspender"
 | 
			
		||||
  suspend: "凍結"
 | 
			
		||||
  suspended: "凍結しました"
 | 
			
		||||
desktop/views/pages/admin/admin.unsuspend-user.vue:
 | 
			
		||||
  unsuspend-user: "ユーザーの凍結の解除"
 | 
			
		||||
  unsuspend: "凍結の解除"
 | 
			
		||||
  unsuspended: "凍結を解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.verify-user.vue:
 | 
			
		||||
  verify-user: "ユーザーの公式アカウント設定"
 | 
			
		||||
  verify: "公式アカウントにする"
 | 
			
		||||
  verified: "公式アカウントにしました"
 | 
			
		||||
desktop/views/pages/admin/admin.unverify-user.vue:
 | 
			
		||||
  unverify-user: "ユーザーの公式アカウント解除"
 | 
			
		||||
  unverify: "公式アカウントを解除する"
 | 
			
		||||
  unverified: "公式アカウントを解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.announcements.vue:
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "絵文字の登録"
 | 
			
		||||
    name: "絵文字名"
 | 
			
		||||
    name-desc: "a~z 0~9 _ の文字が使えます。"
 | 
			
		||||
    aliases: "エイリアス"
 | 
			
		||||
    aliases-desc: "スペースで区切って複数設定できます。"
 | 
			
		||||
    url: "絵文字画像URL"
 | 
			
		||||
    add: "追加"
 | 
			
		||||
  emojis:
 | 
			
		||||
    title: "絵文字一覧"
 | 
			
		||||
    update: "更新"
 | 
			
		||||
    remove: "削除"
 | 
			
		||||
admin/views/announcements.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
desktop/views/pages/admin/admin.hashtags.vue:
 | 
			
		||||
  save: "保存"
 | 
			
		||||
  remove: "削除"
 | 
			
		||||
  add: "追加"
 | 
			
		||||
  title: "タイトル"
 | 
			
		||||
  text: "内容"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
desktop/views/pages/deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-only: "メディア投稿のみ"
 | 
			
		||||
@@ -1328,6 +1374,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  sound: "Lyder"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
  mark-as-read-all-unread-notes: "すべての投稿を既読にする"
 | 
			
		||||
  password: "パスワード"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  following: "Følger"
 | 
			
		||||
 
 | 
			
		||||
@@ -186,6 +186,7 @@ common:
 | 
			
		||||
    stack-left: "Przypnij do lewej"
 | 
			
		||||
    pop-right: "Odepnij w prawo"
 | 
			
		||||
  dev: "アプリの作成に失敗しました。再度お試しください。"
 | 
			
		||||
  ai-chan-kawaii: "藍ちゃかわいい"
 | 
			
		||||
auth/views/form.vue:
 | 
			
		||||
  share-access: "Czy chcesz <b>zezwolić</b> <i>{{ app.name }}</i> na dostęp do Twojego konta?"
 | 
			
		||||
  permission-ask: "Ta aplikacja wymaga następujących uprawnień:"
 | 
			
		||||
@@ -874,13 +875,13 @@ common/views/components/mute-and-block.vue:
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  no-muted-users: "ミュートしているユーザーはいません"
 | 
			
		||||
  no-blocked-users: "ブロックしているユーザーはいません"
 | 
			
		||||
desktop/views/components/settings.password.vue:
 | 
			
		||||
  reset: "Zmień hasło"
 | 
			
		||||
  enter-current-password: "Wprowadź obecne hasło"
 | 
			
		||||
  enter-new-password: "Wprowadź nowe hasło"
 | 
			
		||||
  enter-new-password-again: "Wprowadź ponownie nowe hasło"
 | 
			
		||||
  not-match: "Nowe hasła nie pasują do siebie"
 | 
			
		||||
  changed: "Pomyślnie zmieniono hasło"
 | 
			
		||||
common/views/components/password-settings.vue:
 | 
			
		||||
  reset: "パスワードを変更する"
 | 
			
		||||
  enter-current-password: "現在のパスワードを入力してください"
 | 
			
		||||
  enter-new-password: "新しいパスワードを入力してください"
 | 
			
		||||
  enter-new-password-again: "もう一度新しいパスワードを入力してください"
 | 
			
		||||
  not-match: "新しいパスワードが一致しません"
 | 
			
		||||
  changed: "パスワードを変更しました"
 | 
			
		||||
desktop/views/components/sub-note-content.vue:
 | 
			
		||||
  private: "ten wpis jest prywatny"
 | 
			
		||||
  deleted: "ten wpis został usunięty"
 | 
			
		||||
@@ -946,41 +947,86 @@ desktop/views/components/users-list-item.vue:
 | 
			
		||||
desktop/views/components/window.vue:
 | 
			
		||||
  popout: "Pop-out"
 | 
			
		||||
  close: "Zamknij"
 | 
			
		||||
desktop/views/pages/admin/admin.vue:
 | 
			
		||||
admin/views/index.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  instance: "インスタンス"
 | 
			
		||||
  emoji: "カスタム絵文字"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  update: "更新"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
desktop/views/pages/admin/admin.dashboard.vue:
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  all-users: "全てのユーザー"
 | 
			
		||||
  original-users: "このインスタンスのユーザー"
 | 
			
		||||
  all-notes: "全ての投稿"
 | 
			
		||||
  original-notes: "このインスタンスの投稿"
 | 
			
		||||
  accounts: "アカウント"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
  invite: "招待"
 | 
			
		||||
  banner-url: "Banner URL"
 | 
			
		||||
  disableRegistration: "Disable new user registration"
 | 
			
		||||
  disableLocalTimeline: "Disable the local timeline"
 | 
			
		||||
desktop/views/pages/admin/admin.suspend-user.vue:
 | 
			
		||||
admin/views/charts.vue:
 | 
			
		||||
  title: "チャート"
 | 
			
		||||
  per-day: "1日ごと"
 | 
			
		||||
  per-hour: "1時間ごと"
 | 
			
		||||
  federation: "フェデレーション"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  network: "ネットワーク"
 | 
			
		||||
  charts:
 | 
			
		||||
    federation-instances: "インスタンスの増減"
 | 
			
		||||
    federation-instances-total: "インスタンスの積算"
 | 
			
		||||
    notes: "投稿の増減 (統合)"
 | 
			
		||||
    local-notes: "投稿の増減 (ローカル)"
 | 
			
		||||
    remote-notes: "投稿の増減 (リモート)"
 | 
			
		||||
    notes-total: "投稿の積算"
 | 
			
		||||
    users: "ユーザーの増減"
 | 
			
		||||
    users-total: "ユーザーの積算"
 | 
			
		||||
    drive: "ドライブ使用量の増減"
 | 
			
		||||
    drive-total: "ドライブ使用量の積算"
 | 
			
		||||
    drive-files: "ドライブのファイル数の増減"
 | 
			
		||||
    drive-files-total: "ドライブのファイル数の積算"
 | 
			
		||||
    network-requests: "リクエスト"
 | 
			
		||||
    network-time: "応答時間"
 | 
			
		||||
    network-usage: "通信量"
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  suspend-user: "ユーザーの凍結"
 | 
			
		||||
  suspend: "凍結"
 | 
			
		||||
  suspended: "凍結しました"
 | 
			
		||||
desktop/views/pages/admin/admin.unsuspend-user.vue:
 | 
			
		||||
  unsuspend-user: "ユーザーの凍結の解除"
 | 
			
		||||
  unsuspend: "凍結の解除"
 | 
			
		||||
  unsuspended: "凍結を解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.verify-user.vue:
 | 
			
		||||
  verify-user: "ユーザーの公式アカウント設定"
 | 
			
		||||
  verify: "公式アカウントにする"
 | 
			
		||||
  verified: "公式アカウントにしました"
 | 
			
		||||
desktop/views/pages/admin/admin.unverify-user.vue:
 | 
			
		||||
  unverify-user: "ユーザーの公式アカウント解除"
 | 
			
		||||
  unverify: "公式アカウントを解除する"
 | 
			
		||||
  unverified: "公式アカウントを解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.announcements.vue:
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "絵文字の登録"
 | 
			
		||||
    name: "絵文字名"
 | 
			
		||||
    name-desc: "a~z 0~9 _ の文字が使えます。"
 | 
			
		||||
    aliases: "エイリアス"
 | 
			
		||||
    aliases-desc: "スペースで区切って複数設定できます。"
 | 
			
		||||
    url: "絵文字画像URL"
 | 
			
		||||
    add: "追加"
 | 
			
		||||
  emojis:
 | 
			
		||||
    title: "絵文字一覧"
 | 
			
		||||
    update: "更新"
 | 
			
		||||
    remove: "削除"
 | 
			
		||||
admin/views/announcements.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
desktop/views/pages/admin/admin.hashtags.vue:
 | 
			
		||||
  save: "保存"
 | 
			
		||||
  remove: "削除"
 | 
			
		||||
  add: "追加"
 | 
			
		||||
  title: "タイトル"
 | 
			
		||||
  text: "内容"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
desktop/views/pages/deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-only: "Tylko wpisy z zawartością multimedialną"
 | 
			
		||||
@@ -1328,6 +1374,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
  mark-as-read-all-unread-notes: "すべての投稿を既読にする"
 | 
			
		||||
  password: "パスワード"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "Śledzi Cię"
 | 
			
		||||
  following: "Śledzeni"
 | 
			
		||||
 
 | 
			
		||||
@@ -186,6 +186,7 @@ common:
 | 
			
		||||
    stack-left: "左に重ねる"
 | 
			
		||||
    pop-right: "Acoplar à direita"
 | 
			
		||||
  dev: "アプリの作成に失敗しました。再度お試しください。"
 | 
			
		||||
  ai-chan-kawaii: "藍ちゃかわいい"
 | 
			
		||||
auth/views/form.vue:
 | 
			
		||||
  share-access: "Você <b>permite</b> que <i>{{ app.name }}</i> acesse sua conta?"
 | 
			
		||||
  permission-ask: "Este aplicativo precisa das seguintes permissões:"
 | 
			
		||||
@@ -874,7 +875,7 @@ common/views/components/mute-and-block.vue:
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  no-muted-users: "ミュートしているユーザーはいません"
 | 
			
		||||
  no-blocked-users: "ブロックしているユーザーはいません"
 | 
			
		||||
desktop/views/components/settings.password.vue:
 | 
			
		||||
common/views/components/password-settings.vue:
 | 
			
		||||
  reset: "パスワードを変更する"
 | 
			
		||||
  enter-current-password: "現在のパスワードを入力してください"
 | 
			
		||||
  enter-new-password: "新しいパスワードを入力してください"
 | 
			
		||||
@@ -946,41 +947,86 @@ desktop/views/components/users-list-item.vue:
 | 
			
		||||
desktop/views/components/window.vue:
 | 
			
		||||
  popout: "ポップアウト"
 | 
			
		||||
  close: "閉じる"
 | 
			
		||||
desktop/views/pages/admin/admin.vue:
 | 
			
		||||
admin/views/index.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  users: "Usuários"
 | 
			
		||||
  update: "Actualizações"
 | 
			
		||||
  instance: "インスタンス"
 | 
			
		||||
  emoji: "カスタム絵文字"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  update: "更新"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
desktop/views/pages/admin/admin.dashboard.vue:
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  all-users: "Todos os usuários"
 | 
			
		||||
  original-users: "このインスタンスのユーザー"
 | 
			
		||||
  all-notes: "全ての投稿"
 | 
			
		||||
  original-notes: "このインスタンスの投稿"
 | 
			
		||||
  accounts: "アカウント"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
  invite: "招待"
 | 
			
		||||
  banner-url: "Banner URL"
 | 
			
		||||
  disableRegistration: "Disable new user registration"
 | 
			
		||||
  disableLocalTimeline: "Disable the local timeline"
 | 
			
		||||
desktop/views/pages/admin/admin.suspend-user.vue:
 | 
			
		||||
admin/views/charts.vue:
 | 
			
		||||
  title: "チャート"
 | 
			
		||||
  per-day: "1日ごと"
 | 
			
		||||
  per-hour: "1時間ごと"
 | 
			
		||||
  federation: "フェデレーション"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  network: "ネットワーク"
 | 
			
		||||
  charts:
 | 
			
		||||
    federation-instances: "インスタンスの増減"
 | 
			
		||||
    federation-instances-total: "インスタンスの積算"
 | 
			
		||||
    notes: "投稿の増減 (統合)"
 | 
			
		||||
    local-notes: "投稿の増減 (ローカル)"
 | 
			
		||||
    remote-notes: "投稿の増減 (リモート)"
 | 
			
		||||
    notes-total: "投稿の積算"
 | 
			
		||||
    users: "ユーザーの増減"
 | 
			
		||||
    users-total: "ユーザーの積算"
 | 
			
		||||
    drive: "ドライブ使用量の増減"
 | 
			
		||||
    drive-total: "ドライブ使用量の積算"
 | 
			
		||||
    drive-files: "ドライブのファイル数の増減"
 | 
			
		||||
    drive-files-total: "ドライブのファイル数の積算"
 | 
			
		||||
    network-requests: "リクエスト"
 | 
			
		||||
    network-time: "応答時間"
 | 
			
		||||
    network-usage: "通信量"
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  suspend-user: "ユーザーの凍結"
 | 
			
		||||
  suspend: "凍結"
 | 
			
		||||
  suspended: "凍結しました"
 | 
			
		||||
desktop/views/pages/admin/admin.unsuspend-user.vue:
 | 
			
		||||
  unsuspend-user: "ユーザーの凍結の解除"
 | 
			
		||||
  unsuspend: "凍結の解除"
 | 
			
		||||
  unsuspended: "凍結を解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.verify-user.vue:
 | 
			
		||||
  verify-user: "ユーザーの公式アカウント設定"
 | 
			
		||||
  verify: "公式アカウントにする"
 | 
			
		||||
  verified: "公式アカウントにしました"
 | 
			
		||||
desktop/views/pages/admin/admin.unverify-user.vue:
 | 
			
		||||
  unverify-user: "ユーザーの公式アカウント解除"
 | 
			
		||||
  unverify: "公式アカウントを解除する"
 | 
			
		||||
  unverified: "公式アカウントを解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.announcements.vue:
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "絵文字の登録"
 | 
			
		||||
    name: "絵文字名"
 | 
			
		||||
    name-desc: "a~z 0~9 _ の文字が使えます。"
 | 
			
		||||
    aliases: "エイリアス"
 | 
			
		||||
    aliases-desc: "スペースで区切って複数設定できます。"
 | 
			
		||||
    url: "絵文字画像URL"
 | 
			
		||||
    add: "追加"
 | 
			
		||||
  emojis:
 | 
			
		||||
    title: "絵文字一覧"
 | 
			
		||||
    update: "更新"
 | 
			
		||||
    remove: "削除"
 | 
			
		||||
admin/views/announcements.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
desktop/views/pages/admin/admin.hashtags.vue:
 | 
			
		||||
  save: "保存"
 | 
			
		||||
  remove: "削除"
 | 
			
		||||
  add: "追加"
 | 
			
		||||
  title: "タイトル"
 | 
			
		||||
  text: "内容"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
desktop/views/pages/deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-only: "メディア投稿のみ"
 | 
			
		||||
@@ -1328,6 +1374,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  sound: "Sons"
 | 
			
		||||
  enable-sounds: "Ativar sons"
 | 
			
		||||
  mark-as-read-all-unread-notes: "すべての投稿を既読にする"
 | 
			
		||||
  password: "パスワード"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "Te segue"
 | 
			
		||||
  following: "Seguindo"
 | 
			
		||||
 
 | 
			
		||||
@@ -186,6 +186,7 @@ common:
 | 
			
		||||
    stack-left: "左に重ねる"
 | 
			
		||||
    pop-right: "右に出す"
 | 
			
		||||
  dev: "アプリの作成に失敗しました。再度お試しください。"
 | 
			
		||||
  ai-chan-kawaii: "藍ちゃかわいい"
 | 
			
		||||
auth/views/form.vue:
 | 
			
		||||
  share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
 | 
			
		||||
  permission-ask: "このアプリは次の権限を要求しています:"
 | 
			
		||||
@@ -874,7 +875,7 @@ common/views/components/mute-and-block.vue:
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  no-muted-users: "ミュートしているユーザーはいません"
 | 
			
		||||
  no-blocked-users: "ブロックしているユーザーはいません"
 | 
			
		||||
desktop/views/components/settings.password.vue:
 | 
			
		||||
common/views/components/password-settings.vue:
 | 
			
		||||
  reset: "パスワードを変更する"
 | 
			
		||||
  enter-current-password: "現在のパスワードを入力してください"
 | 
			
		||||
  enter-new-password: "新しいパスワードを入力してください"
 | 
			
		||||
@@ -946,41 +947,86 @@ desktop/views/components/users-list-item.vue:
 | 
			
		||||
desktop/views/components/window.vue:
 | 
			
		||||
  popout: "ポップアウト"
 | 
			
		||||
  close: "閉じる"
 | 
			
		||||
desktop/views/pages/admin/admin.vue:
 | 
			
		||||
admin/views/index.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  instance: "インスタンス"
 | 
			
		||||
  emoji: "カスタム絵文字"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  update: "更新"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
desktop/views/pages/admin/admin.dashboard.vue:
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  all-users: "全てのユーザー"
 | 
			
		||||
  original-users: "このインスタンスのユーザー"
 | 
			
		||||
  all-notes: "全ての投稿"
 | 
			
		||||
  original-notes: "このインスタンスの投稿"
 | 
			
		||||
  accounts: "アカウント"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
  invite: "招待"
 | 
			
		||||
  banner-url: "Banner URL"
 | 
			
		||||
  disableRegistration: "Disable new user registration"
 | 
			
		||||
  disableLocalTimeline: "Disable the local timeline"
 | 
			
		||||
desktop/views/pages/admin/admin.suspend-user.vue:
 | 
			
		||||
admin/views/charts.vue:
 | 
			
		||||
  title: "チャート"
 | 
			
		||||
  per-day: "1日ごと"
 | 
			
		||||
  per-hour: "1時間ごと"
 | 
			
		||||
  federation: "フェデレーション"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  network: "ネットワーク"
 | 
			
		||||
  charts:
 | 
			
		||||
    federation-instances: "インスタンスの増減"
 | 
			
		||||
    federation-instances-total: "インスタンスの積算"
 | 
			
		||||
    notes: "投稿の増減 (統合)"
 | 
			
		||||
    local-notes: "投稿の増減 (ローカル)"
 | 
			
		||||
    remote-notes: "投稿の増減 (リモート)"
 | 
			
		||||
    notes-total: "投稿の積算"
 | 
			
		||||
    users: "ユーザーの増減"
 | 
			
		||||
    users-total: "ユーザーの積算"
 | 
			
		||||
    drive: "ドライブ使用量の増減"
 | 
			
		||||
    drive-total: "ドライブ使用量の積算"
 | 
			
		||||
    drive-files: "ドライブのファイル数の増減"
 | 
			
		||||
    drive-files-total: "ドライブのファイル数の積算"
 | 
			
		||||
    network-requests: "リクエスト"
 | 
			
		||||
    network-time: "応答時間"
 | 
			
		||||
    network-usage: "通信量"
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  suspend-user: "ユーザーの凍結"
 | 
			
		||||
  suspend: "凍結"
 | 
			
		||||
  suspended: "凍結しました"
 | 
			
		||||
desktop/views/pages/admin/admin.unsuspend-user.vue:
 | 
			
		||||
  unsuspend-user: "ユーザーの凍結の解除"
 | 
			
		||||
  unsuspend: "凍結の解除"
 | 
			
		||||
  unsuspended: "凍結を解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.verify-user.vue:
 | 
			
		||||
  verify-user: "ユーザーの公式アカウント設定"
 | 
			
		||||
  verify: "公式アカウントにする"
 | 
			
		||||
  verified: "公式アカウントにしました"
 | 
			
		||||
desktop/views/pages/admin/admin.unverify-user.vue:
 | 
			
		||||
  unverify-user: "ユーザーの公式アカウント解除"
 | 
			
		||||
  unverify: "公式アカウントを解除する"
 | 
			
		||||
  unverified: "公式アカウントを解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.announcements.vue:
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "絵文字の登録"
 | 
			
		||||
    name: "絵文字名"
 | 
			
		||||
    name-desc: "a~z 0~9 _ の文字が使えます。"
 | 
			
		||||
    aliases: "エイリアス"
 | 
			
		||||
    aliases-desc: "スペースで区切って複数設定できます。"
 | 
			
		||||
    url: "絵文字画像URL"
 | 
			
		||||
    add: "追加"
 | 
			
		||||
  emojis:
 | 
			
		||||
    title: "絵文字一覧"
 | 
			
		||||
    update: "更新"
 | 
			
		||||
    remove: "削除"
 | 
			
		||||
admin/views/announcements.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
desktop/views/pages/admin/admin.hashtags.vue:
 | 
			
		||||
  save: "保存"
 | 
			
		||||
  remove: "削除"
 | 
			
		||||
  add: "追加"
 | 
			
		||||
  title: "タイトル"
 | 
			
		||||
  text: "内容"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
desktop/views/pages/deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-only: "メディア投稿のみ"
 | 
			
		||||
@@ -1328,6 +1374,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
  mark-as-read-all-unread-notes: "すべての投稿を既読にする"
 | 
			
		||||
  password: "パスワード"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
 
 | 
			
		||||
@@ -186,6 +186,7 @@ common:
 | 
			
		||||
    stack-left: "左に重ねる"
 | 
			
		||||
    pop-right: "右に出す"
 | 
			
		||||
  dev: "アプリの作成に失敗しました。再度お試しください。"
 | 
			
		||||
  ai-chan-kawaii: "藍ちゃかわいい"
 | 
			
		||||
auth/views/form.vue:
 | 
			
		||||
  share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
 | 
			
		||||
  permission-ask: "このアプリは次の権限を要求しています:"
 | 
			
		||||
@@ -874,7 +875,7 @@ common/views/components/mute-and-block.vue:
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  no-muted-users: "ミュートしているユーザーはいません"
 | 
			
		||||
  no-blocked-users: "ブロックしているユーザーはいません"
 | 
			
		||||
desktop/views/components/settings.password.vue:
 | 
			
		||||
common/views/components/password-settings.vue:
 | 
			
		||||
  reset: "パスワードを変更する"
 | 
			
		||||
  enter-current-password: "現在のパスワードを入力してください"
 | 
			
		||||
  enter-new-password: "新しいパスワードを入力してください"
 | 
			
		||||
@@ -946,41 +947,86 @@ desktop/views/components/users-list-item.vue:
 | 
			
		||||
desktop/views/components/window.vue:
 | 
			
		||||
  popout: "ポップアウト"
 | 
			
		||||
  close: "閉じる"
 | 
			
		||||
desktop/views/pages/admin/admin.vue:
 | 
			
		||||
admin/views/index.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  instance: "インスタンス"
 | 
			
		||||
  emoji: "カスタム絵文字"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  update: "更新"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
desktop/views/pages/admin/admin.dashboard.vue:
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
  all-users: "全てのユーザー"
 | 
			
		||||
  original-users: "このインスタンスのユーザー"
 | 
			
		||||
  all-notes: "全ての投稿"
 | 
			
		||||
  original-notes: "このインスタンスの投稿"
 | 
			
		||||
  accounts: "アカウント"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
  invite: "招待"
 | 
			
		||||
  banner-url: "Banner URL"
 | 
			
		||||
  disableRegistration: "Disable new user registration"
 | 
			
		||||
  disableLocalTimeline: "Disable the local timeline"
 | 
			
		||||
desktop/views/pages/admin/admin.suspend-user.vue:
 | 
			
		||||
admin/views/charts.vue:
 | 
			
		||||
  title: "チャート"
 | 
			
		||||
  per-day: "1日ごと"
 | 
			
		||||
  per-hour: "1時間ごと"
 | 
			
		||||
  federation: "フェデレーション"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  network: "ネットワーク"
 | 
			
		||||
  charts:
 | 
			
		||||
    federation-instances: "インスタンスの増減"
 | 
			
		||||
    federation-instances-total: "インスタンスの積算"
 | 
			
		||||
    notes: "投稿の増減 (統合)"
 | 
			
		||||
    local-notes: "投稿の増減 (ローカル)"
 | 
			
		||||
    remote-notes: "投稿の増減 (リモート)"
 | 
			
		||||
    notes-total: "投稿の積算"
 | 
			
		||||
    users: "ユーザーの増減"
 | 
			
		||||
    users-total: "ユーザーの積算"
 | 
			
		||||
    drive: "ドライブ使用量の増減"
 | 
			
		||||
    drive-total: "ドライブ使用量の積算"
 | 
			
		||||
    drive-files: "ドライブのファイル数の増減"
 | 
			
		||||
    drive-files-total: "ドライブのファイル数の積算"
 | 
			
		||||
    network-requests: "リクエスト"
 | 
			
		||||
    network-time: "応答時間"
 | 
			
		||||
    network-usage: "通信量"
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  suspend-user: "ユーザーの凍結"
 | 
			
		||||
  suspend: "凍結"
 | 
			
		||||
  suspended: "凍結しました"
 | 
			
		||||
desktop/views/pages/admin/admin.unsuspend-user.vue:
 | 
			
		||||
  unsuspend-user: "ユーザーの凍結の解除"
 | 
			
		||||
  unsuspend: "凍結の解除"
 | 
			
		||||
  unsuspended: "凍結を解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.verify-user.vue:
 | 
			
		||||
  verify-user: "ユーザーの公式アカウント設定"
 | 
			
		||||
  verify: "公式アカウントにする"
 | 
			
		||||
  verified: "公式アカウントにしました"
 | 
			
		||||
desktop/views/pages/admin/admin.unverify-user.vue:
 | 
			
		||||
  unverify-user: "ユーザーの公式アカウント解除"
 | 
			
		||||
  unverify: "公式アカウントを解除する"
 | 
			
		||||
  unverified: "公式アカウントを解除しました"
 | 
			
		||||
desktop/views/pages/admin/admin.announcements.vue:
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "絵文字の登録"
 | 
			
		||||
    name: "絵文字名"
 | 
			
		||||
    name-desc: "a~z 0~9 _ の文字が使えます。"
 | 
			
		||||
    aliases: "エイリアス"
 | 
			
		||||
    aliases-desc: "スペースで区切って複数設定できます。"
 | 
			
		||||
    url: "絵文字画像URL"
 | 
			
		||||
    add: "追加"
 | 
			
		||||
  emojis:
 | 
			
		||||
    title: "絵文字一覧"
 | 
			
		||||
    update: "更新"
 | 
			
		||||
    remove: "削除"
 | 
			
		||||
admin/views/announcements.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
desktop/views/pages/admin/admin.hashtags.vue:
 | 
			
		||||
  save: "保存"
 | 
			
		||||
  remove: "削除"
 | 
			
		||||
  add: "追加"
 | 
			
		||||
  title: "タイトル"
 | 
			
		||||
  text: "内容"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
desktop/views/pages/deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-only: "メディア投稿のみ"
 | 
			
		||||
@@ -1328,6 +1374,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
  mark-as-read-all-unread-notes: "すべての投稿を既読にする"
 | 
			
		||||
  password: "パスワード"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										77
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										77
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "misskey",
 | 
			
		||||
	"version": "10.36.0",
 | 
			
		||||
	"version": "10.37.0",
 | 
			
		||||
	"lockfileVersion": 1,
 | 
			
		||||
	"requires": true,
 | 
			
		||||
	"dependencies": {
 | 
			
		||||
@@ -563,9 +563,9 @@
 | 
			
		||||
			"integrity": "sha512-OftRLCgAzJP7vmKn9by/GVjnf4hloz/pXNOwPo0vKGAfXI7GqWXJi9N2kRar4cP5s1dGwuwcagWqO6iHBTq1Mg=="
 | 
			
		||||
		},
 | 
			
		||||
		"@types/node": {
 | 
			
		||||
			"version": "10.12.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.0.tgz",
 | 
			
		||||
			"integrity": "sha512-3TUHC3jsBAB7qVRGxT6lWyYo2v96BMmD2PTcl47H25Lu7UXtFH/2qqmKiVrnel6Ne//0TFYf6uvNX+HW2FRkLQ=="
 | 
			
		||||
			"version": "10.12.2",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.2.tgz",
 | 
			
		||||
			"integrity": "sha512-53ElVDSnZeFUUFIYzI8WLQ25IhWzb6vbddNp8UHlXQyU0ET2RhV5zg0NfubzU7iNMh5bBXb0htCzfvrSVNgzaQ=="
 | 
			
		||||
		},
 | 
			
		||||
		"@types/orchestrator": {
 | 
			
		||||
			"version": "0.3.2",
 | 
			
		||||
@@ -626,9 +626,9 @@
 | 
			
		||||
			"integrity": "sha1-a9p9uGU/piZD9e5p6facEaOS46Y="
 | 
			
		||||
		},
 | 
			
		||||
		"@types/request": {
 | 
			
		||||
			"version": "2.47.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/@types/request/-/request-2.47.1.tgz",
 | 
			
		||||
			"integrity": "sha512-TV3XLvDjQbIeVxJ1Z3oCTDk/KuYwwcNKVwz2YaT0F5u86Prgc4syDAp6P96rkTQQ4bIdh+VswQIC9zS6NjY7/g==",
 | 
			
		||||
			"version": "2.48.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.0.tgz",
 | 
			
		||||
			"integrity": "sha512-KnfoOtqXKllSqfXSEvGTd8KDkNlpHs+PWr6I6XiEIWk/jckH3pNmWDXNFZyPkB9wApb8vzDq2wMByM/0GFSmXg==",
 | 
			
		||||
			"requires": {
 | 
			
		||||
				"@types/caseless": "*",
 | 
			
		||||
				"@types/form-data": "*",
 | 
			
		||||
@@ -736,9 +736,9 @@
 | 
			
		||||
			"integrity": "sha1-EHPEvIJHVK49EM+riKsCN7qWTk0="
 | 
			
		||||
		},
 | 
			
		||||
		"@types/tough-cookie": {
 | 
			
		||||
			"version": "2.3.3",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.3.tgz",
 | 
			
		||||
			"integrity": "sha512-MDQLxNFRLasqS4UlkWMSACMKeSm1x4Q3TxzUC7KQUsh6RK1ZrQ0VEyE3yzXcBu+K8ejVj4wuX32eUG02yNp+YQ=="
 | 
			
		||||
			"version": "2.3.4",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.4.tgz",
 | 
			
		||||
			"integrity": "sha512-Set5ZdrAaKI/qHdFlVMgm/GsAv/wkXhSTuZFkJ+JI7HK+wIkIlOaUXSXieIvJ0+OvGIqtREFoE+NHJtEq0gtEw=="
 | 
			
		||||
		},
 | 
			
		||||
		"@types/uglify-js": {
 | 
			
		||||
			"version": "3.0.4",
 | 
			
		||||
@@ -2250,9 +2250,9 @@
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"cafy": {
 | 
			
		||||
			"version": "11.3.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/cafy/-/cafy-11.3.0.tgz",
 | 
			
		||||
			"integrity": "sha512-7kqqF4I6seSNSAWihRfnM78wP/OwaZMrCNIUzu0+TC1pDGfF2uoVfMsAJ1oV1jZsZ2L2qlUSvo9zhSEIouS/xQ=="
 | 
			
		||||
			"version": "12.0.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/cafy/-/cafy-12.0.0.tgz",
 | 
			
		||||
			"integrity": "sha512-HGsunRfyqFyG1/oh+Szw8GtVpj4pwehyqmp8sTO1QwDF3htjDP+vVBWzg7iOU2Y3Cm+h+UiEpf6DJ0p57RNmAg=="
 | 
			
		||||
		},
 | 
			
		||||
		"caller-path": {
 | 
			
		||||
			"version": "0.1.0",
 | 
			
		||||
@@ -3097,15 +3097,15 @@
 | 
			
		||||
			"integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA="
 | 
			
		||||
		},
 | 
			
		||||
		"css-loader": {
 | 
			
		||||
			"version": "1.0.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-1.0.0.tgz",
 | 
			
		||||
			"integrity": "sha512-tMXlTYf3mIMt3b0dDCOQFJiVvxbocJ5Ho577WiGPYPZcqVEO218L2iU22pDXzkTZCLDE+9AmGSUkWxeh/nZReA==",
 | 
			
		||||
			"version": "1.0.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-1.0.1.tgz",
 | 
			
		||||
			"integrity": "sha512-+ZHAZm/yqvJ2kDtPne3uX0C+Vr3Zn5jFn2N4HywtS5ujwvsVkyg0VArEXpl3BgczDA8anieki1FIzhchX4yrDw==",
 | 
			
		||||
			"requires": {
 | 
			
		||||
				"babel-code-frame": "^6.26.0",
 | 
			
		||||
				"css-selector-tokenizer": "^0.7.0",
 | 
			
		||||
				"icss-utils": "^2.1.0",
 | 
			
		||||
				"loader-utils": "^1.0.2",
 | 
			
		||||
				"lodash.camelcase": "^4.3.0",
 | 
			
		||||
				"lodash": "^4.17.11",
 | 
			
		||||
				"postcss": "^6.0.23",
 | 
			
		||||
				"postcss-modules-extract-imports": "^1.2.0",
 | 
			
		||||
				"postcss-modules-local-by-default": "^1.2.0",
 | 
			
		||||
@@ -3137,9 +3137,9 @@
 | 
			
		||||
			"integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w=="
 | 
			
		||||
		},
 | 
			
		||||
		"css-selector-tokenizer": {
 | 
			
		||||
			"version": "0.7.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz",
 | 
			
		||||
			"integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=",
 | 
			
		||||
			"version": "0.7.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz",
 | 
			
		||||
			"integrity": "sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA==",
 | 
			
		||||
			"requires": {
 | 
			
		||||
				"cssesc": "^0.1.0",
 | 
			
		||||
				"fastparse": "^1.1.1",
 | 
			
		||||
@@ -4504,9 +4504,9 @@
 | 
			
		||||
			"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
 | 
			
		||||
		},
 | 
			
		||||
		"elasticsearch": {
 | 
			
		||||
			"version": "15.1.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/elasticsearch/-/elasticsearch-15.1.1.tgz",
 | 
			
		||||
			"integrity": "sha512-Yr9xy10rUMjDty7qCys7X9AIW5+PX4Gtv2NksZqXIc+AZiWna/y2QwZdiSLtb5LTOKDp7PbegfuokhIjMHUpKw==",
 | 
			
		||||
			"version": "15.2.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/elasticsearch/-/elasticsearch-15.2.0.tgz",
 | 
			
		||||
			"integrity": "sha512-jOFcBoEh3Sn3gjUTozInODZTLriJtfppAUC7jnQCUE+OUj8o7GoAyC+L4h/L3ZxmXNFbQCunqVR+nmSofHdo9A==",
 | 
			
		||||
			"requires": {
 | 
			
		||||
				"agentkeepalive": "^3.4.1",
 | 
			
		||||
				"chalk": "^1.0.0",
 | 
			
		||||
@@ -5275,9 +5275,9 @@
 | 
			
		||||
			"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
 | 
			
		||||
		},
 | 
			
		||||
		"fastparse": {
 | 
			
		||||
			"version": "1.1.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz",
 | 
			
		||||
			"integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg="
 | 
			
		||||
			"version": "1.1.2",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
 | 
			
		||||
			"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ=="
 | 
			
		||||
		},
 | 
			
		||||
		"fd-slicer": {
 | 
			
		||||
			"version": "1.1.0",
 | 
			
		||||
@@ -5314,9 +5314,9 @@
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"file-type": {
 | 
			
		||||
			"version": "10.2.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/file-type/-/file-type-10.2.0.tgz",
 | 
			
		||||
			"integrity": "sha512-eqX81S1PWdLDPW39yyB214TVVOsUQjSmPcyUjeVH6ksH+94Y2YA/ItiIwa53rJiSofJZLK6lGsuCE3rwt0vp4w=="
 | 
			
		||||
			"version": "10.3.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/file-type/-/file-type-10.3.0.tgz",
 | 
			
		||||
			"integrity": "sha512-wk3yZ4wav7qrpJJuDfW3zSYCoxA/ZWZ8YtvrFYcbAE8jSXGFEej7jWVqFKWeeNqFIlG3B3o+fzoSKC6HJvdWUg=="
 | 
			
		||||
		},
 | 
			
		||||
		"filename-regex": {
 | 
			
		||||
			"version": "2.0.1",
 | 
			
		||||
@@ -9040,11 +9040,6 @@
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz",
 | 
			
		||||
			"integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU="
 | 
			
		||||
		},
 | 
			
		||||
		"lodash.camelcase": {
 | 
			
		||||
			"version": "4.3.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
 | 
			
		||||
			"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY="
 | 
			
		||||
		},
 | 
			
		||||
		"lodash.clone": {
 | 
			
		||||
			"version": "4.5.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz",
 | 
			
		||||
@@ -11989,9 +11984,9 @@
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"postcss-modules-extract-imports": {
 | 
			
		||||
			"version": "1.2.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz",
 | 
			
		||||
			"integrity": "sha1-ZhQOzs447wa/DT41XWm/WdFB6oU=",
 | 
			
		||||
			"version": "1.2.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz",
 | 
			
		||||
			"integrity": "sha512-6jt9XZwUhwmRUhb/CkyJY020PYaPJsCyt3UjbaWo6XEbH/94Hmv6MP7fG2C5NDU/BcHzyGYxNtHvM+LTf9HrYw==",
 | 
			
		||||
			"requires": {
 | 
			
		||||
				"postcss": "^6.0.1"
 | 
			
		||||
			}
 | 
			
		||||
@@ -15687,9 +15682,9 @@
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"typescript": {
 | 
			
		||||
			"version": "3.1.4",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.4.tgz",
 | 
			
		||||
			"integrity": "sha512-JZHJtA6ZL15+Q3Dqkbh8iCUmvxD3iJ7ujXS+fVkKnwIVAdHc5BJTDNM0aTrnr2luKulFjU7W+SRhDZvi66Ru7Q=="
 | 
			
		||||
			"version": "3.1.5",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.5.tgz",
 | 
			
		||||
			"integrity": "sha512-muYNWV9j5+3mXoKD6oPONKuGUmYiFX14gfo9lWm9ZXRHOqVDQiB4q1CzFPbF4QLV2E9TZXH6oK55oQ94rn3PpA=="
 | 
			
		||||
		},
 | 
			
		||||
		"typescript-eslint-parser": {
 | 
			
		||||
			"version": "20.1.1",
 | 
			
		||||
@@ -15830,7 +15825,7 @@
 | 
			
		||||
				},
 | 
			
		||||
				"fast-deep-equal": {
 | 
			
		||||
					"version": "1.1.0",
 | 
			
		||||
					"resolved": "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
 | 
			
		||||
					"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
 | 
			
		||||
					"integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ="
 | 
			
		||||
				},
 | 
			
		||||
				"ignore": {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								package.json
									
									
									
									
									
								
							@@ -1,8 +1,8 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "misskey",
 | 
			
		||||
	"author": "syuilo <i@syuilo.com>",
 | 
			
		||||
	"version": "10.37.0",
 | 
			
		||||
	"clientVersion": "1.0.11314",
 | 
			
		||||
	"version": "10.38.1",
 | 
			
		||||
	"clientVersion": "1.0.11482",
 | 
			
		||||
	"codename": "nighthike",
 | 
			
		||||
	"main": "./built/index.js",
 | 
			
		||||
	"private": true,
 | 
			
		||||
@@ -61,13 +61,13 @@
 | 
			
		||||
		"@types/mocha": "5.2.5",
 | 
			
		||||
		"@types/mongodb": "3.1.12",
 | 
			
		||||
		"@types/ms": "0.7.30",
 | 
			
		||||
		"@types/node": "10.12.0",
 | 
			
		||||
		"@types/node": "10.12.2",
 | 
			
		||||
		"@types/portscanner": "2.1.0",
 | 
			
		||||
		"@types/pug": "2.0.4",
 | 
			
		||||
		"@types/qrcode": "1.3.0",
 | 
			
		||||
		"@types/ratelimiter": "2.1.28",
 | 
			
		||||
		"@types/redis": "2.8.7",
 | 
			
		||||
		"@types/request": "2.47.1",
 | 
			
		||||
		"@types/request": "2.48.0",
 | 
			
		||||
		"@types/request-promise-native": "1.0.15",
 | 
			
		||||
		"@types/rimraf": "2.0.2",
 | 
			
		||||
		"@types/seedrandom": "2.4.27",
 | 
			
		||||
@@ -91,28 +91,28 @@
 | 
			
		||||
		"bcryptjs": "2.4.3",
 | 
			
		||||
		"bee-queue": "1.2.2",
 | 
			
		||||
		"bootstrap-vue": "2.0.0-rc.11",
 | 
			
		||||
		"cafy": "11.3.0",
 | 
			
		||||
		"cafy": "12.0.0",
 | 
			
		||||
		"chai": "4.2.0",
 | 
			
		||||
		"chai-http": "4.2.0",
 | 
			
		||||
		"chalk": "2.4.1",
 | 
			
		||||
		"chart.js": "2.7.3",
 | 
			
		||||
		"commander": "2.19.0",
 | 
			
		||||
		"crc-32": "1.2.0",
 | 
			
		||||
		"css-loader": "1.0.0",
 | 
			
		||||
		"css-loader": "1.0.1",
 | 
			
		||||
		"dateformat": "3.0.3",
 | 
			
		||||
		"debug": "4.1.0",
 | 
			
		||||
		"deep-equal": "1.0.1",
 | 
			
		||||
		"deepcopy": "0.6.3",
 | 
			
		||||
		"diskusage": "0.2.5",
 | 
			
		||||
		"double-ended-queue": "2.1.0-0",
 | 
			
		||||
		"elasticsearch": "15.1.1",
 | 
			
		||||
		"elasticsearch": "15.2.0",
 | 
			
		||||
		"emojilib": "2.3.0",
 | 
			
		||||
		"escape-regexp": "0.0.1",
 | 
			
		||||
		"eslint": "5.8.0",
 | 
			
		||||
		"eslint-plugin-vue": "4.7.1",
 | 
			
		||||
		"eventemitter3": "3.1.0",
 | 
			
		||||
		"file-loader": "2.0.0",
 | 
			
		||||
		"file-type": "10.2.0",
 | 
			
		||||
		"file-type": "10.3.0",
 | 
			
		||||
		"fuckadblock": "3.2.1",
 | 
			
		||||
		"gulp": "3.9.1",
 | 
			
		||||
		"gulp-cssnano": "2.1.3",
 | 
			
		||||
@@ -204,7 +204,7 @@
 | 
			
		||||
		"ts-loader": "5.3.0",
 | 
			
		||||
		"ts-node": "7.0.1",
 | 
			
		||||
		"tslint": "5.10.0",
 | 
			
		||||
		"typescript": "3.1.4",
 | 
			
		||||
		"typescript": "3.1.5",
 | 
			
		||||
		"typescript-eslint-parser": "20.1.1",
 | 
			
		||||
		"uglify-es": "3.3.9",
 | 
			
		||||
		"url-loader": "1.1.2",
 | 
			
		||||
 
 | 
			
		||||
@@ -230,7 +230,7 @@ export default abstract class Chart<T> {
 | 
			
		||||
			null;
 | 
			
		||||
 | 
			
		||||
		// ログ取得
 | 
			
		||||
		const logs = await this.collection.find({
 | 
			
		||||
		let logs = await this.collection.find({
 | 
			
		||||
			group: group,
 | 
			
		||||
			span: span,
 | 
			
		||||
			date: {
 | 
			
		||||
@@ -245,6 +245,27 @@ export default abstract class Chart<T> {
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		// 要求された範囲にログがひとつもなかったら
 | 
			
		||||
		if (logs.length == 0) {
 | 
			
		||||
			// もっとも新しいログを持ってくる
 | 
			
		||||
			// (すくなくともひとつログが無いと隙間埋めできないため)
 | 
			
		||||
			const recentLog = await this.collection.findOne({
 | 
			
		||||
				group: group,
 | 
			
		||||
				span: span
 | 
			
		||||
			}, {
 | 
			
		||||
				sort: {
 | 
			
		||||
					date: -1
 | 
			
		||||
				},
 | 
			
		||||
				fields: {
 | 
			
		||||
					_id: 0
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			if (recentLog) {
 | 
			
		||||
				logs = [recentLog];
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 整形
 | 
			
		||||
		for (let i = (range - 1); i >= 0; i--) {
 | 
			
		||||
			const current =
 | 
			
		||||
@@ -269,14 +290,11 @@ export default abstract class Chart<T> {
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * [{
 | 
			
		||||
		 * 	xxxxx: 1,
 | 
			
		||||
		 * 	yyyyy: 5
 | 
			
		||||
		 * 	xxxxx: 1, yyyyy: 5
 | 
			
		||||
		 * }, {
 | 
			
		||||
		 * 	xxxxx: 2,
 | 
			
		||||
		 * 	yyyyy: 6
 | 
			
		||||
		 * 	xxxxx: 2, yyyyy: 6
 | 
			
		||||
		 * }, {
 | 
			
		||||
		 * 	xxxxx: 3,
 | 
			
		||||
		 * 	yyyyy: 7
 | 
			
		||||
		 * 	xxxxx: 3, yyyyy: 7
 | 
			
		||||
		 * }]
 | 
			
		||||
		 *
 | 
			
		||||
		 * を
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										150
									
								
								src/client/app/admin/assets/header-icon.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								src/client/app/admin/assets/header-icon.svg
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,150 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
 | 
			
		||||
 | 
			
		||||
<svg
 | 
			
		||||
   xmlns:dc="http://purl.org/dc/elements/1.1/"
 | 
			
		||||
   xmlns:cc="http://creativecommons.org/ns#"
 | 
			
		||||
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 | 
			
		||||
   xmlns:svg="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 | 
			
		||||
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 | 
			
		||||
   width="512"
 | 
			
		||||
   height="512"
 | 
			
		||||
   viewBox="0 0 135.46667 135.46667"
 | 
			
		||||
   version="1.1"
 | 
			
		||||
   id="svg8"
 | 
			
		||||
   inkscape:version="0.92.1 r15371"
 | 
			
		||||
   sodipodi:docname="header-icon.dark.svg"
 | 
			
		||||
   inkscape:export-filename="C:\Users\syuilo\projects\misskey\assets\favicon\32.png"
 | 
			
		||||
   inkscape:export-xdpi="6"
 | 
			
		||||
   inkscape:export-ydpi="6">
 | 
			
		||||
  <defs
 | 
			
		||||
     id="defs2">
 | 
			
		||||
    <inkscape:path-effect
 | 
			
		||||
       effect="simplify"
 | 
			
		||||
       id="path-effect5115"
 | 
			
		||||
       is_visible="true"
 | 
			
		||||
       steps="1"
 | 
			
		||||
       threshold="0.000408163"
 | 
			
		||||
       smooth_angles="360"
 | 
			
		||||
       helper_size="0"
 | 
			
		||||
       simplify_individual_paths="false"
 | 
			
		||||
       simplify_just_coalesce="false"
 | 
			
		||||
       simplifyindividualpaths="false"
 | 
			
		||||
       simplifyJustCoalesce="false" />
 | 
			
		||||
    <inkscape:path-effect
 | 
			
		||||
       effect="simplify"
 | 
			
		||||
       id="path-effect5111"
 | 
			
		||||
       is_visible="true"
 | 
			
		||||
       steps="1"
 | 
			
		||||
       threshold="0.000408163"
 | 
			
		||||
       smooth_angles="360"
 | 
			
		||||
       helper_size="0"
 | 
			
		||||
       simplify_individual_paths="false"
 | 
			
		||||
       simplify_just_coalesce="false"
 | 
			
		||||
       simplifyindividualpaths="false"
 | 
			
		||||
       simplifyJustCoalesce="false" />
 | 
			
		||||
    <inkscape:path-effect
 | 
			
		||||
       effect="simplify"
 | 
			
		||||
       id="path-effect5104"
 | 
			
		||||
       is_visible="true"
 | 
			
		||||
       steps="1"
 | 
			
		||||
       threshold="0.000408163"
 | 
			
		||||
       smooth_angles="360"
 | 
			
		||||
       helper_size="0"
 | 
			
		||||
       simplify_individual_paths="false"
 | 
			
		||||
       simplify_just_coalesce="false"
 | 
			
		||||
       simplifyindividualpaths="false"
 | 
			
		||||
       simplifyJustCoalesce="false" />
 | 
			
		||||
  </defs>
 | 
			
		||||
  <sodipodi:namedview
 | 
			
		||||
     id="base"
 | 
			
		||||
     pagecolor="#ffffff"
 | 
			
		||||
     bordercolor="#666666"
 | 
			
		||||
     borderopacity="1.0"
 | 
			
		||||
     inkscape:pageopacity="0.0"
 | 
			
		||||
     inkscape:pageshadow="2"
 | 
			
		||||
     inkscape:zoom="1.4142136"
 | 
			
		||||
     inkscape:cx="114.309"
 | 
			
		||||
     inkscape:cy="251.50613"
 | 
			
		||||
     inkscape:document-units="px"
 | 
			
		||||
     inkscape:current-layer="g4502"
 | 
			
		||||
     showgrid="true"
 | 
			
		||||
     units="px"
 | 
			
		||||
     inkscape:snap-bbox="true"
 | 
			
		||||
     inkscape:bbox-nodes="true"
 | 
			
		||||
     inkscape:snap-bbox-edge-midpoints="false"
 | 
			
		||||
     inkscape:snap-smooth-nodes="true"
 | 
			
		||||
     inkscape:snap-center="true"
 | 
			
		||||
     inkscape:snap-page="true"
 | 
			
		||||
     inkscape:window-width="1920"
 | 
			
		||||
     inkscape:window-height="1027"
 | 
			
		||||
     inkscape:window-x="-8"
 | 
			
		||||
     inkscape:window-y="1072"
 | 
			
		||||
     inkscape:window-maximized="1"
 | 
			
		||||
     inkscape:snap-object-midpoints="true"
 | 
			
		||||
     inkscape:snap-midpoints="true"
 | 
			
		||||
     inkscape:object-paths="true"
 | 
			
		||||
     fit-margin-top="0"
 | 
			
		||||
     fit-margin-left="0"
 | 
			
		||||
     fit-margin-right="0"
 | 
			
		||||
     fit-margin-bottom="0"
 | 
			
		||||
     objecttolerance="1"
 | 
			
		||||
     guidetolerance="1"
 | 
			
		||||
     inkscape:snap-nodes="false"
 | 
			
		||||
     inkscape:snap-others="false">
 | 
			
		||||
    <inkscape:grid
 | 
			
		||||
       type="xygrid"
 | 
			
		||||
       id="grid4504"
 | 
			
		||||
       spacingx="4.2333334"
 | 
			
		||||
       spacingy="4.2333334"
 | 
			
		||||
       empcolor="#ff3fff"
 | 
			
		||||
       empopacity="0.25098039"
 | 
			
		||||
       empspacing="4" />
 | 
			
		||||
  </sodipodi:namedview>
 | 
			
		||||
  <metadata
 | 
			
		||||
     id="metadata5">
 | 
			
		||||
    <rdf:RDF>
 | 
			
		||||
      <cc:Work
 | 
			
		||||
         rdf:about="">
 | 
			
		||||
        <dc:format>image/svg+xml</dc:format>
 | 
			
		||||
        <dc:type
 | 
			
		||||
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
 | 
			
		||||
        <dc:title></dc:title>
 | 
			
		||||
      </cc:Work>
 | 
			
		||||
    </rdf:RDF>
 | 
			
		||||
  </metadata>
 | 
			
		||||
  <g
 | 
			
		||||
     inkscape:label="レイヤー 1"
 | 
			
		||||
     inkscape:groupmode="layer"
 | 
			
		||||
     id="layer1"
 | 
			
		||||
     transform="translate(-30.809093,-111.78601)">
 | 
			
		||||
    <g
 | 
			
		||||
       id="g4502"
 | 
			
		||||
       transform="matrix(1.096096,0,0,1.096096,-2.960633,-44.023579)">
 | 
			
		||||
      <g
 | 
			
		||||
         style="fill-opacity:1"
 | 
			
		||||
         transform="translate(-1.3333333e-6,-1.3439941e-6)"
 | 
			
		||||
         id="g5125">
 | 
			
		||||
        <g
 | 
			
		||||
           transform="matrix(0.91391326,0,0,0.91391326,7.9719907,17.595761)"
 | 
			
		||||
           id="text4489"
 | 
			
		||||
           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:141.03404236px;line-height:476.69509888px;font-family:'OTADESIGN Rounded';-inkscape-font-specification:'OTADESIGN Rounded';letter-spacing:0px;word-spacing:0px;fill-opacity:1;stroke:none;stroke-width:0.28950602px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
 | 
			
		||||
           aria-label="Mi">
 | 
			
		||||
          <path
 | 
			
		||||
             sodipodi:nodetypes="zccssscssccscczzzccsccsscscsccz"
 | 
			
		||||
             inkscape:connector-curvature="0"
 | 
			
		||||
             id="path5210"
 | 
			
		||||
             style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'OTADESIGN Rounded';-inkscape-font-specification:'OTADESIGN Rounded';fill-opacity:1;stroke-width:0.28950602px"
 | 
			
		||||
             d="m 75.196381,231.17126 c -5.855419,0.0202 -10.885068,-3.50766 -13.2572,-7.61584 -1.266603,-1.79454 -3.772419,-2.43291 -3.807919,0 v 11.2332 c 0,4.51309 -1.645397,8.41504 -4.936191,11.70583 -3.196772,3.19677 -7.098714,4.79516 -11.705826,4.79516 -4.513089,0 -8.415031,-1.59839 -11.705825,-4.79516 -3.196772,-3.29079 -4.795158,-7.19274 -4.795158,-11.70583 v -61.7729 c 0,-3.47884 0.987238,-6.6286 2.961715,-9.44928 2.068499,-2.91471 4.701135,-4.9362 7.897906,-6.06447 1.786431,-0.65816 3.666885,-0.98724 5.641362,-0.98724 5.077225,0 9.308247,1.97448 12.693064,5.92343 1.786431,1.97448 2.820681,3.00873 3.102749,3.10275 0,0 13.408119,16.21319 13.78421,16.49526 0.376091,0.28206 1.480789,2.43848 4.127113,2.43848 2.646324,0 3.89218,-2.15642 4.26827,-2.43848 0.376091,-0.28207 13.784088,-16.49526 13.784088,-16.49526 0.09402,0.094 1.081261,-0.94022 2.961715,-3.10275 3.478837,-3.94895 7.756866,-5.92343 12.834096,-5.92343 1.88045,0 3.76091,0.32908 5.64136,0.98724 3.19677,1.12827 5.7824,3.14976 7.75688,6.06447 2.06849,2.82068 3.10274,5.97044 3.10274,9.44928 v 61.7729 c 0,4.51309 -1.6454,8.41504 -4.93619,11.70583 -3.19677,3.19677 -7.09871,4.79516 -11.70582,4.79516 -4.51309,0 -8.41504,-1.59839 -11.705828,-4.79516 -3.196772,-3.29079 -4.795158,-7.19274 -4.795158,-11.70583 v -11.2332 c -0.277898,-3.06563 -2.987588,-1.13379 -3.948953,0 -2.538613,4.70114 -7.401781,7.59567 -13.2572,7.61584 z" />
 | 
			
		||||
          <path
 | 
			
		||||
             inkscape:connector-curvature="0"
 | 
			
		||||
             id="path5212"
 | 
			
		||||
             style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'OTADESIGN Rounded';-inkscape-font-specification:'OTADESIGN Rounded';fill-opacity:1;stroke-width:0.28950602px"
 | 
			
		||||
             d="m 145.83461,185.00361 q -5.92343,0 -10.15445,-4.08999 -4.08999,-4.23102 -4.08999,-10.15445 0,-5.92343 4.08999,-10.01342 4.23102,-4.23102 10.15445,-4.23102 5.92343,0 10.15445,4.23102 4.23102,4.08999 4.23102,10.01342 0,5.92343 -4.23102,10.15445 -4.23102,4.08999 -10.15445,4.08999 z m 0.14103,2.82068 q 5.92343,0 10.01342,4.23102 4.23102,4.23102 4.23102,10.15445 v 34.83541 q 0,5.92343 -4.23102,10.15445 -4.08999,4.08999 -10.01342,4.08999 -5.92343,0 -10.15445,-4.08999 -4.23102,-4.23102 -4.23102,-10.15445 v -34.83541 q 0,-5.92343 4.23102,-10.15445 4.23102,-4.23102 10.15445,-4.23102 z" />
 | 
			
		||||
        </g>
 | 
			
		||||
      </g>
 | 
			
		||||
    </g>
 | 
			
		||||
  </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 7.0 KiB  | 
							
								
								
									
										27
									
								
								src/client/app/admin/script.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/client/app/admin/script.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Admin
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import VueRouter from 'vue-router';
 | 
			
		||||
 | 
			
		||||
// Style
 | 
			
		||||
import './style.styl';
 | 
			
		||||
 | 
			
		||||
import init from '../init';
 | 
			
		||||
import Index from './views/index.vue';
 | 
			
		||||
 | 
			
		||||
init(launch => {
 | 
			
		||||
	document.title = 'Admin';
 | 
			
		||||
 | 
			
		||||
	// Init router
 | 
			
		||||
	const router = new VueRouter({
 | 
			
		||||
		mode: 'history',
 | 
			
		||||
		base: '/admin/',
 | 
			
		||||
		routes: [
 | 
			
		||||
			{ path: '/', component: Index },
 | 
			
		||||
		]
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// Launch the app
 | 
			
		||||
	launch(router);
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										6
									
								
								src/client/app/admin/style.styl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/client/app/admin/style.styl
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
@import "../app"
 | 
			
		||||
@import "../reset"
 | 
			
		||||
 | 
			
		||||
html
 | 
			
		||||
	height 100%
 | 
			
		||||
	background var(--bg)
 | 
			
		||||
							
								
								
									
										64
									
								
								src/client/app/admin/views/announcements.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/client/app/admin/views/announcements.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div>
 | 
			
		||||
	<ui-card>
 | 
			
		||||
		<div slot="title">%fa:broadcast-tower% %i18n:@announcements%</div>
 | 
			
		||||
		<section v-for="(announcement, i) in announcements" class="fit-top">
 | 
			
		||||
			<ui-input v-model="announcement.title" @change="save">
 | 
			
		||||
				<span>%i18n:@title%</span>
 | 
			
		||||
			</ui-input>
 | 
			
		||||
			<ui-textarea v-model="announcement.text">
 | 
			
		||||
				<span>%i18n:@text%</span>
 | 
			
		||||
			</ui-textarea>
 | 
			
		||||
			<ui-button-group>
 | 
			
		||||
				<ui-button inline @click="save">%fa:save R% %i18n:@save%</ui-button>
 | 
			
		||||
				<ui-button inline @click="remove(i)">%fa:trash-alt R% %i18n:@remove%</ui-button>
 | 
			
		||||
			</ui-button-group>
 | 
			
		||||
		</section>
 | 
			
		||||
		<section>
 | 
			
		||||
			<ui-button @click="add">%fa:plus% %i18n:@add%</ui-button>
 | 
			
		||||
		</section>
 | 
			
		||||
	</ui-card>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			announcements: [],
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	created() {
 | 
			
		||||
		(this as any).os.getMeta().then(meta => {
 | 
			
		||||
			this.announcements = meta.broadcasts;
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		add() {
 | 
			
		||||
			this.announcements.push({
 | 
			
		||||
				title: '',
 | 
			
		||||
				text: ''
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		remove(i) {
 | 
			
		||||
			this.announcements = this.announcements.filter((_, j) => j !== i);
 | 
			
		||||
			this.save();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		save() {
 | 
			
		||||
			(this as any).api('admin/update-meta', {
 | 
			
		||||
				broadcasts: this.announcements
 | 
			
		||||
			}).then(() => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Saved` });
 | 
			
		||||
			}).catch(e => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Failed ${e}` });
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										104
									
								
								src/client/app/admin/views/ap-log.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/client/app/admin/views/ap-log.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,104 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="hyhctythnmwihguaaapnbrbszsjqxpio">
 | 
			
		||||
	<table>
 | 
			
		||||
		<thead>
 | 
			
		||||
			<tr>
 | 
			
		||||
				<th>%fa:exchange-alt% In/Out</th>
 | 
			
		||||
				<th>%fa:server% Host</th>
 | 
			
		||||
				<th>%fa:bolt% Activity</th>
 | 
			
		||||
				<th>%fa:user% Actor</th>
 | 
			
		||||
			</tr>
 | 
			
		||||
		</thead>
 | 
			
		||||
		<tbody>
 | 
			
		||||
			<tr v-for="log in logs" :key="log.id">
 | 
			
		||||
				<td :class="log.direction">{{ log.direction == 'in' ? '<' : '>' }} {{ log.direction }}</td>
 | 
			
		||||
				<td>{{ log.host }}</td>
 | 
			
		||||
				<td>{{ log.activity }}</td>
 | 
			
		||||
				<td>@{{ log.actor }}</td>
 | 
			
		||||
			</tr>
 | 
			
		||||
		</tbody>
 | 
			
		||||
	</table>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			logs: [],
 | 
			
		||||
			connection: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.connection = (this as any).os.stream.useSharedConnection('apLog');
 | 
			
		||||
		this.connection.on('log', this.onLog);
 | 
			
		||||
		this.connection.on('logs', this.onLogs);
 | 
			
		||||
		this.connection.send('requestLog', {
 | 
			
		||||
			id: Math.random().toString().substr(2, 8),
 | 
			
		||||
			length: 50
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.connection.dispose();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		onLog(log) {
 | 
			
		||||
			log.id = Math.random();
 | 
			
		||||
			this.logs.unshift(log);
 | 
			
		||||
			if (this.logs.length > 50) this.logs.pop();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onLogs(logs) {
 | 
			
		||||
			logs.reverse().forEach(log => this.onLog(log));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.hyhctythnmwihguaaapnbrbszsjqxpio
 | 
			
		||||
	display block
 | 
			
		||||
	padding 16px
 | 
			
		||||
	height 250px
 | 
			
		||||
	overflow auto
 | 
			
		||||
	box-shadow 0 2px 4px rgba(0, 0, 0, 0.1)
 | 
			
		||||
	background var(--face)
 | 
			
		||||
	border-radius 8px
 | 
			
		||||
 | 
			
		||||
	> table
 | 
			
		||||
		width 100%
 | 
			
		||||
		max-width 100%
 | 
			
		||||
		overflow auto
 | 
			
		||||
		border-spacing 0
 | 
			
		||||
		border-collapse collapse
 | 
			
		||||
		color #555
 | 
			
		||||
 | 
			
		||||
		thead
 | 
			
		||||
			border-bottom solid 2px #eee
 | 
			
		||||
 | 
			
		||||
			tr
 | 
			
		||||
				th
 | 
			
		||||
					font-weight normal
 | 
			
		||||
					text-align left
 | 
			
		||||
 | 
			
		||||
		tbody
 | 
			
		||||
			tr
 | 
			
		||||
				&:nth-child(odd)
 | 
			
		||||
					background #fbfbfb
 | 
			
		||||
 | 
			
		||||
		th, td
 | 
			
		||||
			padding 8px 16px
 | 
			
		||||
			min-width 128px
 | 
			
		||||
 | 
			
		||||
		td.in
 | 
			
		||||
			color #d26755
 | 
			
		||||
 | 
			
		||||
		td.out
 | 
			
		||||
			color #55bb83
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										441
									
								
								src/client/app/admin/views/charts.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										441
									
								
								src/client/app/admin/views/charts.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,441 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="qvgidhudpqhjttdhxubzuyrhyzgslujw">
 | 
			
		||||
	<header>
 | 
			
		||||
		<b>%fa:chart-bar R% %i18n:@title%:</b>
 | 
			
		||||
		<select v-model="src">
 | 
			
		||||
			<optgroup label="%i18n:@federation%">
 | 
			
		||||
				<option value="federation-instances">%i18n:@charts.federation-instances%</option>
 | 
			
		||||
				<option value="federation-instances-total">%i18n:@charts.federation-instances-total%</option>
 | 
			
		||||
			</optgroup>
 | 
			
		||||
			<optgroup label="%i18n:@users%">
 | 
			
		||||
				<option value="users">%i18n:@charts.users%</option>
 | 
			
		||||
				<option value="users-total">%i18n:@charts.users-total%</option>
 | 
			
		||||
			</optgroup>
 | 
			
		||||
			<optgroup label="%i18n:@notes%">
 | 
			
		||||
				<option value="notes">%i18n:@charts.notes%</option>
 | 
			
		||||
				<option value="local-notes">%i18n:@charts.local-notes%</option>
 | 
			
		||||
				<option value="remote-notes">%i18n:@charts.remote-notes%</option>
 | 
			
		||||
				<option value="notes-total">%i18n:@charts.notes-total%</option>
 | 
			
		||||
			</optgroup>
 | 
			
		||||
			<optgroup label="%i18n:@drive%">
 | 
			
		||||
				<option value="drive-files">%i18n:@charts.drive-files%</option>
 | 
			
		||||
				<option value="drive-files-total">%i18n:@charts.drive-files-total%</option>
 | 
			
		||||
				<option value="drive">%i18n:@charts.drive%</option>
 | 
			
		||||
				<option value="drive-total">%i18n:@charts.drive-total%</option>
 | 
			
		||||
			</optgroup>
 | 
			
		||||
			<optgroup label="%i18n:@network%">
 | 
			
		||||
				<option value="network-requests">%i18n:@charts.network-requests%</option>
 | 
			
		||||
				<option value="network-time">%i18n:@charts.network-time%</option>
 | 
			
		||||
				<option value="network-usage">%i18n:@charts.network-usage%</option>
 | 
			
		||||
			</optgroup>
 | 
			
		||||
		</select>
 | 
			
		||||
		<div>
 | 
			
		||||
			<span @click="span = 'day'" :class="{ active: span == 'day' }">%i18n:@per-day%</span> | <span @click="span = 'hour'" :class="{ active: span == 'hour' }">%i18n:@per-hour%</span>
 | 
			
		||||
		</div>
 | 
			
		||||
	</header>
 | 
			
		||||
	<div ref="chart"></div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import * as ApexCharts from 'apexcharts';
 | 
			
		||||
 | 
			
		||||
const limit = 90;
 | 
			
		||||
 | 
			
		||||
const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
 | 
			
		||||
const negate = arr => arr.map(x => -x);
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			chart: null,
 | 
			
		||||
			src: 'notes',
 | 
			
		||||
			span: 'hour',
 | 
			
		||||
			chartInstance: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		data(): any {
 | 
			
		||||
			if (this.chart == null) return null;
 | 
			
		||||
			switch (this.src) {
 | 
			
		||||
				case 'federation-instances': return this.federationInstancesChart(false);
 | 
			
		||||
				case 'federation-instances-total': return this.federationInstancesChart(true);
 | 
			
		||||
				case 'users': return this.usersChart(false);
 | 
			
		||||
				case 'users-total': return this.usersChart(true);
 | 
			
		||||
				case 'notes': return this.notesChart('combined');
 | 
			
		||||
				case 'local-notes': return this.notesChart('local');
 | 
			
		||||
				case 'remote-notes': return this.notesChart('remote');
 | 
			
		||||
				case 'notes-total': return this.notesTotalChart();
 | 
			
		||||
				case 'drive': return this.driveChart();
 | 
			
		||||
				case 'drive-total': return this.driveTotalChart();
 | 
			
		||||
				case 'drive-files': return this.driveFilesChart();
 | 
			
		||||
				case 'drive-files-total': return this.driveFilesTotalChart();
 | 
			
		||||
				case 'network-requests': return this.networkRequestsChart();
 | 
			
		||||
				case 'network-time': return this.networkTimeChart();
 | 
			
		||||
				case 'network-usage': return this.networkUsageChart();
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		stats(): any[] {
 | 
			
		||||
			const stats =
 | 
			
		||||
				this.span == 'day' ? this.chart.perDay :
 | 
			
		||||
				this.span == 'hour' ? this.chart.perHour :
 | 
			
		||||
				null;
 | 
			
		||||
 | 
			
		||||
			return stats;
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	watch: {
 | 
			
		||||
		src() {
 | 
			
		||||
			this.render();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		span() {
 | 
			
		||||
			this.render();
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	async mounted() {
 | 
			
		||||
		this.now = new Date();
 | 
			
		||||
 | 
			
		||||
		const [perHour, perDay] = await Promise.all([Promise.all([
 | 
			
		||||
			(this as any).api('charts/federation', { limit: limit, span: 'hour' }),
 | 
			
		||||
			(this as any).api('charts/users', { limit: limit, span: 'hour' }),
 | 
			
		||||
			(this as any).api('charts/notes', { limit: limit, span: 'hour' }),
 | 
			
		||||
			(this as any).api('charts/drive', { limit: limit, span: 'hour' }),
 | 
			
		||||
			(this as any).api('charts/network', { limit: limit, span: 'hour' })
 | 
			
		||||
		]), Promise.all([
 | 
			
		||||
			(this as any).api('charts/federation', { limit: limit, span: 'day' }),
 | 
			
		||||
			(this as any).api('charts/users', { limit: limit, span: 'day' }),
 | 
			
		||||
			(this as any).api('charts/notes', { limit: limit, span: 'day' }),
 | 
			
		||||
			(this as any).api('charts/drive', { limit: limit, span: 'day' }),
 | 
			
		||||
			(this as any).api('charts/network', { limit: limit, span: 'day' })
 | 
			
		||||
		])]);
 | 
			
		||||
 | 
			
		||||
		const chart = {
 | 
			
		||||
			perHour: {
 | 
			
		||||
				federation: perHour[0],
 | 
			
		||||
				users: perHour[1],
 | 
			
		||||
				notes: perHour[2],
 | 
			
		||||
				drive: perHour[3],
 | 
			
		||||
				network: perHour[4]
 | 
			
		||||
			},
 | 
			
		||||
			perDay: {
 | 
			
		||||
				federation: perDay[0],
 | 
			
		||||
				users: perDay[1],
 | 
			
		||||
				notes: perDay[2],
 | 
			
		||||
				drive: perDay[3],
 | 
			
		||||
				network: perDay[4]
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		this.chart = chart;
 | 
			
		||||
 | 
			
		||||
		this.render();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		setSrc(src) {
 | 
			
		||||
			this.src = src;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		render() {
 | 
			
		||||
			if (this.chartInstance) {
 | 
			
		||||
				this.chartInstance.destroy();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			this.chartInstance = new ApexCharts(this.$refs.chart, Object.assign({
 | 
			
		||||
				chart: {
 | 
			
		||||
					type: 'area',
 | 
			
		||||
					height: 300,
 | 
			
		||||
					animations: {
 | 
			
		||||
						dynamicAnimation: {
 | 
			
		||||
							enabled: false
 | 
			
		||||
						}
 | 
			
		||||
					},
 | 
			
		||||
					toolbar: {
 | 
			
		||||
						show: false
 | 
			
		||||
					},
 | 
			
		||||
					zoom: {
 | 
			
		||||
						enabled: false
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				dataLabels: {
 | 
			
		||||
					enabled: false
 | 
			
		||||
				},
 | 
			
		||||
				grid: {
 | 
			
		||||
					clipMarkers: false,
 | 
			
		||||
				},
 | 
			
		||||
				stroke: {
 | 
			
		||||
					curve: 'straight',
 | 
			
		||||
					width: 2
 | 
			
		||||
				},
 | 
			
		||||
				xaxis: {
 | 
			
		||||
					type: 'datetime'
 | 
			
		||||
				},
 | 
			
		||||
				yaxis: {
 | 
			
		||||
				}
 | 
			
		||||
			}, this.data));
 | 
			
		||||
 | 
			
		||||
			this.chartInstance.render();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		getDate(i: number) {
 | 
			
		||||
			const y = this.now.getFullYear();
 | 
			
		||||
			const m = this.now.getMonth();
 | 
			
		||||
			const d = this.now.getDate();
 | 
			
		||||
			const h = this.now.getHours();
 | 
			
		||||
 | 
			
		||||
			return (
 | 
			
		||||
				this.span == 'day' ? new Date(y, m, d - i) :
 | 
			
		||||
				this.span == 'hour' ? new Date(y, m, d, h - i) :
 | 
			
		||||
				null
 | 
			
		||||
			);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		format(arr) {
 | 
			
		||||
			return arr.map((v, i) => ({ x: this.getDate(i).getTime(), y: v }));
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		federationInstancesChart(total: boolean): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					data: this.format(total
 | 
			
		||||
						? this.stats.federation.instance.total
 | 
			
		||||
						: sum(this.stats.federation.instance.inc, negate(this.stats.federation.instance.dec))
 | 
			
		||||
					)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		notesChart(type: string): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'All',
 | 
			
		||||
					type: 'line',
 | 
			
		||||
					data: this.format(type == 'combined'
 | 
			
		||||
						? sum(this.stats.notes.local.inc, negate(this.stats.notes.local.dec), this.stats.notes.remote.inc, negate(this.stats.notes.remote.dec))
 | 
			
		||||
						: sum(this.stats.notes[type].inc, negate(this.stats.notes[type].dec))
 | 
			
		||||
					)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Renotes',
 | 
			
		||||
					type: 'area',
 | 
			
		||||
					data: this.format(type == 'combined'
 | 
			
		||||
						? sum(this.stats.notes.local.diffs.renote, this.stats.notes.remote.diffs.renote)
 | 
			
		||||
						: this.stats.notes[type].diffs.renote
 | 
			
		||||
					)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Replies',
 | 
			
		||||
					type: 'area',
 | 
			
		||||
					data: this.format(type == 'combined'
 | 
			
		||||
						? sum(this.stats.notes.local.diffs.reply, this.stats.notes.remote.diffs.reply)
 | 
			
		||||
						: this.stats.notes[type].diffs.reply
 | 
			
		||||
					)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Normal',
 | 
			
		||||
					type: 'area',
 | 
			
		||||
					data: this.format(type == 'combined'
 | 
			
		||||
						? sum(this.stats.notes.local.diffs.normal, this.stats.notes.remote.diffs.normal)
 | 
			
		||||
						: this.stats.notes[type].diffs.normal
 | 
			
		||||
					)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		notesTotalChart(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Combined',
 | 
			
		||||
					data: this.format(sum(this.stats.notes.local.total, this.stats.notes.remote.total))
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Local',
 | 
			
		||||
					data: this.format(this.stats.notes.local.total)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Remote',
 | 
			
		||||
					data: this.format(this.stats.notes.remote.total)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		usersChart(total: boolean): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Combined',
 | 
			
		||||
					data: this.format(total
 | 
			
		||||
						? sum(this.stats.users.local.total, this.stats.users.remote.total)
 | 
			
		||||
						: sum(this.stats.users.local.inc, negate(this.stats.users.local.dec), this.stats.users.remote.inc, negate(this.stats.users.remote.dec))
 | 
			
		||||
					)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Local',
 | 
			
		||||
					data: this.format(total
 | 
			
		||||
						? this.stats.users.local.total
 | 
			
		||||
						: sum(this.stats.users.local.inc, negate(this.stats.users.local.dec))
 | 
			
		||||
					)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Remote',
 | 
			
		||||
					data: this.format(total
 | 
			
		||||
						? this.stats.users.remote.total
 | 
			
		||||
						: sum(this.stats.users.remote.inc, negate(this.stats.users.remote.dec))
 | 
			
		||||
					)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		driveChart(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'All',
 | 
			
		||||
					data: this.format(
 | 
			
		||||
						sum(
 | 
			
		||||
							this.stats.drive.local.incSize,
 | 
			
		||||
							negate(this.stats.drive.local.decSize),
 | 
			
		||||
							this.stats.drive.remote.incSize,
 | 
			
		||||
							negate(this.stats.drive.remote.decSize)
 | 
			
		||||
						)
 | 
			
		||||
					)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Local +',
 | 
			
		||||
					data: this.format(this.stats.drive.local.incSize)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Local -',
 | 
			
		||||
					data: this.format(negate(this.stats.drive.local.decSize))
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Remote +',
 | 
			
		||||
					data: this.format(this.stats.drive.remote.incSize)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Remote -',
 | 
			
		||||
					data: this.format(negate(this.stats.drive.remote.decSize))
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		driveTotalChart(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Combined',
 | 
			
		||||
					data: this.format(sum(this.stats.drive.local.totalSize, this.stats.drive.remote.totalSize))
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Local',
 | 
			
		||||
					data: this.format(this.stats.drive.local.totalSize)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Remote',
 | 
			
		||||
					data: this.format(this.stats.drive.remote.totalSize)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		driveFilesChart(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'All',
 | 
			
		||||
					data: this.format(
 | 
			
		||||
						sum(
 | 
			
		||||
							this.stats.drive.local.incCount,
 | 
			
		||||
							negate(this.stats.drive.local.decCount),
 | 
			
		||||
							this.stats.drive.remote.incCount,
 | 
			
		||||
							negate(this.stats.drive.remote.decCount)
 | 
			
		||||
						)
 | 
			
		||||
					)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Local +',
 | 
			
		||||
					data: this.format(this.stats.drive.local.incCount)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Local -',
 | 
			
		||||
					data: this.format(negate(this.stats.drive.local.decCount))
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Remote +',
 | 
			
		||||
					data: this.format(this.stats.drive.remote.incCount)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Remote -',
 | 
			
		||||
					data: this.format(negate(this.stats.drive.remote.decCount))
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		driveFilesTotalChart(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Combined',
 | 
			
		||||
					data: this.format(sum(this.stats.drive.local.totalCount, this.stats.drive.remote.totalCount))
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Local',
 | 
			
		||||
					data: this.format(this.stats.drive.local.totalCount)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Remote',
 | 
			
		||||
					data: this.format(this.stats.drive.remote.totalCount)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		networkRequestsChart(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Incoming',
 | 
			
		||||
					data: this.format(this.stats.network.incomingRequests)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		networkTimeChart(): any {
 | 
			
		||||
			const data = [];
 | 
			
		||||
 | 
			
		||||
			for (let i = 0; i < limit; i++) {
 | 
			
		||||
				data.push(this.stats.network.incomingRequests[i] != 0 ? (this.stats.network.totalTime[i] / this.stats.network.incomingRequests[i]) : 0);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Avg time',
 | 
			
		||||
					data: this.format(data)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		networkUsageChart(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Incoming',
 | 
			
		||||
					data: this.format(this.stats.network.incomingBytes)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Outgoing',
 | 
			
		||||
					data: this.format(this.stats.network.outgoingBytes)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.qvgidhudpqhjttdhxubzuyrhyzgslujw
 | 
			
		||||
	display block
 | 
			
		||||
	flex 1
 | 
			
		||||
	padding 32px 24px
 | 
			
		||||
	padding-bottom 0
 | 
			
		||||
	box-shadow 0 2px 4px rgba(0, 0, 0, 0.1)
 | 
			
		||||
	background var(--face)
 | 
			
		||||
	border-radius 8px
 | 
			
		||||
 | 
			
		||||
	> header
 | 
			
		||||
		display flex
 | 
			
		||||
		margin 0 8px
 | 
			
		||||
		padding 0 0 8px 0
 | 
			
		||||
		font-size 1em
 | 
			
		||||
		color #555
 | 
			
		||||
		border-bottom solid 1px #eee
 | 
			
		||||
 | 
			
		||||
		> b
 | 
			
		||||
			margin-right 8px
 | 
			
		||||
 | 
			
		||||
		> *:last-child
 | 
			
		||||
			margin-left auto
 | 
			
		||||
 | 
			
		||||
			*
 | 
			
		||||
				&:not(.active)
 | 
			
		||||
					color var(--primary)
 | 
			
		||||
					cursor pointer
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										170
									
								
								src/client/app/admin/views/cpu-memory.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								src/client/app/admin/views/cpu-memory.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,170 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="zyknedwtlthezamcjlolyusmipqmjgxz">
 | 
			
		||||
	<div>
 | 
			
		||||
		<header>
 | 
			
		||||
			<span>%fa:microchip% CPU <span>{{ cpuP }}%</span></span>
 | 
			
		||||
			<span v-if="meta">{{ meta.cpu.model }}</span>
 | 
			
		||||
		</header>
 | 
			
		||||
		<div ref="cpu"></div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div>
 | 
			
		||||
		<header>
 | 
			
		||||
			<span>%fa:memory% MEM <span>{{ memP }}%</span></span>
 | 
			
		||||
			<span v-if="meta"></span>
 | 
			
		||||
		</header>
 | 
			
		||||
		<div ref="mem"></div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import * as ApexCharts from 'apexcharts';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: ['connection'],
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			stats: [],
 | 
			
		||||
			cpuChart: null,
 | 
			
		||||
			memChart: null,
 | 
			
		||||
			cpuP: '',
 | 
			
		||||
			memP: '',
 | 
			
		||||
			meta: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	watch: {
 | 
			
		||||
		stats(stats) {
 | 
			
		||||
			this.cpuChart.updateSeries([{
 | 
			
		||||
				data: stats.map((x, i) => ({ x: i, y: x.cpu_usage }))
 | 
			
		||||
			}]);
 | 
			
		||||
			this.memChart.updateSeries([{
 | 
			
		||||
				data: stats.map((x, i) => ({ x: i, y: (x.mem.used / x.mem.total) }))
 | 
			
		||||
			}]);
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		(this as any).os.getMeta().then(meta => {
 | 
			
		||||
			this.meta = meta;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.connection.on('stats', this.onStats);
 | 
			
		||||
		this.connection.on('statsLog', this.onStatsLog);
 | 
			
		||||
		this.connection.send('requestLog', {
 | 
			
		||||
			id: Math.random().toString().substr(2, 8),
 | 
			
		||||
			length: 200
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		const chartOpts = {
 | 
			
		||||
			chart: {
 | 
			
		||||
				type: 'area',
 | 
			
		||||
				height: 200,
 | 
			
		||||
				animations: {
 | 
			
		||||
					dynamicAnimation: {
 | 
			
		||||
						enabled: false
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				toolbar: {
 | 
			
		||||
					show: false
 | 
			
		||||
				},
 | 
			
		||||
				zoom: {
 | 
			
		||||
					enabled: false
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			dataLabels: {
 | 
			
		||||
				enabled: false
 | 
			
		||||
			},
 | 
			
		||||
			grid: {
 | 
			
		||||
				clipMarkers: false,
 | 
			
		||||
			},
 | 
			
		||||
			stroke: {
 | 
			
		||||
				curve: 'straight',
 | 
			
		||||
				width: 2
 | 
			
		||||
			},
 | 
			
		||||
			tooltip: {
 | 
			
		||||
				enabled: false
 | 
			
		||||
			},
 | 
			
		||||
			series: [{
 | 
			
		||||
				data: []
 | 
			
		||||
			}],
 | 
			
		||||
			xaxis: {
 | 
			
		||||
				type: 'numeric',
 | 
			
		||||
				labels: {
 | 
			
		||||
					show: false
 | 
			
		||||
				},
 | 
			
		||||
				tooltip: {
 | 
			
		||||
					enabled: false
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			yaxis: {
 | 
			
		||||
				show: false,
 | 
			
		||||
				min: 0,
 | 
			
		||||
				max: 1
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		this.cpuChart = new ApexCharts(this.$refs.cpu, chartOpts);
 | 
			
		||||
		this.memChart = new ApexCharts(this.$refs.mem, chartOpts);
 | 
			
		||||
 | 
			
		||||
		this.cpuChart.render();
 | 
			
		||||
		this.memChart.render();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.connection.off('stats', this.onStats);
 | 
			
		||||
		this.connection.off('statsLog', this.onStatsLog);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		onStats(stats) {
 | 
			
		||||
			this.stats.push(stats);
 | 
			
		||||
			if (this.stats.length > 200) this.stats.shift();
 | 
			
		||||
 | 
			
		||||
			this.cpuP = (stats.cpu_usage * 100).toFixed(0);
 | 
			
		||||
			this.memP = (stats.mem.used / stats.mem.total * 100).toFixed(0);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onStatsLog(statsLog) {
 | 
			
		||||
			statsLog.reverse().forEach(stats => this.onStats(stats));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.zyknedwtlthezamcjlolyusmipqmjgxz
 | 
			
		||||
	display flex
 | 
			
		||||
 | 
			
		||||
	> div
 | 
			
		||||
		display block
 | 
			
		||||
		flex 1
 | 
			
		||||
		padding 20px 12px 0 12px
 | 
			
		||||
		box-shadow 0 2px 4px rgba(0, 0, 0, 0.1)
 | 
			
		||||
		background var(--face)
 | 
			
		||||
		border-radius 8px
 | 
			
		||||
 | 
			
		||||
		&:first-child
 | 
			
		||||
			margin-right 16px
 | 
			
		||||
 | 
			
		||||
		> header
 | 
			
		||||
			display flex
 | 
			
		||||
			padding 0 8px
 | 
			
		||||
			margin-bottom -16px
 | 
			
		||||
			color #555
 | 
			
		||||
			font-size 14px
 | 
			
		||||
 | 
			
		||||
			> span
 | 
			
		||||
				&:last-child
 | 
			
		||||
					margin-left auto
 | 
			
		||||
					opacity 0.7
 | 
			
		||||
 | 
			
		||||
				> span
 | 
			
		||||
					opacity 0.7
 | 
			
		||||
 | 
			
		||||
		> div
 | 
			
		||||
			margin-bottom -10px
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										211
									
								
								src/client/app/admin/views/dashboard.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								src/client/app/admin/views/dashboard.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,211 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="obdskegsannmntldydackcpzezagxqfy">
 | 
			
		||||
	<header v-if="meta">
 | 
			
		||||
		<p><b>Misskey</b><span>{{ meta.version }}</span></p>
 | 
			
		||||
		<p><b>Machine</b><span>{{ meta.machine }}</span></p>
 | 
			
		||||
		<p><b>OS</b><span>{{ meta.os }}</span></p>
 | 
			
		||||
		<p><b>Node</b><span>{{ meta.node }}</span></p>
 | 
			
		||||
		<p>%i18n:common.ai-chan-kawaii%</p>
 | 
			
		||||
	</header>
 | 
			
		||||
 | 
			
		||||
	<div v-if="stats" class="stats">
 | 
			
		||||
		<div>
 | 
			
		||||
			<div>
 | 
			
		||||
				<div>%fa:user%</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<span>%i18n:@accounts%</span>
 | 
			
		||||
					<b class="primary">{{ stats.originalUsersCount | number }}</b>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div>
 | 
			
		||||
				<span>%fa:home% %i18n:@this-instance%</span>
 | 
			
		||||
				<span @click="setChartSrc('users')">%fa:chart-bar R%</span>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div>
 | 
			
		||||
			<div>
 | 
			
		||||
				<div>%fa:pencil-alt%</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<span>%i18n:@notes%</span>
 | 
			
		||||
					<b class="primary">{{ stats.originalNotesCount | number }}</b>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div>
 | 
			
		||||
				<span>%fa:home% %i18n:@this-instance%</span>
 | 
			
		||||
				<span @click="setChartSrc('notes')">%fa:chart-bar R%</span>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div>
 | 
			
		||||
			<div>
 | 
			
		||||
				<div>%fa:database%</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<span>%i18n:@drive%</span>
 | 
			
		||||
					<b>{{ stats.driveUsageLocal | bytes }}</b>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div>
 | 
			
		||||
				<span>%fa:home% %i18n:@this-instance%</span>
 | 
			
		||||
				<span @click="setChartSrc('drive')">%fa:chart-bar R%</span>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div>
 | 
			
		||||
			<div>
 | 
			
		||||
				<div>%fa:hdd R%</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<span>%i18n:@instances%</span>
 | 
			
		||||
					<b>{{ stats.instances | number }}</b>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div>
 | 
			
		||||
				<span>%fa:globe% %i18n:@federated%</span>
 | 
			
		||||
				<span @click="setChartSrc('federation-instances-total')">%fa:chart-bar R%</span>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class="charts">
 | 
			
		||||
		<x-charts ref="charts"/>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class="cpu-memory">
 | 
			
		||||
		<x-cpu-memory :connection="connection"/>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class="ap">
 | 
			
		||||
		<x-ap-log/>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
import XCpuMemory from "./cpu-memory.vue";
 | 
			
		||||
import XCharts from "./charts.vue";
 | 
			
		||||
import XApLog from "./ap-log.vue";
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XCpuMemory,
 | 
			
		||||
		XCharts,
 | 
			
		||||
		XApLog
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			stats: null,
 | 
			
		||||
			connection: null,
 | 
			
		||||
			meta: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	created() {
 | 
			
		||||
		this.connection = (this as any).os.stream.useSharedConnection('serverStats');
 | 
			
		||||
 | 
			
		||||
		(this as any).os.getMeta().then(meta => {
 | 
			
		||||
			this.meta = meta;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		(this as any).api('stats').then(stats => {
 | 
			
		||||
			this.stats = stats;
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.connection.dispose();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		setChartSrc(src) {
 | 
			
		||||
			this.$refs.charts.setSrc(src);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.obdskegsannmntldydackcpzezagxqfy
 | 
			
		||||
	> header
 | 
			
		||||
		display flex
 | 
			
		||||
		margin-bottom 16px
 | 
			
		||||
		padding-bottom 16px
 | 
			
		||||
		border-bottom solid 1px #ccc
 | 
			
		||||
		color #777
 | 
			
		||||
		font-size 14px
 | 
			
		||||
 | 
			
		||||
		> p
 | 
			
		||||
			display inline
 | 
			
		||||
			margin 0 32px 0 0
 | 
			
		||||
 | 
			
		||||
			> b
 | 
			
		||||
				&:after
 | 
			
		||||
					content ':'
 | 
			
		||||
					margin-right 8px
 | 
			
		||||
 | 
			
		||||
			&:last-child
 | 
			
		||||
				margin-left auto
 | 
			
		||||
				margin-right 0
 | 
			
		||||
 | 
			
		||||
	> .stats
 | 
			
		||||
		display flex
 | 
			
		||||
		justify-content space-between
 | 
			
		||||
		margin-bottom 16px
 | 
			
		||||
 | 
			
		||||
		> div
 | 
			
		||||
			flex 1
 | 
			
		||||
			max-width 300px
 | 
			
		||||
			margin-right 16px
 | 
			
		||||
			color var(--text)
 | 
			
		||||
			box-shadow 0 2px 4px rgba(0, 0, 0, 0.1)
 | 
			
		||||
			background var(--face)
 | 
			
		||||
			border-radius 8px
 | 
			
		||||
 | 
			
		||||
			&:last-child
 | 
			
		||||
				margin-right 0
 | 
			
		||||
 | 
			
		||||
			> div:first-child
 | 
			
		||||
				display flex
 | 
			
		||||
				align-items center
 | 
			
		||||
				text-align center
 | 
			
		||||
 | 
			
		||||
				&:last-child
 | 
			
		||||
					margin-right 0
 | 
			
		||||
 | 
			
		||||
				> div:first-child
 | 
			
		||||
					padding 16px 24px
 | 
			
		||||
					font-size 28px
 | 
			
		||||
 | 
			
		||||
				> div:last-child
 | 
			
		||||
					flex 1
 | 
			
		||||
					padding 16px 32px 16px 0
 | 
			
		||||
					text-align right
 | 
			
		||||
 | 
			
		||||
					> span
 | 
			
		||||
						font-size 70%
 | 
			
		||||
						opacity 0.7
 | 
			
		||||
 | 
			
		||||
					> b
 | 
			
		||||
						display block
 | 
			
		||||
 | 
			
		||||
						&.primary
 | 
			
		||||
							color var(--primary)
 | 
			
		||||
 | 
			
		||||
			> div:last-child
 | 
			
		||||
				display flex
 | 
			
		||||
				padding 6px 16px
 | 
			
		||||
				border-top solid 1px #eee
 | 
			
		||||
 | 
			
		||||
				> span
 | 
			
		||||
					font-size 70%
 | 
			
		||||
					opacity 0.7
 | 
			
		||||
 | 
			
		||||
					&:last-child
 | 
			
		||||
						margin-left auto
 | 
			
		||||
						cursor pointer
 | 
			
		||||
 | 
			
		||||
	> .charts
 | 
			
		||||
		margin-bottom 16px
 | 
			
		||||
 | 
			
		||||
	> .cpu-memory
 | 
			
		||||
		margin-bottom 16px
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										108
									
								
								src/client/app/admin/views/emoji.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/client/app/admin/views/emoji.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div>
 | 
			
		||||
	<ui-card>
 | 
			
		||||
		<div slot="title">%fa:plus% %i18n:@add-emoji.title%</div>
 | 
			
		||||
		<section class="fit-top">
 | 
			
		||||
			<ui-input v-model="name">
 | 
			
		||||
				<span>%i18n:@add-emoji.name%</span>
 | 
			
		||||
				<span slot="text">%i18n:@add-emoji.name-desc%</span>
 | 
			
		||||
			</ui-input>
 | 
			
		||||
			<ui-input v-model="aliases">
 | 
			
		||||
				<span>%i18n:@add-emoji.aliases%</span>
 | 
			
		||||
				<span slot="text">%i18n:@add-emoji.aliases-desc%</span>
 | 
			
		||||
			</ui-input>
 | 
			
		||||
			<ui-input v-model="url">
 | 
			
		||||
				<span>%i18n:@add-emoji.url%</span>
 | 
			
		||||
			</ui-input>
 | 
			
		||||
			<ui-button @click="add">%i18n:@add-emoji.add%</ui-button>
 | 
			
		||||
		</section>
 | 
			
		||||
	</ui-card>
 | 
			
		||||
 | 
			
		||||
	<ui-card>
 | 
			
		||||
		<div slot="title">%fa:grin R% %i18n:@emojis.title%</div>
 | 
			
		||||
		<section v-for="emoji in emojis">
 | 
			
		||||
			<img :src="emoji.url" :alt="emoji.name" style="width: 64px;"/>
 | 
			
		||||
			<ui-input v-model="emoji.name">
 | 
			
		||||
				<span>%i18n:@add-emoji.name%</span>
 | 
			
		||||
				<span slot="text">%i18n:@add-emoji.name-desc%</span>
 | 
			
		||||
			</ui-input>
 | 
			
		||||
			<ui-input v-model="emoji.aliases">
 | 
			
		||||
				<span>%i18n:@add-emoji.aliases%</span>
 | 
			
		||||
				<span slot="text">%i18n:@add-emoji.aliases-desc%</span>
 | 
			
		||||
			</ui-input>
 | 
			
		||||
			<ui-input v-model="emoji.url">
 | 
			
		||||
				<span>%i18n:@add-emoji.url%</span>
 | 
			
		||||
			</ui-input>
 | 
			
		||||
			<ui-button-group>
 | 
			
		||||
				<ui-button inline @click="updateEmoji(emoji)">%fa:save R% %i18n:@emojis.update%</ui-button>
 | 
			
		||||
				<ui-button inline @click="removeEmoji(emoji)">%fa:trash-alt R% %i18n:@emojis.remove%</ui-button>
 | 
			
		||||
			</ui-button-group>
 | 
			
		||||
		</section>
 | 
			
		||||
	</ui-card>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			name: '',
 | 
			
		||||
			url: '',
 | 
			
		||||
			aliases: '',
 | 
			
		||||
			emojis: []
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.fetchEmojis();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		add() {
 | 
			
		||||
			(this as any).api('admin/emoji/add', {
 | 
			
		||||
				name: this.name,
 | 
			
		||||
				url: this.url,
 | 
			
		||||
				aliases: this.aliases.split(' ')
 | 
			
		||||
			}).then(() => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Added` });
 | 
			
		||||
				this.fetchEmojis();
 | 
			
		||||
			}).catch(e => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Failed ${e}` });
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		fetchEmojis() {
 | 
			
		||||
			(this as any).api('admin/emoji/list').then(emojis => {
 | 
			
		||||
				emojis.forEach(e => e.aliases = (e.aliases || []).join(' '));
 | 
			
		||||
				this.emojis = emojis;
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		updateEmoji(emoji) {
 | 
			
		||||
			(this as any).api('admin/emoji/update', {
 | 
			
		||||
				id: emoji.id,
 | 
			
		||||
				name: emoji.name,
 | 
			
		||||
				url: emoji.url,
 | 
			
		||||
				aliases: emoji.aliases.split(' ')
 | 
			
		||||
			}).then(() => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Updated` });
 | 
			
		||||
			}).catch(e => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Failed ${e}` });
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		removeEmoji(emoji) {
 | 
			
		||||
			(this as any).api('admin/emoji/remove', {
 | 
			
		||||
				id: emoji.id
 | 
			
		||||
			}).then(() => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Removed` });
 | 
			
		||||
				this.fetchEmojis();
 | 
			
		||||
			}).catch(e => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Failed ${e}` });
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,8 +1,12 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="jdnqwkzlnxcfftthoybjxrebyolvoucw mk-admin-card">
 | 
			
		||||
	<header>%i18n:@hided-tags%</header>
 | 
			
		||||
	<textarea v-model="hidedTags"></textarea>
 | 
			
		||||
	<button class="ui" @click="save">%i18n:@save%</button>
 | 
			
		||||
<div>
 | 
			
		||||
	<ui-card>
 | 
			
		||||
		<div slot="title">%i18n:@hided-tags%</div>
 | 
			
		||||
		<section>
 | 
			
		||||
			<textarea class="jdnqwkzlnxcfftthoybjxrebyolvoucw" v-model="hidedTags"></textarea>
 | 
			
		||||
			<ui-button @click="save">%i18n:@save%</ui-button>
 | 
			
		||||
		</section>
 | 
			
		||||
	</ui-card>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -35,10 +39,7 @@ export default Vue.extend({
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.jdnqwkzlnxcfftthoybjxrebyolvoucw
 | 
			
		||||
	textarea
 | 
			
		||||
	width 100%
 | 
			
		||||
	min-height 300px
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										195
									
								
								src/client/app/admin/views/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								src/client/app/admin/views/index.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,195 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-admin">
 | 
			
		||||
	<nav>
 | 
			
		||||
		<div class="mi">
 | 
			
		||||
			<img svg-inline src="../assets/header-icon.svg"/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="me">
 | 
			
		||||
			<img class="avatar" :src="$store.state.i.avatarUrl" alt="avatar"/>
 | 
			
		||||
			<p class="name">{{ $store.state.i | userName }}</p>
 | 
			
		||||
		</div>
 | 
			
		||||
		<ul>
 | 
			
		||||
			<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }">%fa:home .fw%%i18n:@dashboard%</li>
 | 
			
		||||
			<li @click="nav('instance')" :class="{ active: page == 'instance' }">%fa:cog .fw%%i18n:@instance%</li>
 | 
			
		||||
			<li @click="nav('users')" :class="{ active: page == 'users' }">%fa:users .fw%%i18n:@users%</li>
 | 
			
		||||
			<li @click="nav('emoji')" :class="{ active: page == 'emoji' }">%fa:grin R .fw%%i18n:@emoji%</li>
 | 
			
		||||
			<li @click="nav('announcements')" :class="{ active: page == 'announcements' }">%fa:broadcast-tower .fw%%i18n:@announcements%</li>
 | 
			
		||||
			<li @click="nav('hashtags')" :class="{ active: page == 'hashtags' }">%fa:hashtag .fw%%i18n:@hashtags%</li>
 | 
			
		||||
 | 
			
		||||
			<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }">%fa:cloud .fw%%i18n:common.drive%</li> -->
 | 
			
		||||
			<!-- <li @click="nav('update')" :class="{ active: page == 'update' }">%i18n:@update%</li> -->
 | 
			
		||||
		</ul>
 | 
			
		||||
		<div class="back-to-misskey">
 | 
			
		||||
			<a href="/">%fa:arrow-left% %i18n:@back-to-misskey%</a>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="version">
 | 
			
		||||
			<small>Misskey {{ version }}</small>
 | 
			
		||||
		</div>
 | 
			
		||||
	</nav>
 | 
			
		||||
	<main>
 | 
			
		||||
		<div v-show="page == 'dashboard'"><x-dashboard/></div>
 | 
			
		||||
		<div v-show="page == 'instance'"><x-instance/></div>
 | 
			
		||||
		<div v-if="page == 'users'"><x-users/></div>
 | 
			
		||||
		<div v-show="page == 'emoji'"><x-emoji/></div>
 | 
			
		||||
		<div v-show="page == 'announcements'"><x-announcements/></div>
 | 
			
		||||
		<div v-show="page == 'hashtags'"><x-hashtags/></div>
 | 
			
		||||
		<div v-if="page == 'drive'"></div>
 | 
			
		||||
		<div v-if="page == 'update'"></div>
 | 
			
		||||
	</main>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
import { version } from '../../config';
 | 
			
		||||
import XDashboard from "./dashboard.vue";
 | 
			
		||||
import XInstance from "./instance.vue";
 | 
			
		||||
import XEmoji from "./emoji.vue";
 | 
			
		||||
import XAnnouncements from "./announcements.vue";
 | 
			
		||||
import XHashtags from "./hashtags.vue";
 | 
			
		||||
import XUsers from "./users.vue";
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XDashboard,
 | 
			
		||||
		XInstance,
 | 
			
		||||
		XEmoji,
 | 
			
		||||
		XAnnouncements,
 | 
			
		||||
		XHashtags,
 | 
			
		||||
		XUsers
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			page: 'dashboard',
 | 
			
		||||
			version
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		nav(page: string) {
 | 
			
		||||
			this.page = page;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
.mk-admin
 | 
			
		||||
	display flex
 | 
			
		||||
	height 100%
 | 
			
		||||
 | 
			
		||||
	> nav
 | 
			
		||||
		position fixed
 | 
			
		||||
		z-index 10000
 | 
			
		||||
		top 0
 | 
			
		||||
		left 0
 | 
			
		||||
		width 250px
 | 
			
		||||
		height 100vh
 | 
			
		||||
		overflow auto
 | 
			
		||||
		background #333
 | 
			
		||||
		color #fff
 | 
			
		||||
 | 
			
		||||
		> .mi
 | 
			
		||||
			text-align center
 | 
			
		||||
 | 
			
		||||
			> svg
 | 
			
		||||
				width 24px
 | 
			
		||||
				height 82px
 | 
			
		||||
				vertical-align top
 | 
			
		||||
				fill #fff
 | 
			
		||||
				opacity 0.7
 | 
			
		||||
 | 
			
		||||
		> .me
 | 
			
		||||
			display flex
 | 
			
		||||
			margin 0 16px 16px 16px
 | 
			
		||||
			padding 16px 0
 | 
			
		||||
			align-items center
 | 
			
		||||
			border-top solid 1px #555
 | 
			
		||||
			border-bottom solid 1px #555
 | 
			
		||||
 | 
			
		||||
			> .avatar
 | 
			
		||||
				height 48px
 | 
			
		||||
				border-radius 100%
 | 
			
		||||
				vertical-align middle
 | 
			
		||||
 | 
			
		||||
			> .name
 | 
			
		||||
				margin 0 16px
 | 
			
		||||
				padding 0
 | 
			
		||||
				color #fff
 | 
			
		||||
				overflow hidden
 | 
			
		||||
				text-overflow ellipsis
 | 
			
		||||
				white-space nowrap
 | 
			
		||||
				font-size 15px
 | 
			
		||||
 | 
			
		||||
		> .back-to-misskey
 | 
			
		||||
			margin 16px 16px 0 16px
 | 
			
		||||
			padding 0
 | 
			
		||||
			border-top solid 1px #555
 | 
			
		||||
 | 
			
		||||
			> a
 | 
			
		||||
				display block
 | 
			
		||||
				padding 16px 4px
 | 
			
		||||
				color inherit
 | 
			
		||||
				text-decoration none
 | 
			
		||||
				color #eee
 | 
			
		||||
				font-size 15px
 | 
			
		||||
 | 
			
		||||
				&:hover
 | 
			
		||||
					color #fff
 | 
			
		||||
 | 
			
		||||
				> [data-fa]
 | 
			
		||||
					margin-right 6px
 | 
			
		||||
 | 
			
		||||
		> .version
 | 
			
		||||
			margin 0 16px 16px 16px
 | 
			
		||||
			padding-top 16px
 | 
			
		||||
			border-top solid 1px #555
 | 
			
		||||
			text-align center
 | 
			
		||||
 | 
			
		||||
			> small
 | 
			
		||||
				opacity 0.7
 | 
			
		||||
 | 
			
		||||
		> ul
 | 
			
		||||
			margin 0
 | 
			
		||||
			padding 0
 | 
			
		||||
			list-style none
 | 
			
		||||
			font-size 15px
 | 
			
		||||
 | 
			
		||||
			> li
 | 
			
		||||
				display block
 | 
			
		||||
				padding 10px 16px
 | 
			
		||||
				margin 0
 | 
			
		||||
				cursor pointer
 | 
			
		||||
				user-select none
 | 
			
		||||
				color #eee
 | 
			
		||||
				transition margin-left 0.2s ease
 | 
			
		||||
 | 
			
		||||
				&:hover
 | 
			
		||||
					color #fff
 | 
			
		||||
 | 
			
		||||
				> [data-fa]
 | 
			
		||||
					margin-right 6px
 | 
			
		||||
 | 
			
		||||
				&.active
 | 
			
		||||
					margin-left 8px
 | 
			
		||||
					color var(--primary) !important
 | 
			
		||||
 | 
			
		||||
					&:after
 | 
			
		||||
						content ""
 | 
			
		||||
						display block
 | 
			
		||||
						position absolute
 | 
			
		||||
						top 0
 | 
			
		||||
						right 0
 | 
			
		||||
						bottom 0
 | 
			
		||||
						margin auto 0
 | 
			
		||||
						height 0
 | 
			
		||||
						border-top solid 16px transparent
 | 
			
		||||
						border-right solid 16px var(--bg)
 | 
			
		||||
						border-bottom solid 16px transparent
 | 
			
		||||
						border-left solid 16px transparent
 | 
			
		||||
 | 
			
		||||
	> main
 | 
			
		||||
		width 100%
 | 
			
		||||
		padding 32px 32px 32px calc(32px + 250px)
 | 
			
		||||
		max-width 1300px
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										62
									
								
								src/client/app/admin/views/instance.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/client/app/admin/views/instance.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div>
 | 
			
		||||
	<ui-card>
 | 
			
		||||
		<div slot="title">%i18n:@banner-url%</div>
 | 
			
		||||
		<section class="fit-top">
 | 
			
		||||
			<ui-input v-model="bannerUrl"/>
 | 
			
		||||
			<ui-button @click="updateMeta">%i18n:@save%</ui-button>
 | 
			
		||||
		</section>
 | 
			
		||||
	</ui-card>
 | 
			
		||||
 | 
			
		||||
	<ui-card>
 | 
			
		||||
		<div slot="title">%i18n:@disable-registration%</div>
 | 
			
		||||
		<section>
 | 
			
		||||
			<input type="checkbox" v-model="disableRegistration" @change="updateMeta">
 | 
			
		||||
			<button class="ui" @click="invite">%i18n:@invite%</button>
 | 
			
		||||
			<p v-if="inviteCode">Code: <code>{{ inviteCode }}</code></p>
 | 
			
		||||
		</section>
 | 
			
		||||
	</ui-card>
 | 
			
		||||
 | 
			
		||||
	<ui-card>
 | 
			
		||||
		<div slot="title">%i18n:@disable-local-timeline%</div>
 | 
			
		||||
		<section>
 | 
			
		||||
			<input type="checkbox" v-model="disableLocalTimeline" @change="updateMeta">
 | 
			
		||||
		</section>
 | 
			
		||||
	</ui-card>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			disableRegistration: false,
 | 
			
		||||
			disableLocalTimeline: false,
 | 
			
		||||
			bannerUrl: null,
 | 
			
		||||
			inviteCode: null,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		invite() {
 | 
			
		||||
			(this as any).api('admin/invite').then(x => {
 | 
			
		||||
				this.inviteCode = x.code;
 | 
			
		||||
			}).catch(e => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Failed ${e}` });
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		updateMeta() {
 | 
			
		||||
			(this as any).api('admin/update-meta', {
 | 
			
		||||
				disableRegistration: this.disableRegistration,
 | 
			
		||||
				disableLocalTimeline: this.disableLocalTimeline,
 | 
			
		||||
				bannerUrl: this.bannerUrl
 | 
			
		||||
			}).then(() => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Saved` });
 | 
			
		||||
			}).catch(e => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Failed ${e}` });
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										129
									
								
								src/client/app/admin/views/users.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								src/client/app/admin/views/users.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div>
 | 
			
		||||
	<ui-card>
 | 
			
		||||
		<div slot="title">%i18n:@verify-user%</div>
 | 
			
		||||
		<section class="fit-top">
 | 
			
		||||
			<ui-input v-model="verifyUsername" type="text">
 | 
			
		||||
				<span slot="prefix">@</span>
 | 
			
		||||
			</ui-input>
 | 
			
		||||
			<ui-button @click="verifyUser" :disabled="verifying">%i18n:@verify%</ui-button>
 | 
			
		||||
		</section>
 | 
			
		||||
	</ui-card>
 | 
			
		||||
 | 
			
		||||
	<ui-card>
 | 
			
		||||
		<div slot="title">%i18n:@unverify-user%</div>
 | 
			
		||||
		<section class="fit-top">
 | 
			
		||||
			<ui-input v-model="unverifyUsername" type="text">
 | 
			
		||||
				<span slot="prefix">@</span>
 | 
			
		||||
			</ui-input>
 | 
			
		||||
			<ui-button @click="unverifyUser" :disabled="unverifying">%i18n:@unverify%</ui-button>
 | 
			
		||||
		</section>
 | 
			
		||||
	</ui-card>
 | 
			
		||||
 | 
			
		||||
	<ui-card>
 | 
			
		||||
		<div slot="title">%i18n:@suspend-user%</div>
 | 
			
		||||
		<section class="fit-top">
 | 
			
		||||
			<ui-input v-model="suspendUsername" type="text">
 | 
			
		||||
				<span slot="prefix">@</span>
 | 
			
		||||
			</ui-input>
 | 
			
		||||
			<ui-button @click="suspendUser" :disabled="suspending">%i18n:@suspend%</ui-button>
 | 
			
		||||
		</section>
 | 
			
		||||
	</ui-card>
 | 
			
		||||
 | 
			
		||||
	<ui-card>
 | 
			
		||||
		<div slot="title">%i18n:@unsuspend-user%</div>
 | 
			
		||||
		<section class="fit-top">
 | 
			
		||||
			<ui-input v-model="unsuspendUsername" type="text">
 | 
			
		||||
				<span slot="prefix">@</span>
 | 
			
		||||
			</ui-input>
 | 
			
		||||
			<ui-button @click="unsuspendUser" :disabled="unsuspending">%i18n:@unsuspend%</ui-button>
 | 
			
		||||
		</section>
 | 
			
		||||
	</ui-card>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
import parseAcct from "../../../../misc/acct/parse";
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			verifyUsername: null,
 | 
			
		||||
			verifying: false,
 | 
			
		||||
			unverifyUsername: null,
 | 
			
		||||
			unverifying: false,
 | 
			
		||||
			suspendUsername: null,
 | 
			
		||||
			suspending: false,
 | 
			
		||||
			unsuspendUsername: null,
 | 
			
		||||
			unsuspending: false
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		async verifyUser() {
 | 
			
		||||
			this.verifying = true;
 | 
			
		||||
 | 
			
		||||
			const process = async () => {
 | 
			
		||||
				const user = await (this as any).os.api('users/show', parseAcct(this.verifyUsername));
 | 
			
		||||
				await (this as any).os.api('admin/verify-user', { userId: user.id });
 | 
			
		||||
				(this as any).os.apis.dialog({ text: '%i18n:@verified%' });
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			await process().catch(e => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Failed: ${e}` });
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			this.verifying = false;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		async unverifyUser() {
 | 
			
		||||
			this.unverifying = true;
 | 
			
		||||
 | 
			
		||||
			const process = async () => {
 | 
			
		||||
				const user = await (this as any).os.api('users/show', parseAcct(this.unverifyUsername));
 | 
			
		||||
				await (this as any).os.api('admin/unverify-user', { userId: user.id });
 | 
			
		||||
				(this as any).os.apis.dialog({ text: '%i18n:@unverified%' });
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			await process().catch(e => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Failed: ${e}` });
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			this.unverifying = false;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		async suspendUser() {
 | 
			
		||||
			this.suspending = true;
 | 
			
		||||
 | 
			
		||||
			const process = async () => {
 | 
			
		||||
				const user = await (this as any).os.api('users/show', parseAcct(this.suspendUsername));
 | 
			
		||||
				await (this as any).os.api('admin/suspend-user', { userId: user.id });
 | 
			
		||||
				(this as any).os.apis.dialog({ text: '%i18n:@suspended%' });
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			await process().catch(e => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Failed: ${e}` });
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			this.suspending = false;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		async unsuspendUser() {
 | 
			
		||||
			this.unsuspending = true;
 | 
			
		||||
 | 
			
		||||
			const process = async () => {
 | 
			
		||||
				const user = await (this as any).os.api('users/show', parseAcct(this.unsuspendUsername));
 | 
			
		||||
				await (this as any).os.api('admin/unsuspend-user', { userId: user.id });
 | 
			
		||||
				(this as any).os.apis.dialog({ text: '%i18n:@unsuspended%' });
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			await process().catch(e => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Failed: ${e}` });
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			this.unsuspending = false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
@@ -46,6 +46,7 @@
 | 
			
		||||
	if (`${url.pathname}/`.startsWith('/docs/')) app = 'docs';
 | 
			
		||||
	if (`${url.pathname}/`.startsWith('/dev/')) app = 'dev';
 | 
			
		||||
	if (`${url.pathname}/`.startsWith('/auth/')) app = 'auth';
 | 
			
		||||
	if (`${url.pathname}/`.startsWith('/admin/')) app = 'admin';
 | 
			
		||||
	//#endregion
 | 
			
		||||
 | 
			
		||||
	//#region Detect the user language
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import MiOS from '../../mios';
 | 
			
		||||
import { version as current } from '../../config';
 | 
			
		||||
import { clientVersion as current } from '../../config';
 | 
			
		||||
 | 
			
		||||
export default async function(mios: MiOS, force = false, silent = false) {
 | 
			
		||||
	const meta = await mios.getMeta(force);
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
			<span class="emoji" v-if="emoji.url"><img :src="emoji.url" :alt="emoji.emoji"/></span>
 | 
			
		||||
			<span class="emoji" v-else>{{ emoji.emoji }}</span>
 | 
			
		||||
			<span class="name" v-html="emoji.name.replace(q, `<b>${q}</b>`)"></span>
 | 
			
		||||
			<span class="alias" v-if="emoji.alias">({{ emoji.alias }})</span>
 | 
			
		||||
			<span class="alias" v-if="emoji.aliasOf">({{ emoji.aliasOf }})</span>
 | 
			
		||||
		</li>
 | 
			
		||||
	</ol>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -28,14 +28,21 @@ import Vue from 'vue';
 | 
			
		||||
import * as emojilib from 'emojilib';
 | 
			
		||||
import contains from '../../../common/scripts/contains';
 | 
			
		||||
 | 
			
		||||
type EmojiDef = {
 | 
			
		||||
	emoji: string;
 | 
			
		||||
	name: string;
 | 
			
		||||
	aliasOf?: string;
 | 
			
		||||
	url?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const lib = Object.entries(emojilib.lib).filter((x: any) => {
 | 
			
		||||
	return x[1].category != 'flags';
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emjdb = lib.map((x: any) => ({
 | 
			
		||||
const emjdb: EmojiDef[] = lib.map((x: any) => ({
 | 
			
		||||
	emoji: x[1].char,
 | 
			
		||||
	name: x[0],
 | 
			
		||||
	alias: null
 | 
			
		||||
	aliasOf: null
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
lib.forEach((x: any) => {
 | 
			
		||||
@@ -44,7 +51,7 @@ lib.forEach((x: any) => {
 | 
			
		||||
			emjdb.push({
 | 
			
		||||
				emoji: x[1].char,
 | 
			
		||||
				name: k,
 | 
			
		||||
				alias: x[0]
 | 
			
		||||
				aliasOf: x[0]
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
@@ -62,7 +69,8 @@ export default Vue.extend({
 | 
			
		||||
			hashtags: [],
 | 
			
		||||
			emojis: [],
 | 
			
		||||
			select: -1,
 | 
			
		||||
			emojilib
 | 
			
		||||
			emojilib,
 | 
			
		||||
			emojiDb: [] as EmojiDef[]
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -91,6 +99,34 @@ export default Vue.extend({
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		//#region Construct Emoji DB
 | 
			
		||||
		const customEmojis = (this.os.getMetaSync() || { emojis: [] }).emojis || [];
 | 
			
		||||
		const emojiDefinitions: EmojiDef[] = [];
 | 
			
		||||
 | 
			
		||||
		customEmojis.forEach(x => {
 | 
			
		||||
			emojiDefinitions.push({
 | 
			
		||||
				name: x.name,
 | 
			
		||||
				emoji: `:${x.name}:`,
 | 
			
		||||
				url: x.url
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			if (x.aliases) {
 | 
			
		||||
				x.aliases.forEach(alias => {
 | 
			
		||||
					emojiDefinitions.push({
 | 
			
		||||
						name: alias,
 | 
			
		||||
						aliasOf: x.name,
 | 
			
		||||
						emoji: `:${x.name}:`,
 | 
			
		||||
						url: x.url
 | 
			
		||||
					});
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		emojiDefinitions.sort((a, b) => a.name.length - b.name.length);
 | 
			
		||||
 | 
			
		||||
		this.emojiDb = emojiDefinitions.concat(emjdb);
 | 
			
		||||
		//#endregion
 | 
			
		||||
 | 
			
		||||
		this.textarea.addEventListener('keydown', this.onKeydown);
 | 
			
		||||
 | 
			
		||||
		Array.from(document.querySelectorAll('body *')).forEach(el => {
 | 
			
		||||
@@ -172,39 +208,19 @@ export default Vue.extend({
 | 
			
		||||
				const matched = [];
 | 
			
		||||
				const max = 30;
 | 
			
		||||
 | 
			
		||||
				const customEmojis = (this.os.getMetaSync() || { emojis: [] }).emojis;
 | 
			
		||||
				customEmojis.some(x => {
 | 
			
		||||
					if (x.name.startsWith(this.q)) matched.push({
 | 
			
		||||
						name: x.name,
 | 
			
		||||
						emoji: `:${x.name}:`,
 | 
			
		||||
						url: x.url
 | 
			
		||||
					});
 | 
			
		||||
					return matched.length == max;
 | 
			
		||||
				});
 | 
			
		||||
				customEmojis.some(x => {
 | 
			
		||||
					const alias = (x.aliases || []).find(a => a.startsWith(this.q));
 | 
			
		||||
					if (alias) matched.push({
 | 
			
		||||
						alias: x.name,
 | 
			
		||||
						name: alias,
 | 
			
		||||
						emoji: `:${x.name}:`,
 | 
			
		||||
						url: x.url
 | 
			
		||||
					});
 | 
			
		||||
					return matched.length == max;
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				emjdb.some(x => {
 | 
			
		||||
					if (x.name.indexOf(this.q) == 0 && !x.alias && !matched.some(y => y.emoji == x.emoji)) matched.push(x);
 | 
			
		||||
				this.emojiDb.some(x => {
 | 
			
		||||
					if (x.name.startsWith(this.q) && !x.aliasOf && !matched.some(y => y.emoji == x.emoji)) matched.push(x);
 | 
			
		||||
					return matched.length == max;
 | 
			
		||||
				});
 | 
			
		||||
				if (matched.length < max) {
 | 
			
		||||
					emjdb.some(x => {
 | 
			
		||||
						if (x.name.indexOf(this.q) == 0 && !matched.some(y => y.emoji == x.emoji)) matched.push(x);
 | 
			
		||||
					this.emojiDb.some(x => {
 | 
			
		||||
						if (x.name.startsWith(this.q) && !matched.some(y => y.emoji == x.emoji)) matched.push(x);
 | 
			
		||||
						return matched.length == max;
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
				if (matched.length < max) {
 | 
			
		||||
					emjdb.some(x => {
 | 
			
		||||
						if (x.name.indexOf(this.q) > -1 && !matched.some(y => y.emoji == x.emoji)) matched.push(x);
 | 
			
		||||
					this.emojiDb.some(x => {
 | 
			
		||||
						if (x.name.includes(this.q) && !matched.some(y => y.emoji == x.emoji)) matched.push(x);
 | 
			
		||||
						return matched.length == max;
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<a class="a" href="https://github.com/syuilo/misskey" target="_blank" title="View source on Github">
 | 
			
		||||
<a class="a" href="https://github.com/syuilo/misskey" target="_blank" title="View source on GitHub">
 | 
			
		||||
	<svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="aria-hidden">
 | 
			
		||||
		<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
 | 
			
		||||
		<path class="octo-arm" d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor"></path>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ import Vue from 'vue';
 | 
			
		||||
import muteAndBlock from './mute-and-block.vue';
 | 
			
		||||
import error from './error.vue';
 | 
			
		||||
import apiSettings from './api-settings.vue';
 | 
			
		||||
import passwordSettings from './password-settings.vue';
 | 
			
		||||
import driveSettings from './drive-settings.vue';
 | 
			
		||||
import profileEditor from './profile-editor.vue';
 | 
			
		||||
import noteSkeleton from './note-skeleton.vue';
 | 
			
		||||
@@ -41,6 +42,7 @@ import Reversi from './games/reversi/reversi.vue';
 | 
			
		||||
import welcomeTimeline from './welcome-timeline.vue';
 | 
			
		||||
import uiInput from './ui/input.vue';
 | 
			
		||||
import uiButton from './ui/button.vue';
 | 
			
		||||
import uiButtonGroup from './ui/button-group.vue';
 | 
			
		||||
import uiCard from './ui/card.vue';
 | 
			
		||||
import uiForm from './ui/form.vue';
 | 
			
		||||
import uiTextarea from './ui/textarea.vue';
 | 
			
		||||
@@ -54,6 +56,7 @@ import formRadio from './ui/form/radio.vue';
 | 
			
		||||
Vue.component('mk-mute-and-block', muteAndBlock);
 | 
			
		||||
Vue.component('mk-error', error);
 | 
			
		||||
Vue.component('mk-api-settings', apiSettings);
 | 
			
		||||
Vue.component('mk-password-settings', passwordSettings);
 | 
			
		||||
Vue.component('mk-drive-settings', driveSettings);
 | 
			
		||||
Vue.component('mk-profile-editor', profileEditor);
 | 
			
		||||
Vue.component('mk-note-skeleton', noteSkeleton);
 | 
			
		||||
@@ -92,6 +95,7 @@ Vue.component('mk-reversi', Reversi);
 | 
			
		||||
Vue.component('mk-welcome-timeline', welcomeTimeline);
 | 
			
		||||
Vue.component('ui-input', uiInput);
 | 
			
		||||
Vue.component('ui-button', uiButton);
 | 
			
		||||
Vue.component('ui-button-group', uiButtonGroup);
 | 
			
		||||
Vue.component('ui-card', uiCard);
 | 
			
		||||
Vue.component('ui-form', uiForm);
 | 
			
		||||
Vue.component('ui-textarea', uiTextarea);
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,9 @@ export default Vue.component('misskey-flavored-markdown', {
 | 
			
		||||
		i: {
 | 
			
		||||
			type: Object,
 | 
			
		||||
			default: null
 | 
			
		||||
		},
 | 
			
		||||
		customEmojis: {
 | 
			
		||||
			required: false,
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -186,8 +189,8 @@ export default Vue.component('misskey-flavored-markdown', {
 | 
			
		||||
 | 
			
		||||
				case 'emoji': {
 | 
			
		||||
					//#region カスタム絵文字
 | 
			
		||||
					const customEmojis = (this.os.getMetaSync() || { emojis: [] }).emojis;
 | 
			
		||||
					const customEmoji = customEmojis.find(e => e.name == token.emoji || (e.aliases || []).includes(token.emoji));
 | 
			
		||||
					if (this.customEmojis != null) {
 | 
			
		||||
						const customEmoji = this.customEmojis.find(e => e.name == token.emoji || (e.aliases || []).includes(token.emoji));
 | 
			
		||||
						if (customEmoji) {
 | 
			
		||||
							return [createElement('img', {
 | 
			
		||||
								attrs: {
 | 
			
		||||
@@ -198,6 +201,7 @@ export default Vue.component('misskey-flavored-markdown', {
 | 
			
		||||
								}
 | 
			
		||||
							})];
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					//#endregion
 | 
			
		||||
 | 
			
		||||
					const emoji = emojilib.lib[token.emoji];
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								src/client/app/common/views/components/ui/button-group.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/client/app/common/views/components/ui/button-group.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="pfzekjfwkwvadvlujpdnnxfggqgqjoze">
 | 
			
		||||
	<slot></slot>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
export default Vue.extend({});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.pfzekjfwkwvadvlujpdnnxfggqgqjoze
 | 
			
		||||
	display flex
 | 
			
		||||
 | 
			
		||||
	> *
 | 
			
		||||
		flex 1
 | 
			
		||||
 | 
			
		||||
		&:not(:last-child)
 | 
			
		||||
			margin-right 16px
 | 
			
		||||
</style>
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
					</div>
 | 
			
		||||
				</header>
 | 
			
		||||
				<div class="text">
 | 
			
		||||
					<misskey-flavored-markdown v-if="note.text" :text="note.text"/>
 | 
			
		||||
					<misskey-flavored-markdown v-if="note.text" :text="note.text" :customEmojis="note.emojis"/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
Vue.filter('bytes', (v, digits = 0) => {
 | 
			
		||||
	if (v == null) return '?';
 | 
			
		||||
	const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
 | 
			
		||||
	if (v == 0) return '0';
 | 
			
		||||
	const isMinus = v < 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
Vue.filter('number', (n) => {
 | 
			
		||||
	if (n == null) return 'N/A';
 | 
			
		||||
	return n.toLocaleString();
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { version, codename } from '../../../config';
 | 
			
		||||
import { clientVersion as version, codename } from '../../../config';
 | 
			
		||||
import define from '../../../common/define-widget';
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'version'
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ declare const _LANGS_: string;
 | 
			
		||||
declare const _THEME_COLOR_: string;
 | 
			
		||||
declare const _COPYRIGHT_: string;
 | 
			
		||||
declare const _VERSION_: string;
 | 
			
		||||
declare const _CLIENT_VERSION_: string;
 | 
			
		||||
declare const _CODENAME_: string;
 | 
			
		||||
declare const _ENV_: string;
 | 
			
		||||
 | 
			
		||||
@@ -18,5 +19,6 @@ export const langs = _LANGS_;
 | 
			
		||||
export const themeColor = _THEME_COLOR_;
 | 
			
		||||
export const copyright = _COPYRIGHT_;
 | 
			
		||||
export const version = _VERSION_;
 | 
			
		||||
export const clientVersion = _CLIENT_VERSION_;
 | 
			
		||||
export const codename = _CODENAME_;
 | 
			
		||||
export const env = _ENV_;
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,6 @@ import updateBanner from './api/update-banner';
 | 
			
		||||
import MkIndex from './views/pages/index.vue';
 | 
			
		||||
import MkHome from './views/pages/home.vue';
 | 
			
		||||
import MkDeck from './views/pages/deck/deck.vue';
 | 
			
		||||
import MkAdmin from './views/pages/admin/admin.vue';
 | 
			
		||||
import MkStats from './views/pages/stats/stats.vue';
 | 
			
		||||
import MkUser from './views/pages/user/user.vue';
 | 
			
		||||
import MkFavorites from './views/pages/favorites.vue';
 | 
			
		||||
@@ -57,7 +56,6 @@ init(async (launch) => {
 | 
			
		||||
			{ path: '/', name: 'index', component: MkIndex },
 | 
			
		||||
			{ path: '/home', name: 'home', component: MkHome },
 | 
			
		||||
			{ path: '/deck', name: 'deck', component: MkDeck },
 | 
			
		||||
			{ path: '/admin', name: 'admin', component: MkAdmin },
 | 
			
		||||
			{ path: '/stats', name: 'stats', component: MkStats },
 | 
			
		||||
			{ path: '/i/customize-home', component: MkHomeCustomize },
 | 
			
		||||
			{ path: '/i/favorites', component: MkFavorites },
 | 
			
		||||
 
 | 
			
		||||
@@ -32,9 +32,21 @@ export default Vue.extend({
 | 
			
		||||
		this.data.forEach(d => d.total = d.notes + d.replies + d.renotes);
 | 
			
		||||
		const peak = Math.max.apply(null, this.data.map(d => d.total));
 | 
			
		||||
 | 
			
		||||
		const now = new Date();
 | 
			
		||||
		const year = now.getFullYear();
 | 
			
		||||
		const month = now.getMonth();
 | 
			
		||||
		const day = now.getDate();
 | 
			
		||||
 | 
			
		||||
		let x = 0;
 | 
			
		||||
		this.data.slice().reverse().forEach(d => {
 | 
			
		||||
		this.data.slice().reverse().forEach((d, i) => {
 | 
			
		||||
			d.x = x;
 | 
			
		||||
 | 
			
		||||
			const date = new Date(year, month, day - i);
 | 
			
		||||
			d.date = {
 | 
			
		||||
				year: date.getFullYear(),
 | 
			
		||||
				month: date.getMonth(),
 | 
			
		||||
				day: date.getDate()
 | 
			
		||||
			};
 | 
			
		||||
			d.date.weekday = (new Date(d.date.year, d.date.month - 1, d.date.day)).getDay();
 | 
			
		||||
 | 
			
		||||
			d.v = peak == 0 ? 0 : d.total / (peak / 2);
 | 
			
		||||
 
 | 
			
		||||
@@ -43,11 +43,17 @@ export default Vue.extend({
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		(this as any).api('aggregation/users/activity', {
 | 
			
		||||
		(this as any).api('charts/user/notes', {
 | 
			
		||||
			userId: this.user.id,
 | 
			
		||||
			limit: 20 * 7
 | 
			
		||||
			span: 'day',
 | 
			
		||||
			limit: 7 * 20
 | 
			
		||||
		}).then(activity => {
 | 
			
		||||
			this.activity = activity;
 | 
			
		||||
			this.activity = activity.diffs.normal.map((_, i) => ({
 | 
			
		||||
				total: activity.diffs.normal[i] + activity.diffs.reply[i] + activity.diffs.renote[i],
 | 
			
		||||
				notes: activity.diffs.normal[i],
 | 
			
		||||
				replies: activity.diffs.reply[i],
 | 
			
		||||
				renotes: activity.diffs.renote[i]
 | 
			
		||||
			}));
 | 
			
		||||
			this.fetching = false;
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,7 @@
 | 
			
		||||
				<div class="text">
 | 
			
		||||
					<span v-if="p.isHidden" style="opacity: 0.5">%i18n:@private%</span>
 | 
			
		||||
					<span v-if="p.deletedAt" style="opacity: 0.5">%i18n:@deleted%</span>
 | 
			
		||||
					<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
 | 
			
		||||
					<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i" :customEmojis="p.emojis" />
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="files" v-if="p.files.length > 0">
 | 
			
		||||
					<mk-media-list :media-list="p.files" :raw="true"/>
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@
 | 
			
		||||
					<div class="text">
 | 
			
		||||
						<span v-if="appearNote.isHidden" style="opacity: 0.5">%i18n:@private%</span>
 | 
			
		||||
						<a class="reply" v-if="appearNote.reply">%fa:reply%</a>
 | 
			
		||||
						<misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :class="$style.text"/>
 | 
			
		||||
						<misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :class="$style.text" :customEmojis="appearNote.emojis"/>
 | 
			
		||||
						<a class="rp" v-if="appearNote.renote">RN:</a>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="files" v-if="appearNote.files.length > 0">
 | 
			
		||||
 
 | 
			
		||||
@@ -214,7 +214,7 @@
 | 
			
		||||
		<ui-card class="password" v-show="page == 'security'">
 | 
			
		||||
			<div slot="title">%fa:unlock-alt% %i18n:@password%</div>
 | 
			
		||||
			<section>
 | 
			
		||||
				<x-password/>
 | 
			
		||||
				<mk-password-settings/>
 | 
			
		||||
			</section>
 | 
			
		||||
		</ui-card>
 | 
			
		||||
 | 
			
		||||
@@ -286,17 +286,15 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import XPassword from './settings.password.vue';
 | 
			
		||||
import X2fa from './settings.2fa.vue';
 | 
			
		||||
import XApps from './settings.apps.vue';
 | 
			
		||||
import XSignins from './settings.signins.vue';
 | 
			
		||||
import XTags from './settings.tags.vue';
 | 
			
		||||
import { url, langs, version } from '../../../config';
 | 
			
		||||
import { url, langs, clientVersion as version } from '../../../config';
 | 
			
		||||
import checkForUpdate from '../../../common/scripts/check-for-update';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XPassword,
 | 
			
		||||
		X2fa,
 | 
			
		||||
		XApps,
 | 
			
		||||
		XSignins,
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
		<span v-if="note.isHidden" style="opacity: 0.5">%i18n:@private%</span>
 | 
			
		||||
		<span v-if="note.deletedAt" style="opacity: 0.5">%i18n:@deleted%</span>
 | 
			
		||||
		<a class="reply" v-if="note.replyId">%fa:reply%</a>
 | 
			
		||||
		<misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i"/>
 | 
			
		||||
		<misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i" :customEmojis="note.emojis"/>
 | 
			
		||||
		<a class="rp" v-if="note.renoteId" :href="`/notes/${note.renoteId}`">RN: ...</a>
 | 
			
		||||
	</div>
 | 
			
		||||
	<details v-if="note.files.length > 0">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,52 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="qldxjjsrseehkusjuoooapmsprvfrxyl mk-admin-card">
 | 
			
		||||
	<header>%i18n:@announcements%</header>
 | 
			
		||||
	<textarea v-model="broadcasts" placeholder='[ { "title": "Title1", "text": "Text1" }, { "title": "Title2", "text": "Text2" } ]'></textarea>
 | 
			
		||||
	<button class="ui" @click="save">%i18n:@save%</button>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			broadcasts: '',
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	created() {
 | 
			
		||||
		(this as any).os.getMeta().then(meta => {
 | 
			
		||||
			this.broadcasts = JSON.stringify(meta.broadcasts, null, '  ');
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		save() {
 | 
			
		||||
			let json;
 | 
			
		||||
 | 
			
		||||
			try {
 | 
			
		||||
				json = JSON.parse(this.broadcasts);
 | 
			
		||||
			} catch (e) {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Failed: ${e}` });
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			(this as any).api('admin/update-meta', {
 | 
			
		||||
				broadcasts: json
 | 
			
		||||
			}).then(() => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Saved` });
 | 
			
		||||
			}.catch(e => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Failed ${e}` });
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.qldxjjsrseehkusjuoooapmsprvfrxyl
 | 
			
		||||
	textarea
 | 
			
		||||
		width 100%
 | 
			
		||||
		min-height 300px
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,137 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="zyknedwtlthezamcjlolyusmipqmjgxz">
 | 
			
		||||
	<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
 | 
			
		||||
		<defs>
 | 
			
		||||
			<linearGradient :id="cpuGradientId" x1="0" x2="0" y1="1" y2="0">
 | 
			
		||||
				<stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop>
 | 
			
		||||
				<stop offset="100%" stop-color="hsl(0, 80%, 70%)"></stop>
 | 
			
		||||
			</linearGradient>
 | 
			
		||||
			<mask :id="cpuMaskId" x="0" y="0" :width="viewBoxX" :height="viewBoxY">
 | 
			
		||||
				<polygon
 | 
			
		||||
					:points="cpuPolygonPoints"
 | 
			
		||||
					fill="#fff"
 | 
			
		||||
					fill-opacity="0.5"/>
 | 
			
		||||
				<polyline
 | 
			
		||||
					:points="cpuPolylinePoints"
 | 
			
		||||
					fill="none"
 | 
			
		||||
					stroke="#fff"
 | 
			
		||||
					stroke-width="1"/>
 | 
			
		||||
			</mask>
 | 
			
		||||
		</defs>
 | 
			
		||||
		<rect
 | 
			
		||||
			x="0" y="0"
 | 
			
		||||
			:width="viewBoxX" :height="viewBoxY"
 | 
			
		||||
			:style="`stroke: none; fill: url(#${ cpuGradientId }); mask: url(#${ cpuMaskId })`"/>
 | 
			
		||||
		<text x="1" y="12">CPU <tspan>{{ cpuP }}%</tspan></text>
 | 
			
		||||
	</svg>
 | 
			
		||||
	<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
 | 
			
		||||
		<defs>
 | 
			
		||||
			<linearGradient :id="memGradientId" x1="0" x2="0" y1="1" y2="0">
 | 
			
		||||
				<stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop>
 | 
			
		||||
				<stop offset="100%" stop-color="hsl(0, 80%, 70%)"></stop>
 | 
			
		||||
			</linearGradient>
 | 
			
		||||
			<mask :id="memMaskId" x="0" y="0" :width="viewBoxX" :height="viewBoxY">
 | 
			
		||||
				<polygon
 | 
			
		||||
					:points="memPolygonPoints"
 | 
			
		||||
					fill="#fff"
 | 
			
		||||
					fill-opacity="0.5"/>
 | 
			
		||||
				<polyline
 | 
			
		||||
					:points="memPolylinePoints"
 | 
			
		||||
					fill="none"
 | 
			
		||||
					stroke="#fff"
 | 
			
		||||
					stroke-width="1"/>
 | 
			
		||||
			</mask>
 | 
			
		||||
		</defs>
 | 
			
		||||
		<rect
 | 
			
		||||
			x="0" y="0"
 | 
			
		||||
			:width="viewBoxX" :height="viewBoxY"
 | 
			
		||||
			:style="`stroke: none; fill: url(#${ memGradientId }); mask: url(#${ memMaskId })`"/>
 | 
			
		||||
		<text x="1" y="12">MEM <tspan>{{ memP }}%</tspan></text>
 | 
			
		||||
	</svg>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: ['connection'],
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			viewBoxX: 200,
 | 
			
		||||
			viewBoxY: 70,
 | 
			
		||||
			stats: [],
 | 
			
		||||
			cpuGradientId: uuid(),
 | 
			
		||||
			cpuMaskId: uuid(),
 | 
			
		||||
			memGradientId: uuid(),
 | 
			
		||||
			memMaskId: uuid(),
 | 
			
		||||
			cpuPolylinePoints: '',
 | 
			
		||||
			memPolylinePoints: '',
 | 
			
		||||
			cpuPolygonPoints: '',
 | 
			
		||||
			memPolygonPoints: '',
 | 
			
		||||
			cpuP: '',
 | 
			
		||||
			memP: ''
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.connection.on('stats', this.onStats);
 | 
			
		||||
		this.connection.on('statsLog', this.onStatsLog);
 | 
			
		||||
		this.connection.send('requestLog', {
 | 
			
		||||
			id: Math.random().toString().substr(2, 8),
 | 
			
		||||
			length: 200
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.connection.off('stats', this.onStats);
 | 
			
		||||
		this.connection.off('statsLog', this.onStatsLog);
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		onStats(stats) {
 | 
			
		||||
			this.stats.push(stats);
 | 
			
		||||
			if (this.stats.length > 200) this.stats.shift();
 | 
			
		||||
 | 
			
		||||
			const cpuPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - s.cpu_usage) * this.viewBoxY]);
 | 
			
		||||
			const memPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.mem.used / s.mem.total)) * this.viewBoxY]);
 | 
			
		||||
			this.cpuPolylinePoints = cpuPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
 | 
			
		||||
			this.memPolylinePoints = memPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
 | 
			
		||||
 | 
			
		||||
			this.cpuPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.cpuPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`;
 | 
			
		||||
			this.memPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.memPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`;
 | 
			
		||||
 | 
			
		||||
			this.cpuP = (stats.cpu_usage * 100).toFixed(0);
 | 
			
		||||
			this.memP = (stats.mem.used / stats.mem.total * 100).toFixed(0);
 | 
			
		||||
		},
 | 
			
		||||
		onStatsLog(statsLog) {
 | 
			
		||||
			statsLog.reverse().forEach(stats => this.onStats(stats));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.zyknedwtlthezamcjlolyusmipqmjgxz
 | 
			
		||||
	> svg
 | 
			
		||||
		display block
 | 
			
		||||
		width 50%
 | 
			
		||||
		float left
 | 
			
		||||
 | 
			
		||||
		&:first-child
 | 
			
		||||
			padding-right 5px
 | 
			
		||||
 | 
			
		||||
		&:last-child
 | 
			
		||||
			padding-left 5px
 | 
			
		||||
 | 
			
		||||
		> text
 | 
			
		||||
			font-size 10px
 | 
			
		||||
			fill var(--chartCaption)
 | 
			
		||||
 | 
			
		||||
			> tspan
 | 
			
		||||
				opacity 0.5
 | 
			
		||||
 | 
			
		||||
	&:after
 | 
			
		||||
		content ""
 | 
			
		||||
		display block
 | 
			
		||||
		clear both
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,135 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="obdskegsannmntldydackcpzezagxqfy mk-admin-card">
 | 
			
		||||
	<header>%i18n:@dashboard%</header>
 | 
			
		||||
 | 
			
		||||
	<div v-if="stats" class="stats">
 | 
			
		||||
		<div><b>%fa:user% {{ stats.originalUsersCount | number }}</b><span>%i18n:@original-users%</span></div>
 | 
			
		||||
		<div><span>%fa:user% {{ stats.usersCount | number }}</span><span>%i18n:@all-users%</span></div>
 | 
			
		||||
		<div><b>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</b><span>%i18n:@original-notes%</span></div>
 | 
			
		||||
		<div><span>%fa:pencil-alt% {{ stats.notesCount | number }}</span><span>%i18n:@all-notes%</span></div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class="cpu-memory">
 | 
			
		||||
		<x-cpu-memory :connection="connection"/>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div v-if="this.$store.state.i && this.$store.state.i.isAdmin" class="form">
 | 
			
		||||
		<div>
 | 
			
		||||
			<label>
 | 
			
		||||
				<p>%i18n:@banner-url%</p>
 | 
			
		||||
				<input v-model="bannerUrl">
 | 
			
		||||
			</label>
 | 
			
		||||
			<button class="ui" @click="updateMeta">%i18n:@save%</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<label>
 | 
			
		||||
				<input type="checkbox" v-model="disableRegistration" @change="updateMeta">
 | 
			
		||||
				<span>%i18n:@disableRegistration%</span>
 | 
			
		||||
			</label>
 | 
			
		||||
			<button class="ui" @click="invite">%i18n:@invite%</button>
 | 
			
		||||
			<p v-if="inviteCode">Code: <code>{{ inviteCode }}</code></p>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<label>
 | 
			
		||||
				<input type="checkbox" v-model="disableLocalTimeline" @change="updateMeta">
 | 
			
		||||
				<span>%i18n:@disableLocalTimeline%</span>
 | 
			
		||||
			</label>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
import XCpuMemory from "./admin.cpu-memory.vue";
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XCpuMemory
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			stats: null,
 | 
			
		||||
			disableRegistration: false,
 | 
			
		||||
			disableLocalTimeline: false,
 | 
			
		||||
			bannerUrl: null,
 | 
			
		||||
			inviteCode: null,
 | 
			
		||||
			connection: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	created() {
 | 
			
		||||
		this.connection = (this as any).os.stream.useSharedConnection('serverStats');
 | 
			
		||||
 | 
			
		||||
		(this as any).os.getMeta().then(meta => {
 | 
			
		||||
			this.disableRegistration = meta.disableRegistration;
 | 
			
		||||
			this.disableLocalTimeline = meta.disableLocalTimeline;
 | 
			
		||||
			this.bannerUrl = meta.bannerUrl;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		(this as any).api('stats').then(stats => {
 | 
			
		||||
			this.stats = stats;
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.connection.dispose();
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		invite() {
 | 
			
		||||
			(this as any).api('admin/invite').then(x => {
 | 
			
		||||
				this.inviteCode = x.code;
 | 
			
		||||
			}).catch(e => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Failed ${e}` });
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		updateMeta() {
 | 
			
		||||
			(this as any).api('admin/update-meta', {
 | 
			
		||||
				disableRegistration: this.disableRegistration,
 | 
			
		||||
				disableLocalTimeline: this.disableLocalTimeline,
 | 
			
		||||
				bannerUrl: this.bannerUrl
 | 
			
		||||
			}).then(() => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Saved` });
 | 
			
		||||
			}).catch(e => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Failed ${e}` });
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.obdskegsannmntldydackcpzezagxqfy
 | 
			
		||||
	> .stats
 | 
			
		||||
		display flex
 | 
			
		||||
		justify-content center
 | 
			
		||||
		margin-bottom 16px
 | 
			
		||||
		padding 16px
 | 
			
		||||
		border solid 1px #eee
 | 
			
		||||
		border-radius 8px
 | 
			
		||||
 | 
			
		||||
		> div
 | 
			
		||||
			flex 1
 | 
			
		||||
			text-align center
 | 
			
		||||
 | 
			
		||||
			> *:first-child
 | 
			
		||||
				display block
 | 
			
		||||
				color var(--primary)
 | 
			
		||||
 | 
			
		||||
			> *:last-child
 | 
			
		||||
				font-size 70%
 | 
			
		||||
 | 
			
		||||
	> .cpu-memory
 | 
			
		||||
		margin-bottom 16px
 | 
			
		||||
		padding 16px
 | 
			
		||||
		border solid 1px #eee
 | 
			
		||||
		border-radius: 8px
 | 
			
		||||
 | 
			
		||||
	> .form
 | 
			
		||||
		> div
 | 
			
		||||
			padding 16px
 | 
			
		||||
			border-bottom solid 1px #eee
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,57 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-admin-card">
 | 
			
		||||
	<header>%i18n:@suspend-user%</header>
 | 
			
		||||
	<input v-model="username" type="text" class="ui"/>
 | 
			
		||||
	<button class="ui" @click="suspendUser" :disabled="suspending">%i18n:@suspend%</button>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
import parseAcct from "../../../../../../misc/acct/parse";
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			username: null,
 | 
			
		||||
			suspending: false
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		async suspendUser() {
 | 
			
		||||
			this.suspending = true;
 | 
			
		||||
 | 
			
		||||
			const process = async () => {
 | 
			
		||||
				const user = await (this as any).os.api(
 | 
			
		||||
					"users/show",
 | 
			
		||||
					parseAcct(this.username)
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				await (this as any).os.api("admin/suspend-user", {
 | 
			
		||||
					userId: user.id
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				(this as any).os.apis.dialog({ text: "%i18n:@suspended%" });
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			await process().catch(e => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Failed: ${e}` });
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			this.suspending = false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
header
 | 
			
		||||
	margin 10px 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
button
 | 
			
		||||
	margin 16px 0
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,58 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-admin-card">
 | 
			
		||||
	<header>%i18n:@unsuspend-user%</header>
 | 
			
		||||
	<input v-model="username" type="text" class="ui"/>
 | 
			
		||||
	<button class="ui" @click="unsuspendUser" :disabled="unsuspending">%i18n:@unsuspend%</button>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
import parseAcct from "../../../../../../misc/acct/parse";
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			username: null,
 | 
			
		||||
			unsuspending: false
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		async unsuspendUser() {
 | 
			
		||||
			this.unsuspending = true;
 | 
			
		||||
 | 
			
		||||
			const process = async () => {
 | 
			
		||||
				const user = await (this as any).os.api(
 | 
			
		||||
					"users/show",
 | 
			
		||||
					parseAcct(this.username)
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				await (this as any).os.api("admin/unsuspend-user", {
 | 
			
		||||
					userId: user.id
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				(this as any).os.apis.dialog({ text: "%i18n:@unsuspended%" });
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			await process().catch(e => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Failed: ${e}` });
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			this.unsuspending = false;
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
header
 | 
			
		||||
	margin 10px 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
button
 | 
			
		||||
	margin 16px 0
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,57 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-admin-card">
 | 
			
		||||
	<header>%i18n:@unverify-user%</header>
 | 
			
		||||
	<input v-model="username" type="text" class="ui"/>
 | 
			
		||||
	<button class="ui" @click="unverifyUser" :disabled="unverifying">%i18n:@unverify%</button>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
import parseAcct from "../../../../../../misc/acct/parse";
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			username: null,
 | 
			
		||||
			unverifying: false
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		async unverifyUser() {
 | 
			
		||||
			this.unverifying = true;
 | 
			
		||||
 | 
			
		||||
			const process = async () => {
 | 
			
		||||
				const user = await (this as any).os.api(
 | 
			
		||||
					"users/show",
 | 
			
		||||
					parseAcct(this.username)
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				await (this as any).os.api("admin/unverify-user", {
 | 
			
		||||
					userId: user.id
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				(this as any).os.apis.dialog({ text: "%i18n:@unverified%" });
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			await process().catch(e => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Failed: ${e}` });
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			this.unverifying = false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
header
 | 
			
		||||
	margin 10px 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
button
 | 
			
		||||
	margin 16px 0
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,57 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-admin-card">
 | 
			
		||||
	<header>%i18n:@verify-user%</header>
 | 
			
		||||
	<input v-model="username" type="text" class="ui"/>
 | 
			
		||||
	<button class="ui" @click="verifyUser" :disabled="verifying">%i18n:@verify%</button>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
import parseAcct from "../../../../../../misc/acct/parse";
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			username: null,
 | 
			
		||||
			verifying: false
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		async verifyUser() {
 | 
			
		||||
			this.verifying = true;
 | 
			
		||||
 | 
			
		||||
			const process = async () => {
 | 
			
		||||
				const user = await (this as any).os.api(
 | 
			
		||||
					"users/show",
 | 
			
		||||
					parseAcct(this.username)
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				await (this as any).os.api("admin/verify-user", {
 | 
			
		||||
					userId: user.id
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				(this as any).os.apis.dialog({ text: "%i18n:@verified%" });
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			await process().catch(e => {
 | 
			
		||||
				(this as any).os.apis.dialog({ text: `Failed: ${e}` });
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			this.verifying = false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
header
 | 
			
		||||
	margin 10px 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
button
 | 
			
		||||
	margin 16px 0
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,140 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-admin">
 | 
			
		||||
	<nav>
 | 
			
		||||
		<ul>
 | 
			
		||||
			<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }">%fa:chalkboard .fw%%i18n:@dashboard%</li>
 | 
			
		||||
 | 
			
		||||
			<li v-if="this.$store.state.i && this.$store.state.i.isAdmin"
 | 
			
		||||
				@click="nav('users')" :class="{ active: page == 'users' }">%fa:users .fw%%i18n:@users%</li>
 | 
			
		||||
 | 
			
		||||
			<li v-if="this.$store.state.i && this.$store.state.i.isAdmin"
 | 
			
		||||
				@click="nav('announcements')" :class="{ active: page == 'announcements' }">%fa:broadcast-tower .fw%%i18n:@announcements%</li>
 | 
			
		||||
 | 
			
		||||
			<li v-if="this.$store.state.i && this.$store.state.i.isAdmin"
 | 
			
		||||
				@click="nav('hashtags')" :class="{ active: page == 'hashtags' }">%fa:hashtag .fw%%i18n:@hashtags%</li>
 | 
			
		||||
 | 
			
		||||
			<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }">%fa:cloud .fw%%i18n:common.drive%</li> -->
 | 
			
		||||
			<!-- <li @click="nav('update')" :class="{ active: page == 'update' }">%i18n:@update%</li> -->
 | 
			
		||||
		</ul>
 | 
			
		||||
	</nav>
 | 
			
		||||
	<main>
 | 
			
		||||
		<div v-show="page == 'dashboard'">
 | 
			
		||||
			<x-dashboard/>
 | 
			
		||||
			<x-charts/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div v-show="page == 'announcements'">
 | 
			
		||||
			<x-announcements/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div v-show="page == 'hashtags'">
 | 
			
		||||
			<x-hashtags/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div v-if="page == 'users'">
 | 
			
		||||
			<x-suspend-user/>
 | 
			
		||||
			<x-unsuspend-user/>
 | 
			
		||||
			<x-verify-user/>
 | 
			
		||||
			<x-unverify-user/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div v-if="page == 'drive'"></div>
 | 
			
		||||
		<div v-if="page == 'update'"></div>
 | 
			
		||||
	</main>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
import XDashboard from "./admin.dashboard.vue";
 | 
			
		||||
import XAnnouncements from "./admin.announcements.vue";
 | 
			
		||||
import XHashtags from "./admin.hashtags.vue";
 | 
			
		||||
import XSuspendUser from "./admin.suspend-user.vue";
 | 
			
		||||
import XUnsuspendUser from "./admin.unsuspend-user.vue";
 | 
			
		||||
import XVerifyUser from "./admin.verify-user.vue";
 | 
			
		||||
import XUnverifyUser from "./admin.unverify-user.vue";
 | 
			
		||||
import XCharts from "../../components/charts.vue";
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XDashboard,
 | 
			
		||||
		XAnnouncements,
 | 
			
		||||
		XHashtags,
 | 
			
		||||
		XSuspendUser,
 | 
			
		||||
		XUnsuspendUser,
 | 
			
		||||
		XVerifyUser,
 | 
			
		||||
		XUnverifyUser,
 | 
			
		||||
		XCharts
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			page: 'dashboard'
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		nav(page: string) {
 | 
			
		||||
			this.page = page;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.mk-admin
 | 
			
		||||
	display flex
 | 
			
		||||
	height 100%
 | 
			
		||||
	margin 32px
 | 
			
		||||
 | 
			
		||||
	> nav
 | 
			
		||||
		flex 0 0 250px
 | 
			
		||||
		width 100%
 | 
			
		||||
		height 100%
 | 
			
		||||
		padding 16px 0 0 0
 | 
			
		||||
		overflow auto
 | 
			
		||||
		border-right solid 1px #ddd
 | 
			
		||||
 | 
			
		||||
		> ul
 | 
			
		||||
			list-style none
 | 
			
		||||
 | 
			
		||||
			> li
 | 
			
		||||
				display block
 | 
			
		||||
				padding 10px 16px
 | 
			
		||||
				margin 0
 | 
			
		||||
				color #666
 | 
			
		||||
				cursor pointer
 | 
			
		||||
				user-select none
 | 
			
		||||
				transition margin-left 0.2s ease
 | 
			
		||||
 | 
			
		||||
				> [data-fa]
 | 
			
		||||
					margin-right 4px
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
				&:hover
 | 
			
		||||
					color #555
 | 
			
		||||
 | 
			
		||||
				&.active
 | 
			
		||||
					margin-left 8px
 | 
			
		||||
					color var(--primary) !important
 | 
			
		||||
 | 
			
		||||
	> main
 | 
			
		||||
		width 100%
 | 
			
		||||
		padding 16px 32px
 | 
			
		||||
 | 
			
		||||
		> div
 | 
			
		||||
			> div
 | 
			
		||||
				max-width 800px
 | 
			
		||||
 | 
			
		||||
.mk-admin-card
 | 
			
		||||
	padding 32px
 | 
			
		||||
	background #fff
 | 
			
		||||
	box-shadow 0 2px 8px rgba(#000, 0.1)
 | 
			
		||||
 | 
			
		||||
	&:not(:last-child)
 | 
			
		||||
		margin-bottom 16px
 | 
			
		||||
 | 
			
		||||
	> header
 | 
			
		||||
		margin 0 0 1em 0
 | 
			
		||||
		padding 0 0 8px 0
 | 
			
		||||
		font-size 1em
 | 
			
		||||
		color #555
 | 
			
		||||
		border-bottom solid 1px #eee
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -14,7 +14,7 @@ import VueHotkey from './common/hotkey';
 | 
			
		||||
import App from './app.vue';
 | 
			
		||||
import checkForUpdate from './common/scripts/check-for-update';
 | 
			
		||||
import MiOS, { API } from './mios';
 | 
			
		||||
import { version, codename, lang } from './config';
 | 
			
		||||
import { clientVersion as version, codename, lang } from './config';
 | 
			
		||||
import { builtinThemes, lightTheme, applyTheme } from './theme';
 | 
			
		||||
 | 
			
		||||
if (localStorage.getItem('theme') == null) {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ import { EventEmitter } from 'eventemitter3';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
 | 
			
		||||
import initStore from './store';
 | 
			
		||||
import { apiUrl, version, lang } from './config';
 | 
			
		||||
import { apiUrl, clientVersion as version, lang } from './config';
 | 
			
		||||
import Progress from './common/scripts/loading';
 | 
			
		||||
 | 
			
		||||
import Err from './common/views/components/connect-failed.vue';
 | 
			
		||||
@@ -537,7 +537,9 @@ export default class MiOS extends EventEmitter {
 | 
			
		||||
			// forceが有効, meta情報を保持していない or 期限切れ
 | 
			
		||||
			if (force || this.meta == null || Date.now() - this.meta.chachedAt.getTime() > expire) {
 | 
			
		||||
				this.isMetaFetching = true;
 | 
			
		||||
				const meta = await this.api('meta');
 | 
			
		||||
				const meta = await this.api('meta', {
 | 
			
		||||
					detail: false
 | 
			
		||||
				});
 | 
			
		||||
				this.meta = {
 | 
			
		||||
					data: meta,
 | 
			
		||||
					chachedAt: new Date()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,23 +1,13 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-activity">
 | 
			
		||||
	<svg v-if="data" ref="canvas" viewBox="0 0 30 1" preserveAspectRatio="none">
 | 
			
		||||
		<g v-for="(d, i) in data">
 | 
			
		||||
			<rect width="0.8" :height="d.notesH"
 | 
			
		||||
				:x="i + 0.1" :y="1 - d.notesH - d.repliesH - d.renotesH"
 | 
			
		||||
				fill="#41ddde"/>
 | 
			
		||||
			<rect width="0.8" :height="d.repliesH"
 | 
			
		||||
				:x="i + 0.1" :y="1 - d.repliesH - d.renotesH"
 | 
			
		||||
				fill="#f7796c"/>
 | 
			
		||||
			<rect width="0.8" :height="d.renotesH"
 | 
			
		||||
				:x="i + 0.1" :y="1 - d.renotesH"
 | 
			
		||||
				fill="#a1de41"/>
 | 
			
		||||
			</g>
 | 
			
		||||
	</svg>
 | 
			
		||||
	<div ref="chart"></div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import * as ApexCharts from 'apexcharts';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: ['user'],
 | 
			
		||||
	data() {
 | 
			
		||||
@@ -28,19 +18,84 @@ export default Vue.extend({
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		(this as any).api('aggregation/users/activity', {
 | 
			
		||||
		(this as any).api('charts/user/notes', {
 | 
			
		||||
			userId: this.user.id,
 | 
			
		||||
			limit: 30
 | 
			
		||||
		}).then(data => {
 | 
			
		||||
			data.forEach(d => d.total = d.notes + d.replies + d.renotes);
 | 
			
		||||
			this.peak = Math.max.apply(null, data.map(d => d.total));
 | 
			
		||||
			data.forEach(d => {
 | 
			
		||||
				d.notesH = d.notes / this.peak;
 | 
			
		||||
				d.repliesH = d.replies / this.peak;
 | 
			
		||||
				d.renotesH = d.renotes / this.peak;
 | 
			
		||||
			span: 'day',
 | 
			
		||||
			limit: 21
 | 
			
		||||
		}).then(stats => {
 | 
			
		||||
			const normal = [];
 | 
			
		||||
			const reply = [];
 | 
			
		||||
			const renote = [];
 | 
			
		||||
 | 
			
		||||
			const now = new Date();
 | 
			
		||||
			const y = now.getFullYear();
 | 
			
		||||
			const m = now.getMonth();
 | 
			
		||||
			const d = now.getDate();
 | 
			
		||||
 | 
			
		||||
			for (let i = 0; i < 21; i++) {
 | 
			
		||||
				const x = new Date(y, m, d - i);
 | 
			
		||||
				normal.push([
 | 
			
		||||
					x,
 | 
			
		||||
					stats.diffs.normal[i]
 | 
			
		||||
				]);
 | 
			
		||||
				reply.push([
 | 
			
		||||
					x,
 | 
			
		||||
					stats.diffs.reply[i]
 | 
			
		||||
				]);
 | 
			
		||||
				renote.push([
 | 
			
		||||
					x,
 | 
			
		||||
					stats.diffs.renote[i]
 | 
			
		||||
				]);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			const chart = new ApexCharts(this.$refs.chart, {
 | 
			
		||||
				chart: {
 | 
			
		||||
					type: 'bar',
 | 
			
		||||
					stacked: true,
 | 
			
		||||
					height: 100,
 | 
			
		||||
					sparkline: {
 | 
			
		||||
						enabled: true
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				plotOptions: {
 | 
			
		||||
					bar: {
 | 
			
		||||
						columnWidth: '90%',
 | 
			
		||||
						endingShape: 'rounded'
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				grid: {
 | 
			
		||||
					clipMarkers: false,
 | 
			
		||||
					padding: {
 | 
			
		||||
						top: 0,
 | 
			
		||||
						right: 8,
 | 
			
		||||
						bottom: 0,
 | 
			
		||||
						left: 8
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				tooltip: {
 | 
			
		||||
					shared: true,
 | 
			
		||||
					intersect: false
 | 
			
		||||
				},
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Normal',
 | 
			
		||||
					data: normal
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Reply',
 | 
			
		||||
					data: reply
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Renote',
 | 
			
		||||
					data: renote
 | 
			
		||||
				}],
 | 
			
		||||
				xaxis: {
 | 
			
		||||
					type: 'datetime',
 | 
			
		||||
					crosshairs: {
 | 
			
		||||
						width: 1,
 | 
			
		||||
						opacity: 1
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
			data.reverse();
 | 
			
		||||
			this.data = data;
 | 
			
		||||
 | 
			
		||||
			chart.render();
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
@@ -51,12 +106,4 @@ export default Vue.extend({
 | 
			
		||||
	max-width 600px
 | 
			
		||||
	margin 0 auto
 | 
			
		||||
 | 
			
		||||
	> svg
 | 
			
		||||
		display block
 | 
			
		||||
		width 100%
 | 
			
		||||
		height 80px
 | 
			
		||||
 | 
			
		||||
		> rect
 | 
			
		||||
			transform-origin center
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@
 | 
			
		||||
				<div class="text">
 | 
			
		||||
					<span v-if="p.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
 | 
			
		||||
					<span v-if="p.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span>
 | 
			
		||||
					<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
 | 
			
		||||
					<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i" :customEmojis="p.emojis"/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="files" v-if="p.files.length > 0">
 | 
			
		||||
					<mk-media-list :media-list="p.files" :raw="true"/>
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@
 | 
			
		||||
					<div class="text">
 | 
			
		||||
						<span v-if="appearNote.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
 | 
			
		||||
						<a class="reply" v-if="appearNote.reply">%fa:reply%</a>
 | 
			
		||||
						<misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :class="$style.text"/>
 | 
			
		||||
						<misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :class="$style.text" :customEmojis="appearNote.emojis"/>
 | 
			
		||||
						<a class="rp" v-if="appearNote.renote != null">RN:</a>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="files" v-if="appearNote.files.length > 0">
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
		<span v-if="note.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
 | 
			
		||||
		<span v-if="note.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span>
 | 
			
		||||
		<a class="reply" v-if="note.replyId">%fa:reply%</a>
 | 
			
		||||
		<misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i"/>
 | 
			
		||||
		<misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i" :customEmojis="note.emojis"/>
 | 
			
		||||
		<a class="rp" v-if="note.renoteId">RN: ...</a>
 | 
			
		||||
	</div>
 | 
			
		||||
	<details v-if="note.files.length > 0">
 | 
			
		||||
 
 | 
			
		||||
@@ -127,6 +127,13 @@
 | 
			
		||||
 | 
			
		||||
			<mk-api-settings />
 | 
			
		||||
 | 
			
		||||
			<ui-card>
 | 
			
		||||
				<div slot="title">%fa:unlock-alt% %i18n:@password%</div>
 | 
			
		||||
				<section>
 | 
			
		||||
					<mk-password-settings/>
 | 
			
		||||
				</section>
 | 
			
		||||
			</ui-card>
 | 
			
		||||
 | 
			
		||||
			<ui-card>
 | 
			
		||||
				<div slot="title">%fa:sync-alt% %i18n:@update%</div>
 | 
			
		||||
 | 
			
		||||
@@ -154,7 +161,7 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import { apiUrl, version, codename, langs } from '../../../config';
 | 
			
		||||
import { apiUrl, clientVersion as version, codename, langs } from '../../../config';
 | 
			
		||||
import checkForUpdate from '../../../common/scripts/check-for-update';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,9 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export type TextElementBig = {
 | 
			
		||||
	type: 'big'
 | 
			
		||||
	content: string
 | 
			
		||||
	big: string
 | 
			
		||||
	type: 'big';
 | 
			
		||||
	content: string;
 | 
			
		||||
	big: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function(text: string) {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,9 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export type TextElementBold = {
 | 
			
		||||
	type: 'bold'
 | 
			
		||||
	content: string
 | 
			
		||||
	bold: string
 | 
			
		||||
	type: 'bold';
 | 
			
		||||
	content: string;
 | 
			
		||||
	bold: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function(text: string) {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,10 +5,10 @@
 | 
			
		||||
import genHtml from '../core/syntax-highlighter';
 | 
			
		||||
 | 
			
		||||
export type TextElementCode = {
 | 
			
		||||
	type: 'code'
 | 
			
		||||
	content: string
 | 
			
		||||
	code: string
 | 
			
		||||
	html: string
 | 
			
		||||
	type: 'code';
 | 
			
		||||
	content: string;
 | 
			
		||||
	code: string;
 | 
			
		||||
	html: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function(text: string) {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,13 +3,13 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export type TextElementEmoji = {
 | 
			
		||||
	type: 'emoji'
 | 
			
		||||
	content: string
 | 
			
		||||
	emoji: string
 | 
			
		||||
	type: 'emoji';
 | 
			
		||||
	content: string;
 | 
			
		||||
	emoji: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function(text: string) {
 | 
			
		||||
	const match = text.match(/^:([a-zA-Z0-9+-_]+):/);
 | 
			
		||||
	const match = text.match(/^:([a-zA-Z0-9+-_]+?):/);
 | 
			
		||||
	if (!match) return null;
 | 
			
		||||
	const emoji = match[0];
 | 
			
		||||
	return {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,13 +3,13 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export type TextElementHashtag = {
 | 
			
		||||
	type: 'hashtag'
 | 
			
		||||
	content: string
 | 
			
		||||
	hashtag: string
 | 
			
		||||
	type: 'hashtag';
 | 
			
		||||
	content: string;
 | 
			
		||||
	hashtag: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function(text: string, i: number) {
 | 
			
		||||
	if (!(/^\s#[^\s\.,!\?#]+/.test(text) || (i == 0 && /^#[^\s\.,!\?#]+/.test(text)))) return null;
 | 
			
		||||
export default function(text: string, isBegin: boolean) {
 | 
			
		||||
	if (!(/^\s#[^\s\.,!\?#]+/.test(text) || (isBegin && /^#[^\s\.,!\?#]+/.test(text)))) return null;
 | 
			
		||||
	const isHead = text.startsWith('#');
 | 
			
		||||
	const hashtag = text.match(/^\s?#[^\s\.,!\?#]+/)[0];
 | 
			
		||||
	const res: any[] = !isHead ? [{
 | 
			
		||||
 
 | 
			
		||||
@@ -5,10 +5,10 @@
 | 
			
		||||
import genHtml from '../core/syntax-highlighter';
 | 
			
		||||
 | 
			
		||||
export type TextElementInlineCode = {
 | 
			
		||||
	type: 'inline-code'
 | 
			
		||||
	content: string
 | 
			
		||||
	code: string
 | 
			
		||||
	html: string
 | 
			
		||||
	type: 'inline-code';
 | 
			
		||||
	content: string;
 | 
			
		||||
	code: string;
 | 
			
		||||
	html: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function(text: string) {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,11 +3,11 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export type TextElementLink = {
 | 
			
		||||
	type: 'link'
 | 
			
		||||
	content: string
 | 
			
		||||
	title: string
 | 
			
		||||
	url: string
 | 
			
		||||
	silent: boolean
 | 
			
		||||
	type: 'link';
 | 
			
		||||
	content: string;
 | 
			
		||||
	title: string;
 | 
			
		||||
	url: string;
 | 
			
		||||
	silent: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function(text: string) {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,11 +5,11 @@ import parseAcct from '../../../misc/acct/parse';
 | 
			
		||||
import { toUnicode } from 'punycode';
 | 
			
		||||
 | 
			
		||||
export type TextElementMention = {
 | 
			
		||||
	type: 'mention'
 | 
			
		||||
	content: string
 | 
			
		||||
	canonical: string
 | 
			
		||||
	username: string
 | 
			
		||||
	host: string
 | 
			
		||||
	type: 'mention';
 | 
			
		||||
	content: string;
 | 
			
		||||
	canonical: string;
 | 
			
		||||
	username: string;
 | 
			
		||||
	host: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function(text: string) {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,9 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export type TextElementMotion = {
 | 
			
		||||
	type: 'motion'
 | 
			
		||||
	content: string
 | 
			
		||||
	motion: string
 | 
			
		||||
	type: 'motion';
 | 
			
		||||
	content: string;
 | 
			
		||||
	motion: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function(text: string) {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,14 +3,14 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export type TextElementQuote = {
 | 
			
		||||
	type: 'quote'
 | 
			
		||||
	content: string
 | 
			
		||||
	quote: string
 | 
			
		||||
	type: 'quote';
 | 
			
		||||
	content: string;
 | 
			
		||||
	quote: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function(text: string, index: number) {
 | 
			
		||||
export default function(text: string, isBegin: boolean) {
 | 
			
		||||
	const match = text.match(/^"([\s\S]+?)\n"/) || text.match(/^\n>([\s\S]+?)(\n\n|$)/) ||
 | 
			
		||||
		(index == 0 ? text.match(/^>([\s\S]+?)(\n\n|$)/) : null);
 | 
			
		||||
		(isBegin ? text.match(/^>([\s\S]+?)(\n\n|$)/) : null);
 | 
			
		||||
 | 
			
		||||
	if (!match) return null;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,9 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export type TextElementSearch = {
 | 
			
		||||
	type: 'search'
 | 
			
		||||
	content: string
 | 
			
		||||
	query: string
 | 
			
		||||
	type: 'search';
 | 
			
		||||
	content: string;
 | 
			
		||||
	query: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function(text: string) {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,13 +3,13 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export type TextElementTitle = {
 | 
			
		||||
	type: 'title'
 | 
			
		||||
	content: string
 | 
			
		||||
	title: string
 | 
			
		||||
	type: 'title';
 | 
			
		||||
	content: string;
 | 
			
		||||
	title: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function(text: string) {
 | 
			
		||||
	const match = text.match(/^(【|\[)(.+?)(】|])\n/);
 | 
			
		||||
export default function(text: string, isBegin: boolean) {
 | 
			
		||||
	const match = isBegin ? text.match(/^(【|\[)(.+?)(】|])\n/) : text.match(/^\n(【|\[)(.+?)(】|])\n/);
 | 
			
		||||
	if (!match) return null;
 | 
			
		||||
	const title = match[0];
 | 
			
		||||
	return {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,9 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export type TextElementUrl = {
 | 
			
		||||
	type: 'url'
 | 
			
		||||
	content: string
 | 
			
		||||
	url: string
 | 
			
		||||
	type: 'url';
 | 
			
		||||
	content: string;
 | 
			
		||||
	url: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function(text: string) {
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,7 @@ export type TextElement = { type: 'text', content: string }
 | 
			
		||||
	| TextElementTitle
 | 
			
		||||
	| TextElementUrl
 | 
			
		||||
	| TextElementMotion;
 | 
			
		||||
export type TextElementProcessor = (text: string, i: number) => TextElement | TextElement[];
 | 
			
		||||
export type TextElementProcessor = (text: string, isBegin: boolean) => TextElement | TextElement[];
 | 
			
		||||
 | 
			
		||||
export default (source: string): TextElement[] => {
 | 
			
		||||
	if (source == null || source == '') {
 | 
			
		||||
@@ -67,7 +67,7 @@ export default (source: string): TextElement[] => {
 | 
			
		||||
	// パース
 | 
			
		||||
	while (source != '') {
 | 
			
		||||
		const parsed = elements.some(el => {
 | 
			
		||||
			let _tokens = el(source, i);
 | 
			
		||||
			let _tokens = el(source, i == 0);
 | 
			
		||||
			if (_tokens) {
 | 
			
		||||
				if (!Array.isArray(_tokens)) {
 | 
			
		||||
					_tokens = [_tokens];
 | 
			
		||||
 
 | 
			
		||||
@@ -4,23 +4,31 @@ import isObjectId from './is-objectid';
 | 
			
		||||
 | 
			
		||||
export const isAnId = (x: any) => mongo.ObjectID.isValid(x);
 | 
			
		||||
export const isNotAnId = (x: any) => !isAnId(x);
 | 
			
		||||
export const transform = (x: string | mongo.ObjectID): mongo.ObjectID => {
 | 
			
		||||
	if (x == null) return null;
 | 
			
		||||
 | 
			
		||||
	if (isAnId(x) && !isObjectId(x)) {
 | 
			
		||||
		return new mongo.ObjectID(x);
 | 
			
		||||
	} else {
 | 
			
		||||
		return x as mongo.ObjectID;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
export const transformMany = (xs: (string | mongo.ObjectID)[]): mongo.ObjectID[] => {
 | 
			
		||||
	if (xs == null) return null;
 | 
			
		||||
 | 
			
		||||
	return xs.map(x => transform(x));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type ObjectId = mongo.ObjectID;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ID
 | 
			
		||||
 */
 | 
			
		||||
export default class ID extends Context<mongo.ObjectID> {
 | 
			
		||||
export default class ID extends Context<string> {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super();
 | 
			
		||||
 | 
			
		||||
		this.transform = v => {
 | 
			
		||||
			if (isAnId(v) && !isObjectId(v)) {
 | 
			
		||||
				return new mongo.ObjectID(v);
 | 
			
		||||
			} else {
 | 
			
		||||
				return v;
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		this.push(v => {
 | 
			
		||||
		this.push((v: any) => {
 | 
			
		||||
			if (!isObjectId(v) && isNotAnId(v)) {
 | 
			
		||||
				return new Error('must-be-an-id');
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								src/models/emoji.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/models/emoji.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
import * as mongo from 'mongodb';
 | 
			
		||||
import db from '../db/mongodb';
 | 
			
		||||
 | 
			
		||||
const Emoji = db.get<IEmoji>('emoji');
 | 
			
		||||
Emoji.createIndex('name');
 | 
			
		||||
Emoji.createIndex('host');
 | 
			
		||||
Emoji.createIndex(['name', 'host'], { unique: true });
 | 
			
		||||
 | 
			
		||||
export default Emoji;
 | 
			
		||||
 | 
			
		||||
export type IEmoji = {
 | 
			
		||||
	_id: mongo.ObjectID;
 | 
			
		||||
	name: string;
 | 
			
		||||
	host: string;
 | 
			
		||||
	url: string;
 | 
			
		||||
	aliases?: string[];
 | 
			
		||||
	updatedAt?: Date;
 | 
			
		||||
};
 | 
			
		||||
@@ -15,24 +15,4 @@ export type IMeta = {
 | 
			
		||||
	disableLocalTimeline?: boolean;
 | 
			
		||||
	hidedTags?: string[];
 | 
			
		||||
	bannerUrl?: string;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * カスタム絵文字定義
 | 
			
		||||
	 */
 | 
			
		||||
	emojis?: {
 | 
			
		||||
		/**
 | 
			
		||||
		 * 絵文字名 (例: thinking_ai)
 | 
			
		||||
		 */
 | 
			
		||||
		name: string;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * エイリアス
 | 
			
		||||
		 */
 | 
			
		||||
		aliases?: string[];
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 絵文字画像のURL
 | 
			
		||||
		 */
 | 
			
		||||
		url: string;
 | 
			
		||||
	}[];
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ import { packMany as packFileMany, IDriveFile } from './drive-file';
 | 
			
		||||
import Favorite from './favorite';
 | 
			
		||||
import Following from './following';
 | 
			
		||||
import config from '../config';
 | 
			
		||||
import Emoji from './emoji';
 | 
			
		||||
 | 
			
		||||
const Note = db.get<INote>('notes');
 | 
			
		||||
Note.createIndex('uri', { sparse: true, unique: true });
 | 
			
		||||
@@ -49,6 +50,7 @@ export type INote = {
 | 
			
		||||
	text: string;
 | 
			
		||||
	tags: string[];
 | 
			
		||||
	tagsLower: string[];
 | 
			
		||||
	emojis: string[];
 | 
			
		||||
	cw: string;
 | 
			
		||||
	userId: mongo.ObjectID;
 | 
			
		||||
	appId: mongo.ObjectID;
 | 
			
		||||
@@ -228,6 +230,26 @@ export const pack = async (
 | 
			
		||||
 | 
			
		||||
	const id = _note._id;
 | 
			
		||||
 | 
			
		||||
	// _note._userを消す前か、_note.userを解決した後でないとホストがわからない
 | 
			
		||||
	if (_note._user) {
 | 
			
		||||
		const host = _note._user.host;
 | 
			
		||||
		// 互換性のため。(古いMisskeyではNoteにemojisが無い)
 | 
			
		||||
		if (_note.emojis == null) {
 | 
			
		||||
			_note.emojis = Emoji.find({
 | 
			
		||||
				host: host
 | 
			
		||||
			}, {
 | 
			
		||||
				fields: { _id: false }
 | 
			
		||||
			});
 | 
			
		||||
		} else {
 | 
			
		||||
			_note.emojis = Emoji.find({
 | 
			
		||||
				name: { $in: _note.emojis },
 | 
			
		||||
				host: host
 | 
			
		||||
			}, {
 | 
			
		||||
				fields: { _id: false }
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Rename _id to id
 | 
			
		||||
	_note.id = _note._id;
 | 
			
		||||
	delete _note._id;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,8 @@ import * as mongo from 'mongodb';
 | 
			
		||||
import db from '../db/mongodb';
 | 
			
		||||
 | 
			
		||||
const PollVote = db.get<IPollVote>('pollVotes');
 | 
			
		||||
PollVote.createIndex('userId');
 | 
			
		||||
PollVote.createIndex('noteId');
 | 
			
		||||
export default PollVote;
 | 
			
		||||
 | 
			
		||||
export interface IPollVote {
 | 
			
		||||
 
 | 
			
		||||
@@ -189,7 +189,7 @@ export async function getRelation(me: mongo.ObjectId, target: mongo.ObjectId) {
 | 
			
		||||
 | 
			
		||||
	return {
 | 
			
		||||
		isFollowing: following1 !== null,
 | 
			
		||||
		isStalking: following1 && following1.stalk,
 | 
			
		||||
		isStalking: following1 ? following1.stalk : false,
 | 
			
		||||
		hasPendingFollowRequestFromYou: followReq1 !== null,
 | 
			
		||||
		hasPendingFollowRequestToYou: followReq2 !== null,
 | 
			
		||||
		isFollowed: following2 !== null,
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ import perform from '../../../remote/activitypub/perform';
 | 
			
		||||
import { resolvePerson, updatePerson } from '../../../remote/activitypub/models/person';
 | 
			
		||||
import { toUnicode } from 'punycode';
 | 
			
		||||
import { URL } from 'url';
 | 
			
		||||
import { publishApLogStream } from '../../../stream';
 | 
			
		||||
 | 
			
		||||
const log = debug('misskey:queue:inbox');
 | 
			
		||||
 | 
			
		||||
@@ -61,6 +62,15 @@ export default async (job: bq.Job, done: any): Promise<void> => {
 | 
			
		||||
		}) as IRemoteUser;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//#region Log
 | 
			
		||||
	publishApLogStream({
 | 
			
		||||
		direction: 'in',
 | 
			
		||||
		activity: activity.type,
 | 
			
		||||
		host: user.host,
 | 
			
		||||
		actor: user.username
 | 
			
		||||
	});
 | 
			
		||||
	//#endregion
 | 
			
		||||
 | 
			
		||||
	// Update activityの場合は、ここで署名検証/更新処理まで実施して終了
 | 
			
		||||
	if (activity.type === 'Update') {
 | 
			
		||||
		if (activity.object && activity.object.type === 'Person') {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								src/remote/activitypub/misc/get-emoji-names.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/remote/activitypub/misc/get-emoji-names.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
import parse from '../../../mfm/parse';
 | 
			
		||||
 | 
			
		||||
export default function(text: string) {
 | 
			
		||||
	if (!text) return [];
 | 
			
		||||
	return parse(text).filter(t => t.type === 'emoji').map(t => (t as any).emoji);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								src/remote/activitypub/models/icon.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/remote/activitypub/models/icon.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
export type IIcon = {
 | 
			
		||||
	type: string;
 | 
			
		||||
	mediaType?: string;
 | 
			
		||||
	url?: string;
 | 
			
		||||
};
 | 
			
		||||
@@ -10,6 +10,9 @@ import { resolvePerson, updatePerson } from './person';
 | 
			
		||||
import { resolveImage } from './image';
 | 
			
		||||
import { IRemoteUser, IUser } from '../../../models/user';
 | 
			
		||||
import htmlToMFM from '../../../mfm/html-to-mfm';
 | 
			
		||||
import Emoji from '../../../models/emoji';
 | 
			
		||||
import { ITag } from './tag';
 | 
			
		||||
import { toUnicode } from 'punycode';
 | 
			
		||||
 | 
			
		||||
const log = debug('misskey:activitypub');
 | 
			
		||||
 | 
			
		||||
@@ -93,6 +96,10 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
 | 
			
		||||
	// テキストのパース
 | 
			
		||||
	const text = note._misskey_content ? note._misskey_content : htmlToMFM(note.content);
 | 
			
		||||
 | 
			
		||||
	await extractEmojis(note.tag, actor.host).catch(e => {
 | 
			
		||||
		console.log(`extractEmojis: ${e}`);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// ユーザーの情報が古かったらついでに更新しておく
 | 
			
		||||
	if (actor.updatedAt == null || Date.now() - actor.updatedAt.getTime() > 1000 * 60 * 60 * 24) {
 | 
			
		||||
		updatePerson(note.attributedTo);
 | 
			
		||||
@@ -135,3 +142,35 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver):
 | 
			
		||||
	// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
 | 
			
		||||
	return await createNote(uri, resolver);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function extractEmojis(tags: ITag[], host_: string) {
 | 
			
		||||
	const host = toUnicode(host_.toLowerCase());
 | 
			
		||||
 | 
			
		||||
	if (!tags) return [];
 | 
			
		||||
 | 
			
		||||
	const eomjiTags = tags.filter(tag => tag.type === 'Emoji' && tag.icon && tag.icon.url);
 | 
			
		||||
 | 
			
		||||
	return await Promise.all(
 | 
			
		||||
		eomjiTags.map(async tag => {
 | 
			
		||||
			const name = tag.name.replace(/^:/, '').replace(/:$/, '');
 | 
			
		||||
 | 
			
		||||
			const exists = await Emoji.findOne({
 | 
			
		||||
				host,
 | 
			
		||||
				name
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			if (exists) {
 | 
			
		||||
				return exists;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			log(`register emoji host=${host}, name=${name}`);
 | 
			
		||||
 | 
			
		||||
			return await Emoji.insert({
 | 
			
		||||
				host,
 | 
			
		||||
				name,
 | 
			
		||||
				url: tag.icon.url,
 | 
			
		||||
				aliases: [],
 | 
			
		||||
			});
 | 
			
		||||
		})
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								src/remote/activitypub/models/tag.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/remote/activitypub/models/tag.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
import { IIcon } from "./icon";
 | 
			
		||||
 | 
			
		||||
/***
 | 
			
		||||
 * tag (ActivityPub)
 | 
			
		||||
 */
 | 
			
		||||
export type ITag = {
 | 
			
		||||
	id: string;
 | 
			
		||||
	type: string;
 | 
			
		||||
	name?: string;
 | 
			
		||||
	updated?: Date;
 | 
			
		||||
	icon?: IIcon;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										14
									
								
								src/remote/activitypub/renderer/emoji.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/remote/activitypub/renderer/emoji.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
import { IEmoji } from '../../../models/emoji';
 | 
			
		||||
import config from '../../../config';
 | 
			
		||||
 | 
			
		||||
export default (emoji: IEmoji) => ({
 | 
			
		||||
	id: `${config.url}/emojis/${emoji.name}`,
 | 
			
		||||
	type: 'Emoji',
 | 
			
		||||
	name: `:${emoji.name}:`,
 | 
			
		||||
	updated: emoji.updatedAt != null ? emoji.updatedAt.toISOString() : new Date().toISOString,
 | 
			
		||||
	icon: {
 | 
			
		||||
		type: 'Image',
 | 
			
		||||
		mediaType: 'image/png',	//Mei-TODO
 | 
			
		||||
		url: emoji.url
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
@@ -1,12 +1,16 @@
 | 
			
		||||
import renderDocument from './document';
 | 
			
		||||
import renderHashtag from './hashtag';
 | 
			
		||||
import renderMention from './mention';
 | 
			
		||||
import renderEmoji from './emoji';
 | 
			
		||||
import config from '../../../config';
 | 
			
		||||
import DriveFile, { IDriveFile } from '../../../models/drive-file';
 | 
			
		||||
import Note, { INote } from '../../../models/note';
 | 
			
		||||
import User from '../../../models/user';
 | 
			
		||||
import toHtml from '../misc/get-note-html';
 | 
			
		||||
import parseMfm from '../../../mfm/parse';
 | 
			
		||||
import getEmojiNames from '../misc/get-emoji-names';
 | 
			
		||||
import Emoji, { IEmoji } from '../../../models/emoji';
 | 
			
		||||
import { unique } from '../../../prelude/array';
 | 
			
		||||
 | 
			
		||||
export default async function renderNote(note: INote, dive = true): Promise<any> {
 | 
			
		||||
	const promisedFiles: Promise<IDriveFile[]> = note.fileIds
 | 
			
		||||
@@ -75,10 +79,6 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
 | 
			
		||||
 | 
			
		||||
	const hashtagTags = (note.tags || []).map(tag => renderHashtag(tag));
 | 
			
		||||
	const mentionTags = mentionedUsers.map(u => renderMention(u));
 | 
			
		||||
	const tag = [
 | 
			
		||||
		...hashtagTags,
 | 
			
		||||
		...mentionTags,
 | 
			
		||||
	];
 | 
			
		||||
 | 
			
		||||
	const files = await promisedFiles;
 | 
			
		||||
 | 
			
		||||
@@ -108,12 +108,24 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
 | 
			
		||||
		}).join('');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const content = toHtml(Object.assign({}, note, { text }));
 | 
			
		||||
 | 
			
		||||
	const emojiNames = unique(getEmojiNames(content));
 | 
			
		||||
	const emojis = await getEmojis(emojiNames);
 | 
			
		||||
	const apemojis = emojis.map(emoji => renderEmoji(emoji));
 | 
			
		||||
 | 
			
		||||
	const tag = [
 | 
			
		||||
		...hashtagTags,
 | 
			
		||||
		...mentionTags,
 | 
			
		||||
		...apemojis,
 | 
			
		||||
	];
 | 
			
		||||
 | 
			
		||||
	return {
 | 
			
		||||
		id: `${config.url}/notes/${note._id}`,
 | 
			
		||||
		type: 'Note',
 | 
			
		||||
		attributedTo,
 | 
			
		||||
		summary: note.cw,
 | 
			
		||||
		content: toHtml(Object.assign({}, note, { text })),
 | 
			
		||||
		content,
 | 
			
		||||
		_misskey_content: text,
 | 
			
		||||
		published: note.createdAt.toISOString(),
 | 
			
		||||
		to,
 | 
			
		||||
@@ -124,3 +136,18 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
 | 
			
		||||
		tag
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getEmojis(names: string[]): Promise<IEmoji[]> {
 | 
			
		||||
	if (names == null || names.length < 1) return [];
 | 
			
		||||
 | 
			
		||||
	const emojis = await Promise.all(
 | 
			
		||||
		names.map(async name => {
 | 
			
		||||
			return await Emoji.findOne({
 | 
			
		||||
				name,
 | 
			
		||||
				host: null
 | 
			
		||||
			});
 | 
			
		||||
		})
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	return emojis.filter(emoji => emoji != null);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ const crypto = require('crypto');
 | 
			
		||||
 | 
			
		||||
import config from '../../config';
 | 
			
		||||
import { ILocalUser } from '../../models/user';
 | 
			
		||||
import { publishApLogStream } from '../../stream';
 | 
			
		||||
 | 
			
		||||
const log = debug('misskey:activitypub:deliver');
 | 
			
		||||
 | 
			
		||||
@@ -64,4 +65,13 @@ export default (user: ILocalUser, url: string, object: any) => new Promise((reso
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	req.end(data);
 | 
			
		||||
 | 
			
		||||
	//#region Log
 | 
			
		||||
	publishApLogStream({
 | 
			
		||||
		direction: 'out',
 | 
			
		||||
		activity: object.type,
 | 
			
		||||
		host: null,
 | 
			
		||||
		actor: user.username
 | 
			
		||||
	});
 | 
			
		||||
	//#endregion
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import * as mongo from 'mongodb';
 | 
			
		||||
import * as Router from 'koa-router';
 | 
			
		||||
import config from '../../config';
 | 
			
		||||
import $ from 'cafy'; import ID from '../../misc/cafy-id';
 | 
			
		||||
import $ from 'cafy'; import ID, { transform } from '../../misc/cafy-id';
 | 
			
		||||
import User from '../../models/user';
 | 
			
		||||
import Following from '../../models/following';
 | 
			
		||||
import pack from '../../remote/activitypub/renderer';
 | 
			
		||||
@@ -49,7 +49,7 @@ export default async (ctx: Router.IRouterContext) => {
 | 
			
		||||
		// カーソルが指定されている場合
 | 
			
		||||
		if (cursor) {
 | 
			
		||||
			query._id = {
 | 
			
		||||
				$lt: cursor
 | 
			
		||||
				$lt: transform(cursor)
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import * as mongo from 'mongodb';
 | 
			
		||||
import * as Router from 'koa-router';
 | 
			
		||||
import config from '../../config';
 | 
			
		||||
import $ from 'cafy'; import ID from '../../misc/cafy-id';
 | 
			
		||||
import $ from 'cafy'; import ID, { transform } from '../../misc/cafy-id';
 | 
			
		||||
import User from '../../models/user';
 | 
			
		||||
import Following from '../../models/following';
 | 
			
		||||
import pack from '../../remote/activitypub/renderer';
 | 
			
		||||
@@ -49,7 +49,7 @@ export default async (ctx: Router.IRouterContext) => {
 | 
			
		||||
		// カーソルが指定されている場合
 | 
			
		||||
		if (cursor) {
 | 
			
		||||
			query._id = {
 | 
			
		||||
				$lt: cursor
 | 
			
		||||
				$lt: transform(cursor)
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import * as mongo from 'mongodb';
 | 
			
		||||
import * as Router from 'koa-router';
 | 
			
		||||
import config from '../../config';
 | 
			
		||||
import $ from 'cafy'; import ID from '../../misc/cafy-id';
 | 
			
		||||
import $ from 'cafy'; import ID, { transform } from '../../misc/cafy-id';
 | 
			
		||||
import User from '../../models/user';
 | 
			
		||||
import pack from '../../remote/activitypub/renderer';
 | 
			
		||||
import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection';
 | 
			
		||||
@@ -61,11 +61,11 @@ export default async (ctx: Router.IRouterContext) => {
 | 
			
		||||
		if (sinceId) {
 | 
			
		||||
			sort._id = 1;
 | 
			
		||||
			query._id = {
 | 
			
		||||
				$gt: sinceId
 | 
			
		||||
				$gt: transform(sinceId)
 | 
			
		||||
			};
 | 
			
		||||
		} else if (untilId) {
 | 
			
		||||
			query._id = {
 | 
			
		||||
				$lt: untilId
 | 
			
		||||
				$lt: transform(untilId)
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
		//#endregion
 | 
			
		||||
 
 | 
			
		||||
@@ -42,18 +42,12 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	let exec = ep.exec;
 | 
			
		||||
 | 
			
		||||
	if (ep.meta.requireFile && file) {
 | 
			
		||||
		exec = exec.bind(null, file);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	let res;
 | 
			
		||||
 | 
			
		||||
	// API invoking
 | 
			
		||||
	try {
 | 
			
		||||
		const before = performance.now();
 | 
			
		||||
		res = await exec(data, user, app);
 | 
			
		||||
		res = await ep.exec(data, user, app, file);
 | 
			
		||||
		const after = performance.now();
 | 
			
		||||
 | 
			
		||||
		const time = after - before;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										51
									
								
								src/server/api/define.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/server/api/define.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
import * as fs from 'fs';
 | 
			
		||||
import { ILocalUser } from '../../models/user';
 | 
			
		||||
import { IApp } from '../../models/app';
 | 
			
		||||
import { IEndpointMeta } from './endpoints';
 | 
			
		||||
 | 
			
		||||
type Params<T extends IEndpointMeta> = {
 | 
			
		||||
	[P in keyof T['params']]: T['params'][P]['transform'] extends Function
 | 
			
		||||
		? ReturnType<T['params'][P]['transform']>
 | 
			
		||||
		: ReturnType<T['params'][P]['validator']['get']>[0];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function <T extends IEndpointMeta>(meta: T, cb: (params: Params<T>, user: ILocalUser, app: IApp, file?: any, cleanup?: Function) => Promise<any>): (params: any, user: ILocalUser, app: IApp, file?: any) => Promise<any> {
 | 
			
		||||
	return (params: any, user: ILocalUser, app: IApp, file?: any) => {
 | 
			
		||||
		function cleanup() {
 | 
			
		||||
			fs.unlink(file.path, () => {});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (meta.requireFile && file == null) return Promise.reject('file required');
 | 
			
		||||
 | 
			
		||||
		const [ps, pserr] = getParams(meta, params);
 | 
			
		||||
		if (pserr) {
 | 
			
		||||
			if (file) cleanup();
 | 
			
		||||
			return Promise.reject(pserr);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return cb(ps, user, app, file, cleanup);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getParams<T extends IEndpointMeta>(defs: T, params: any): [Params<T>, Error] {
 | 
			
		||||
	const x: any = {};
 | 
			
		||||
	let err: Error = null;
 | 
			
		||||
	Object.entries(defs.params).some(([k, def]) => {
 | 
			
		||||
		const [v, e] = def.validator.get(params[k]);
 | 
			
		||||
		if (e) {
 | 
			
		||||
			err = new Error(e.message);
 | 
			
		||||
			err.name = 'INVALID_PARAM';
 | 
			
		||||
			(err as any).param = k;
 | 
			
		||||
			return true;
 | 
			
		||||
		} else {
 | 
			
		||||
			if (v === undefined && def.default) {
 | 
			
		||||
				x[k] = def.default;
 | 
			
		||||
			} else {
 | 
			
		||||
				x[k] = v;
 | 
			
		||||
			}
 | 
			
		||||
			if (def.transform) x[k] = def.transform(x[k]);
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
	return [x, err];
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +1,21 @@
 | 
			
		||||
import { Context } from 'cafy';
 | 
			
		||||
import * as path from 'path';
 | 
			
		||||
import * as glob from 'glob';
 | 
			
		||||
 | 
			
		||||
export interface IEndpointMeta {
 | 
			
		||||
	stability?: 'deprecated' | 'experimental' | 'stable';
 | 
			
		||||
	stability?: string; //'deprecated' | 'experimental' | 'stable';
 | 
			
		||||
 | 
			
		||||
	desc?: any;
 | 
			
		||||
	desc?: { [key: string]: string };
 | 
			
		||||
 | 
			
		||||
	params?: any;
 | 
			
		||||
	params?: {
 | 
			
		||||
		[key: string]: {
 | 
			
		||||
			validator: Context<any>;
 | 
			
		||||
			transform?: any;
 | 
			
		||||
			default?: any;
 | 
			
		||||
			desc?: { [key: string]: string };
 | 
			
		||||
			ref?: string;
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	res?: any;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user