Compare commits
	
		
			74 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					72b85fc09f | ||
| 
						 | 
					6c27412c9c | ||
| 
						 | 
					46bddfc9c2 | ||
| 
						 | 
					56275bcfcb | ||
| 
						 | 
					f35688bab8 | ||
| 
						 | 
					93541f83c8 | ||
| 
						 | 
					ea0d114833 | ||
| 
						 | 
					7f6a3ec828 | ||
| 
						 | 
					732804b6fa | ||
| 
						 | 
					aba85b977d | ||
| 
						 | 
					e6612f610c | ||
| 
						 | 
					5a28632af7 | ||
| 
						 | 
					4099db0d42 | ||
| 
						 | 
					9d50a06d9c | ||
| 
						 | 
					dd7bf9b2a3 | ||
| 
						 | 
					c463284c2f | ||
| 
						 | 
					c1d728a616 | ||
| 
						 | 
					e43c9c0e21 | ||
| 
						 | 
					15cac10d7b | ||
| 
						 | 
					49958ca03f | ||
| 
						 | 
					280dbe9853 | ||
| 
						 | 
					bf964ee969 | ||
| 
						 | 
					61dcd51888 | ||
| 
						 | 
					5448c22031 | ||
| 
						 | 
					27768081e2 | ||
| 
						 | 
					c3140f57b9 | ||
| 
						 | 
					7275bc6d3b | ||
| 
						 | 
					485f2f460e | ||
| 
						 | 
					336912e442 | ||
| 
						 | 
					dd9c94e47e | ||
| 
						 | 
					055863144d | ||
| 
						 | 
					0bf602bae6 | ||
| 
						 | 
					6fc28d1df7 | ||
| 
						 | 
					2ef795aba8 | ||
| 
						 | 
					1d2c50fc26 | ||
| 
						 | 
					cef8aa5e7a | ||
| 
						 | 
					edf3e75344 | ||
| 
						 | 
					62835c6011 | ||
| 
						 | 
					60fb22cb3c | ||
| 
						 | 
					20dea3a793 | ||
| 
						 | 
					aba37ae701 | ||
| 
						 | 
					2c6e6275aa | ||
| 
						 | 
					20ef362854 | ||
| 
						 | 
					4692aa8d9b | ||
| 
						 | 
					f7b6dc08f7 | ||
| 
						 | 
					7dfe7005e0 | ||
| 
						 | 
					b91de4ac12 | ||
| 
						 | 
					d5205d7328 | ||
| 
						 | 
					f44ce535fa | ||
| 
						 | 
					7177fd27c8 | ||
| 
						 | 
					cf304f88d4 | ||
| 
						 | 
					dff1d84031 | ||
| 
						 | 
					96bc17aa10 | ||
| 
						 | 
					41ba06a5e6 | ||
| 
						 | 
					d7ac0418d7 | ||
| 
						 | 
					f4319a9c01 | ||
| 
						 | 
					f4c4d53bbb | ||
| 
						 | 
					0ed43e1bdf | ||
| 
						 | 
					d25bd876cb | ||
| 
						 | 
					b9782397c2 | ||
| 
						 | 
					ea0abc9f71 | ||
| 
						 | 
					27d16c6a12 | ||
| 
						 | 
					ede70d354e | ||
| 
						 | 
					66fa583f6e | ||
| 
						 | 
					77bcb58f12 | ||
| 
						 | 
					61036e3a70 | ||
| 
						 | 
					bcd886c4f5 | ||
| 
						 | 
					4d868aaf1f | ||
| 
						 | 
					80ea747db6 | ||
| 
						 | 
					960f29ce81 | ||
| 
						 | 
					20ee57931f | ||
| 
						 | 
					71ba72e796 | ||
| 
						 | 
					9835945ee1 | ||
| 
						 | 
					4f2d52697d | 
@@ -6,6 +6,8 @@ mongodb:
 | 
			
		||||
  db: misskey
 | 
			
		||||
  user: syuilo
 | 
			
		||||
  pass: ''
 | 
			
		||||
drive:
 | 
			
		||||
  storage: 'db'
 | 
			
		||||
redis:
 | 
			
		||||
  host: localhost
 | 
			
		||||
  port: 6379
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,8 @@ mongodb:
 | 
			
		||||
  db: test-misskey
 | 
			
		||||
  user: admin
 | 
			
		||||
  pass: ''
 | 
			
		||||
drive:
 | 
			
		||||
  storage: 'db'
 | 
			
		||||
# __REDIS__
 | 
			
		||||
redis:
 | 
			
		||||
  host: localhost
 | 
			
		||||
 
 | 
			
		||||
@@ -108,5 +108,8 @@ autoAdmin: true
 | 
			
		||||
#  port: 9200
 | 
			
		||||
#  pass: null
 | 
			
		||||
 | 
			
		||||
# Whether disable HSTS
 | 
			
		||||
#disableHsts: true
 | 
			
		||||
 | 
			
		||||
# Clustering
 | 
			
		||||
#clusterLimit: 1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							@@ -1,22 +1,30 @@
 | 
			
		||||
---
 | 
			
		||||
name: Bug Report
 | 
			
		||||
about: Create a report to help us improve
 | 
			
		||||
title: ''
 | 
			
		||||
labels: bug
 | 
			
		||||
assignees: ''
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# Summary
 | 
			
		||||
 | 
			
		||||
<!-- Tell us what the bug is -->
 | 
			
		||||
 | 
			
		||||
# Expected Behavior
 | 
			
		||||
 | 
			
		||||
<!--- Tell us what should happen -->
 | 
			
		||||
 | 
			
		||||
# Actual Behavior
 | 
			
		||||
 | 
			
		||||
<!--- Tell us what happens instead of the expected behavior -->
 | 
			
		||||
 | 
			
		||||
# Steps to Reproduce
 | 
			
		||||
 | 
			
		||||
1.
 | 
			
		||||
2.
 | 
			
		||||
3.
 | 
			
		||||
 | 
			
		||||
# Environment
 | 
			
		||||
 | 
			
		||||
<!-- Tell us where on the platform it happens -->
 | 
			
		||||
<!-- e.g. desktop or mobile version, your browser, your OS -->
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										31
									
								
								.github/ISSUE_TEMPLATE/client-side-bug-report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								.github/ISSUE_TEMPLATE/client-side-bug-report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
---
 | 
			
		||||
name: Client-side Bug Report
 | 
			
		||||
about: Create a report to help us improve
 | 
			
		||||
title: ''
 | 
			
		||||
labels: bug, client-side
 | 
			
		||||
assignees: ''
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# Summary
 | 
			
		||||
 | 
			
		||||
<!-- Tell us what the bug is -->
 | 
			
		||||
 | 
			
		||||
# Expected Behavior
 | 
			
		||||
 | 
			
		||||
<!--- Tell us what should happen -->
 | 
			
		||||
 | 
			
		||||
# Actual Behavior
 | 
			
		||||
 | 
			
		||||
<!--- Tell us what happens instead of the expected behavior -->
 | 
			
		||||
 | 
			
		||||
# Steps to Reproduce
 | 
			
		||||
 | 
			
		||||
1.
 | 
			
		||||
2.
 | 
			
		||||
3.
 | 
			
		||||
 | 
			
		||||
# Environment
 | 
			
		||||
 | 
			
		||||
<!-- Tell us where on the platform it happens -->
 | 
			
		||||
<!-- e.g. desktop or mobile version, your browser, your OS -->
 | 
			
		||||
							
								
								
									
										12
									
								
								.github/ISSUE_TEMPLATE/client-side-feature-request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.github/ISSUE_TEMPLATE/client-side-feature-request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
---
 | 
			
		||||
name: Client-side Feature Request
 | 
			
		||||
about: Suggest an idea for this project
 | 
			
		||||
title: ''
 | 
			
		||||
labels: client-side, feature
 | 
			
		||||
assignees: ''
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# Summary
 | 
			
		||||
 | 
			
		||||
<!-- Tell us what the suggestion is -->
 | 
			
		||||
							
								
								
									
										9
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
								
							@@ -1,11 +1,12 @@
 | 
			
		||||
---
 | 
			
		||||
name: Feature Request
 | 
			
		||||
about: Suggest an idea for this project
 | 
			
		||||
title: ''
 | 
			
		||||
labels: feature
 | 
			
		||||
assignees: ''
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# Summary
 | 
			
		||||
<!-- Tell us what the suggestion is -->
 | 
			
		||||
 | 
			
		||||
# Environment
 | 
			
		||||
<!-- Tell us where on the platform it related -->
 | 
			
		||||
<!-- e.g. desktop or mobile version, your browser, your OS -->
 | 
			
		||||
<!-- Tell us what the suggestion is -->
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										31
									
								
								.github/ISSUE_TEMPLATE/server-side-bug-report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								.github/ISSUE_TEMPLATE/server-side-bug-report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
---
 | 
			
		||||
name: Server-side Bug Report
 | 
			
		||||
about: Create a report to help us improve
 | 
			
		||||
title: ''
 | 
			
		||||
labels: bug, server-side
 | 
			
		||||
assignees: ''
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# Summary
 | 
			
		||||
 | 
			
		||||
<!-- Tell us what the bug is -->
 | 
			
		||||
 | 
			
		||||
# Expected Behavior
 | 
			
		||||
 | 
			
		||||
<!--- Tell us what should happen -->
 | 
			
		||||
 | 
			
		||||
# Actual Behavior
 | 
			
		||||
 | 
			
		||||
<!--- Tell us what happens instead of the expected behavior -->
 | 
			
		||||
 | 
			
		||||
# Steps to Reproduce
 | 
			
		||||
 | 
			
		||||
1.
 | 
			
		||||
2.
 | 
			
		||||
3.
 | 
			
		||||
 | 
			
		||||
# Environment
 | 
			
		||||
 | 
			
		||||
<!-- Tell us where on the platform it happens -->
 | 
			
		||||
<!-- e.g. your Node.js version, your OS -->
 | 
			
		||||
							
								
								
									
										12
									
								
								.github/ISSUE_TEMPLATE/server-side-feature-request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.github/ISSUE_TEMPLATE/server-side-feature-request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
---
 | 
			
		||||
name: Server-side Feature Request
 | 
			
		||||
about: Suggest an idea for this project
 | 
			
		||||
title: ''
 | 
			
		||||
labels: feature, server-side
 | 
			
		||||
assignees: ''
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# Summary
 | 
			
		||||
 | 
			
		||||
<!-- Tell us what the suggestion is -->
 | 
			
		||||
							
								
								
									
										21
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,6 +1,27 @@
 | 
			
		||||
ChangeLog
 | 
			
		||||
=========
 | 
			
		||||
 | 
			
		||||
10.83.0
 | 
			
		||||
----------
 | 
			
		||||
* 特定のインスタンスをブロックをできるように
 | 
			
		||||
* 特定のインスタンスからのフォローを全解除できるように
 | 
			
		||||
* インスタンスごとのチャートを追加
 | 
			
		||||
 | 
			
		||||
10.82.4
 | 
			
		||||
----------
 | 
			
		||||
* 起動できなくなることがある問題を修正
 | 
			
		||||
 | 
			
		||||
10.82.3
 | 
			
		||||
----------
 | 
			
		||||
* フォロー/ミュート/ブロックデータをエクスポート可能に
 | 
			
		||||
* バグ修正
 | 
			
		||||
* デザインの調整
 | 
			
		||||
* ジョブキューの動作を修正
 | 
			
		||||
 | 
			
		||||
10.82.2
 | 
			
		||||
----------
 | 
			
		||||
* ジョブキューの動作を修正
 | 
			
		||||
 | 
			
		||||
10.82.1
 | 
			
		||||
----------
 | 
			
		||||
* クラスタリング環境でのジョブキューの動作を修正
 | 
			
		||||
 
 | 
			
		||||
@@ -44,3 +44,31 @@ Stands for _**S**ervice**W**orker_.
 | 
			
		||||
 | 
			
		||||
#### Denyaize
 | 
			
		||||
Nyaizeを解除すること
 | 
			
		||||
 | 
			
		||||
## Code style
 | 
			
		||||
### Don't use `export default`
 | 
			
		||||
Bad:
 | 
			
		||||
``` ts
 | 
			
		||||
export default function(foo: string): string {
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Good:
 | 
			
		||||
``` ts
 | 
			
		||||
export function something(foo: string): string {
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Directory structure
 | 
			
		||||
```
 | 
			
		||||
src ... ソースコード
 | 
			
		||||
	@types ... 外部ライブラリなどの型定義
 | 
			
		||||
	prelude ... Misskeyに関係ないかつ副作用なし
 | 
			
		||||
	misc ... 副作用なしのユーティリティ処理
 | 
			
		||||
	service ... 副作用ありの共通処理
 | 
			
		||||
	queue ... ジョブキューとジョブ
 | 
			
		||||
	server ... Webサーバー
 | 
			
		||||
	client ... クライアント
 | 
			
		||||
	mfm ... MFM
 | 
			
		||||
 | 
			
		||||
test ... テスト
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -115,6 +115,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
 | 
			
		||||
<td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=2PsbFNw0tnubZzgSXD01R6hIgncfiElG7H7HX2Y3dyo%3D" alt="nemu" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3?token-time=2145916800&token-hash=9JtETp0X8gI280Ne1E8bxn6j4Lw5o2k4mJkICx97V_k%3D" alt="YUKIMOCHI" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17195955/be45e5e14c3e48b2bee0456c84e19df4/4?token-time=2145916800&token-hash=SbdZeN5SmsuT9stD6v0jN1z0hftg0FmRiCTxysU0Ihw%3D" alt="Damillora" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/8241184/39e18850e87a449e9c9a71acb3310ebd/3?token-time=2145916800&token-hash=gMq30aylxu5v3G8pRhWR5jeRBbYWEoRKjGbNeiCQz5g%3D" alt="Acid Chicken" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/2?token-time=2145916800&token-hash=zcwFxb2zopzWwksKVU1YpfAEjsl4yKT02aQ6yiAFRiQ%3D" alt="natalie" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=5T8XcaAf9Zyzfg3QubR06s_kJZkArVEM2dwObrBVAU4%3D" alt="Hiratake" width="100"></td>
 | 
			
		||||
@@ -124,6 +125,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=16542964">Takumi Sugita</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=13039004">nemu</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/damillora">Damillora</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/acid_chicken">Acid Chicken</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/hiratake">Hiratake</a></td>
 | 
			
		||||
@@ -140,7 +142,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
 | 
			
		||||
</tr></table>
 | 
			
		||||
 | 
			
		||||
**Last updated:** Sun, 03 Feb 2019 10:13:06 UTC
 | 
			
		||||
**Last updated:** Wed, 06 Feb 2019 18:18:05 UTC
 | 
			
		||||
<!-- PATREON_END -->
 | 
			
		||||
 | 
			
		||||
:four_leaf_clover: Copyright
 | 
			
		||||
 
 | 
			
		||||
@@ -510,7 +510,11 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  email-verified: "メールアドレスが確認されました"
 | 
			
		||||
  email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。"
 | 
			
		||||
  export: "エクスポート"
 | 
			
		||||
  export-notes: "すべての投稿のエクスポート"
 | 
			
		||||
  export-targets:
 | 
			
		||||
    all-notes: "すべての投稿データ"
 | 
			
		||||
    following-list: "フォロー"
 | 
			
		||||
    mute-list: "ミュート"
 | 
			
		||||
    blocking-list: "ブロック"
 | 
			
		||||
  export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
@@ -1003,6 +1007,7 @@ admin/views/index.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
  abuse: "スパム報告"
 | 
			
		||||
  queue: "ジョブキュー"
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
@@ -1012,6 +1017,9 @@ admin/views/dashboard.vue:
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
admin/views/queue.vue:
 | 
			
		||||
  operation: "操作"
 | 
			
		||||
  remove-all-jobs: "すべてのジョブをクリア"
 | 
			
		||||
admin/views/abuse.vue:
 | 
			
		||||
  title: "スパム報告"
 | 
			
		||||
  target: "対象"
 | 
			
		||||
@@ -1225,6 +1233,39 @@ admin/views/announcements.vue:
 | 
			
		||||
    removed: "削除しました"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
admin/views/federation.vue:
 | 
			
		||||
  federation: "連合"
 | 
			
		||||
  host: "ホスト"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  following: "フォロー中"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
  status: "ステータス"
 | 
			
		||||
  latest-request-sent-at: "直近のリクエスト送信"
 | 
			
		||||
  latest-request-received-at: "直近のリクエスト受信"
 | 
			
		||||
  remove-all-following: "フォローを全解除"
 | 
			
		||||
  remove-all-following-info: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  lookup: "照会"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  instance-not-registered: "そのインスタンスは登録されていません"
 | 
			
		||||
  sort: "ソート"
 | 
			
		||||
  sorts:
 | 
			
		||||
    caughtAtAsc: "登録日時が古い順"
 | 
			
		||||
    caughtAtDesc: "登録日時が新しい順"
 | 
			
		||||
    notesAsc: "投稿が少ない順"
 | 
			
		||||
    notesDesc: "投稿が多い順"
 | 
			
		||||
    usersAsc: "ユーザーが少ない順"
 | 
			
		||||
    usersDesc: "ユーザーが多い順"
 | 
			
		||||
    followingAsc: "フォローが少ない順"
 | 
			
		||||
    followingDesc: "フォローが多い順"
 | 
			
		||||
    followersAsc: "フォロワーが少ない順"
 | 
			
		||||
    followersDesc: "フォロワーが多い順"
 | 
			
		||||
  state: "状態"
 | 
			
		||||
  states:
 | 
			
		||||
    all: "すべて"
 | 
			
		||||
    blocked: "ブロック"
 | 
			
		||||
  result-is-truncated: "上位{n}件を表示しています。"
 | 
			
		||||
desktop/views/pages/welcome.vue:
 | 
			
		||||
  about: "詳しく..."
 | 
			
		||||
  gotit: "わかった"
 | 
			
		||||
 
 | 
			
		||||
@@ -510,7 +510,11 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  email-verified: "メールアドレスが確認されました"
 | 
			
		||||
  email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。"
 | 
			
		||||
  export: "エクスポート"
 | 
			
		||||
  export-notes: "すべての投稿のエクスポート"
 | 
			
		||||
  export-targets:
 | 
			
		||||
    all-notes: "すべての投稿データ"
 | 
			
		||||
    following-list: "フォロー"
 | 
			
		||||
    mute-list: "ミュート"
 | 
			
		||||
    blocking-list: "ブロック"
 | 
			
		||||
  export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
@@ -1003,6 +1007,7 @@ admin/views/index.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
  abuse: "スパム報告"
 | 
			
		||||
  queue: "ジョブキュー"
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
@@ -1012,6 +1017,9 @@ admin/views/dashboard.vue:
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
admin/views/queue.vue:
 | 
			
		||||
  operation: "操作"
 | 
			
		||||
  remove-all-jobs: "すべてのジョブをクリア"
 | 
			
		||||
admin/views/abuse.vue:
 | 
			
		||||
  title: "スパム報告"
 | 
			
		||||
  target: "対象"
 | 
			
		||||
@@ -1225,6 +1233,39 @@ admin/views/announcements.vue:
 | 
			
		||||
    removed: "削除しました"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
admin/views/federation.vue:
 | 
			
		||||
  federation: "連合"
 | 
			
		||||
  host: "ホスト"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  following: "フォロー中"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
  status: "ステータス"
 | 
			
		||||
  latest-request-sent-at: "直近のリクエスト送信"
 | 
			
		||||
  latest-request-received-at: "直近のリクエスト受信"
 | 
			
		||||
  remove-all-following: "フォローを全解除"
 | 
			
		||||
  remove-all-following-info: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  lookup: "照会"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  instance-not-registered: "そのインスタンスは登録されていません"
 | 
			
		||||
  sort: "ソート"
 | 
			
		||||
  sorts:
 | 
			
		||||
    caughtAtAsc: "登録日時が古い順"
 | 
			
		||||
    caughtAtDesc: "登録日時が新しい順"
 | 
			
		||||
    notesAsc: "投稿が少ない順"
 | 
			
		||||
    notesDesc: "投稿が多い順"
 | 
			
		||||
    usersAsc: "ユーザーが少ない順"
 | 
			
		||||
    usersDesc: "ユーザーが多い順"
 | 
			
		||||
    followingAsc: "フォローが少ない順"
 | 
			
		||||
    followingDesc: "フォローが多い順"
 | 
			
		||||
    followersAsc: "フォロワーが少ない順"
 | 
			
		||||
    followersDesc: "フォロワーが多い順"
 | 
			
		||||
  state: "状態"
 | 
			
		||||
  states:
 | 
			
		||||
    all: "すべて"
 | 
			
		||||
    blocked: "ブロック"
 | 
			
		||||
  result-is-truncated: "上位{n}件を表示しています。"
 | 
			
		||||
desktop/views/pages/welcome.vue:
 | 
			
		||||
  about: "詳しく..."
 | 
			
		||||
  gotit: "わかった"
 | 
			
		||||
 
 | 
			
		||||
@@ -510,7 +510,11 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  email-verified: "Your email has been verified."
 | 
			
		||||
  email-not-verified: "Email address is not confirmed. Please check your inbox."
 | 
			
		||||
  export: "Export"
 | 
			
		||||
  export-notes: "Export all of your Notes"
 | 
			
		||||
  export-targets:
 | 
			
		||||
    all-notes: "All posted Notes"
 | 
			
		||||
    following-list: "List of followers"
 | 
			
		||||
    mute-list: "List of muted accounts"
 | 
			
		||||
    blocking-list: "List of blocked accounts"
 | 
			
		||||
  export-requested: "You have requested an export. This may take a while. After the export is complete, the resulting file will be added to the drive."
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "User"
 | 
			
		||||
@@ -1003,6 +1007,7 @@ admin/views/index.vue:
 | 
			
		||||
  announcements: "Announcements"
 | 
			
		||||
  hashtags: "Hashtags"
 | 
			
		||||
  abuse: "Abuse"
 | 
			
		||||
  queue: "Job Queue"
 | 
			
		||||
  back-to-misskey: "Back to Misskey"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "Dashboard"
 | 
			
		||||
@@ -1012,6 +1017,9 @@ admin/views/dashboard.vue:
 | 
			
		||||
  instances: "Instances"
 | 
			
		||||
  this-instance: "This instance"
 | 
			
		||||
  federated: "Federated"
 | 
			
		||||
admin/views/queue.vue:
 | 
			
		||||
  operation: "Action(s)"
 | 
			
		||||
  remove-all-jobs: "Clear all queued jobs"
 | 
			
		||||
admin/views/abuse.vue:
 | 
			
		||||
  title: "Abuse"
 | 
			
		||||
  target: "Target"
 | 
			
		||||
@@ -1225,6 +1233,39 @@ admin/views/announcements.vue:
 | 
			
		||||
    removed: "Deleted"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
admin/views/federation.vue:
 | 
			
		||||
  federation: "Federation"
 | 
			
		||||
  host: "Host"
 | 
			
		||||
  notes: "Notes"
 | 
			
		||||
  users: "Users"
 | 
			
		||||
  following: "Following"
 | 
			
		||||
  followers: "Followers"
 | 
			
		||||
  status: "Status"
 | 
			
		||||
  latest-request-sent-at: "Time of last request sent"
 | 
			
		||||
  latest-request-received-at: "Last request received at"
 | 
			
		||||
  remove-all-following: "フォローを全解除"
 | 
			
		||||
  remove-all-following-info: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
 | 
			
		||||
  block: "Block"
 | 
			
		||||
  lookup: "Look up"
 | 
			
		||||
  instances: "Instances"
 | 
			
		||||
  instance-not-registered: "The instance has not been discovered"
 | 
			
		||||
  sort: "Sort by"
 | 
			
		||||
  sorts:
 | 
			
		||||
    caughtAtAsc: "Date of discovery (Ascending)"
 | 
			
		||||
    caughtAtDesc: "Date of discovery (Descending)"
 | 
			
		||||
    notesAsc: "Order by least Notes posted"
 | 
			
		||||
    notesDesc: "Order by most Notes posted"
 | 
			
		||||
    usersAsc: "Less followers"
 | 
			
		||||
    usersDesc: "More followers"
 | 
			
		||||
    followingAsc: "Least followed"
 | 
			
		||||
    followingDesc: "Has more followers"
 | 
			
		||||
    followersAsc: "Sort by having less followers"
 | 
			
		||||
    followersDesc: "Sort by the larger number of followers"
 | 
			
		||||
  state: "Status"
 | 
			
		||||
  states:
 | 
			
		||||
    all: "All"
 | 
			
		||||
    blocked: "Blocked"
 | 
			
		||||
  result-is-truncated: "Displaying the top {n} items."
 | 
			
		||||
desktop/views/pages/welcome.vue:
 | 
			
		||||
  about: "More details..."
 | 
			
		||||
  gotit: "Got it!"
 | 
			
		||||
 
 | 
			
		||||
@@ -510,7 +510,11 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  email-verified: "メールアドレスが確認されました"
 | 
			
		||||
  email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。"
 | 
			
		||||
  export: "エクスポート"
 | 
			
		||||
  export-notes: "すべての投稿のエクスポート"
 | 
			
		||||
  export-targets:
 | 
			
		||||
    all-notes: "すべての投稿データ"
 | 
			
		||||
    following-list: "フォロー"
 | 
			
		||||
    mute-list: "ミュート"
 | 
			
		||||
    blocking-list: "ブロック"
 | 
			
		||||
  export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "Usuarios"
 | 
			
		||||
@@ -1003,6 +1007,7 @@ admin/views/index.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "Hashtags"
 | 
			
		||||
  abuse: "スパム報告"
 | 
			
		||||
  queue: "ジョブキュー"
 | 
			
		||||
  back-to-misskey: "Volver a Misskey"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "Panel de Control"
 | 
			
		||||
@@ -1012,6 +1017,9 @@ admin/views/dashboard.vue:
 | 
			
		||||
  instances: "Instancias"
 | 
			
		||||
  this-instance: "Esta instancia"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
admin/views/queue.vue:
 | 
			
		||||
  operation: "操作"
 | 
			
		||||
  remove-all-jobs: "すべてのジョブをクリア"
 | 
			
		||||
admin/views/abuse.vue:
 | 
			
		||||
  title: "スパム報告"
 | 
			
		||||
  target: "対象"
 | 
			
		||||
@@ -1225,6 +1233,39 @@ admin/views/announcements.vue:
 | 
			
		||||
    removed: "削除しました"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
admin/views/federation.vue:
 | 
			
		||||
  federation: "連合"
 | 
			
		||||
  host: "ホスト"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  following: "フォロー中"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
  status: "ステータス"
 | 
			
		||||
  latest-request-sent-at: "直近のリクエスト送信"
 | 
			
		||||
  latest-request-received-at: "直近のリクエスト受信"
 | 
			
		||||
  remove-all-following: "フォローを全解除"
 | 
			
		||||
  remove-all-following-info: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  lookup: "照会"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  instance-not-registered: "そのインスタンスは登録されていません"
 | 
			
		||||
  sort: "ソート"
 | 
			
		||||
  sorts:
 | 
			
		||||
    caughtAtAsc: "登録日時が古い順"
 | 
			
		||||
    caughtAtDesc: "登録日時が新しい順"
 | 
			
		||||
    notesAsc: "投稿が少ない順"
 | 
			
		||||
    notesDesc: "投稿が多い順"
 | 
			
		||||
    usersAsc: "ユーザーが少ない順"
 | 
			
		||||
    usersDesc: "ユーザーが多い順"
 | 
			
		||||
    followingAsc: "フォローが少ない順"
 | 
			
		||||
    followingDesc: "フォローが多い順"
 | 
			
		||||
    followersAsc: "フォロワーが少ない順"
 | 
			
		||||
    followersDesc: "フォロワーが多い順"
 | 
			
		||||
  state: "状態"
 | 
			
		||||
  states:
 | 
			
		||||
    all: "すべて"
 | 
			
		||||
    blocked: "ブロック"
 | 
			
		||||
  result-is-truncated: "上位{n}件を表示しています。"
 | 
			
		||||
desktop/views/pages/welcome.vue:
 | 
			
		||||
  about: "詳しく..."
 | 
			
		||||
  gotit: "わかった"
 | 
			
		||||
 
 | 
			
		||||
@@ -345,8 +345,8 @@ common/views/components/note-menu.vue:
 | 
			
		||||
  copy-link: "Copier le lien"
 | 
			
		||||
  favorite: "Mettre cette note en favoris"
 | 
			
		||||
  unfavorite: "Retirer des favoris"
 | 
			
		||||
  watch: "ウォッチ"
 | 
			
		||||
  unwatch: "ウォッチ解除"
 | 
			
		||||
  watch: "Surveiller"
 | 
			
		||||
  unwatch: "Ne plus surveiller"
 | 
			
		||||
  pin: "Épingler sur votre profil"
 | 
			
		||||
  unpin: "Désépingler"
 | 
			
		||||
  delete: "Supprimer"
 | 
			
		||||
@@ -363,10 +363,10 @@ common/views/components/user-menu.vue:
 | 
			
		||||
  report-abuse: "Signaler un abus"
 | 
			
		||||
  report-abuse-detail: "Détail du signalement"
 | 
			
		||||
  report-abuse-reported: "Transmit à l’administrateur. Merci de votre collaboration."
 | 
			
		||||
  silence: "サイレンス"
 | 
			
		||||
  unsilence: "サイレンス解除"
 | 
			
		||||
  silence: "Mettre en sourdine"
 | 
			
		||||
  unsilence: "Enlever la sourdine"
 | 
			
		||||
  suspend: "Suspendre"
 | 
			
		||||
  unsuspend: "凍結解除"
 | 
			
		||||
  unsuspend: "Ne plus suspendre"
 | 
			
		||||
common/views/components/poll.vue:
 | 
			
		||||
  vote-to: "Voter pour '{}'"
 | 
			
		||||
  vote-count: "{} votes"
 | 
			
		||||
@@ -509,8 +509,12 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  email-address: "Adresse de courrier électronique"
 | 
			
		||||
  email-verified: "L’adresse du courrier électronique a été vérifiée."
 | 
			
		||||
  email-not-verified: "Adresse de courriel n’est pas confirmée. Veuillez vérifier votre boite de réception."
 | 
			
		||||
  export: "エクスポート"
 | 
			
		||||
  export-notes: "すべての投稿のエクスポート"
 | 
			
		||||
  export: "Exporter"
 | 
			
		||||
  export-targets:
 | 
			
		||||
    all-notes: "Toutes les notes publiées"
 | 
			
		||||
    following-list: "Liste des abonnements"
 | 
			
		||||
    mute-list: "Liste des comptes mis en sourdine"
 | 
			
		||||
    blocking-list: "Liste des comptes bloqués"
 | 
			
		||||
  export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "Utilisateur·rice"
 | 
			
		||||
@@ -1003,6 +1007,7 @@ admin/views/index.vue:
 | 
			
		||||
  announcements: "Annonces"
 | 
			
		||||
  hashtags: "Hashtags"
 | 
			
		||||
  abuse: "Abus"
 | 
			
		||||
  queue: "File d’attente"
 | 
			
		||||
  back-to-misskey: "Retour vers Misskey"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "Tableau de bord"
 | 
			
		||||
@@ -1012,6 +1017,9 @@ admin/views/dashboard.vue:
 | 
			
		||||
  instances: "Instances"
 | 
			
		||||
  this-instance: "Cette instance"
 | 
			
		||||
  federated: "Fédérées"
 | 
			
		||||
admin/views/queue.vue:
 | 
			
		||||
  operation: "Action(s)"
 | 
			
		||||
  remove-all-jobs: "すべてのジョブをクリア"
 | 
			
		||||
admin/views/abuse.vue:
 | 
			
		||||
  title: "Abus"
 | 
			
		||||
  target: "Cible"
 | 
			
		||||
@@ -1153,8 +1161,8 @@ admin/views/users.vue:
 | 
			
		||||
  unsuspend: "Suspension levée"
 | 
			
		||||
  unsuspend-confirm: "Souhaiteriez-vous ne plus suspendre ce compte ?"
 | 
			
		||||
  unsuspended: "La suspension de l’utilisateur a été levée avec succès"
 | 
			
		||||
  make-silence: "サイレンス"
 | 
			
		||||
  unmake-silence: "サイレンスの解除"
 | 
			
		||||
  make-silence: "Mettre en sourdine"
 | 
			
		||||
  unmake-silence: "Enlever la sourdine"
 | 
			
		||||
  verify: "Vérification du compte"
 | 
			
		||||
  verify-confirm: "Souhaiteriez-vous rendre votre compte comme étant un compte vérifié ?"
 | 
			
		||||
  verified: "Le compte a été vérifié"
 | 
			
		||||
@@ -1225,6 +1233,39 @@ admin/views/announcements.vue:
 | 
			
		||||
    removed: "Supprimé"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Tags cachés"
 | 
			
		||||
admin/views/federation.vue:
 | 
			
		||||
  federation: "Fédération"
 | 
			
		||||
  host: "Hôte"
 | 
			
		||||
  notes: "Notes"
 | 
			
		||||
  users: "Utilisateur·rice·s"
 | 
			
		||||
  following: "Abonnements"
 | 
			
		||||
  followers: "Abonné·e·s"
 | 
			
		||||
  status: "Statuts"
 | 
			
		||||
  latest-request-sent-at: "Dernière requête envoyée"
 | 
			
		||||
  latest-request-received-at: "Dernière requête reçue"
 | 
			
		||||
  remove-all-following: "フォローを全解除"
 | 
			
		||||
  remove-all-following-info: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  lookup: "照会"
 | 
			
		||||
  instances: "Instances"
 | 
			
		||||
  instance-not-registered: "そのインスタンスは登録されていません"
 | 
			
		||||
  sort: "Trier par"
 | 
			
		||||
  sorts:
 | 
			
		||||
    caughtAtAsc: "Date d’inscription (Ascendant)"
 | 
			
		||||
    caughtAtDesc: "Date d’inscription (Descendant)"
 | 
			
		||||
    notesAsc: "投稿が少ない順"
 | 
			
		||||
    notesDesc: "Description des notes"
 | 
			
		||||
    usersAsc: "ユーザーが少ない順"
 | 
			
		||||
    usersDesc: "ユーザーが多い順"
 | 
			
		||||
    followingAsc: "Les moins suivies"
 | 
			
		||||
    followingDesc: "フォローが多い順"
 | 
			
		||||
    followersAsc: "Ayant le moins d'abonné·e·s"
 | 
			
		||||
    followersDesc: "Ayant le plus d'abonné·e·s"
 | 
			
		||||
  state: "状態"
 | 
			
		||||
  states:
 | 
			
		||||
    all: "すべて"
 | 
			
		||||
    blocked: "ブロック"
 | 
			
		||||
  result-is-truncated: "上位{n}件を表示しています。"
 | 
			
		||||
desktop/views/pages/welcome.vue:
 | 
			
		||||
  about: "à propos"
 | 
			
		||||
  gotit: "J'ai compris !"
 | 
			
		||||
 
 | 
			
		||||
@@ -510,7 +510,11 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  email-verified: "メールアドレスが確認されました"
 | 
			
		||||
  email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。"
 | 
			
		||||
  export: "エクスポート"
 | 
			
		||||
  export-notes: "すべての投稿のエクスポート"
 | 
			
		||||
  export-targets:
 | 
			
		||||
    all-notes: "すべての投稿データ"
 | 
			
		||||
    following-list: "フォロー"
 | 
			
		||||
    mute-list: "ミュート"
 | 
			
		||||
    blocking-list: "ブロック"
 | 
			
		||||
  export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
@@ -1003,6 +1007,7 @@ admin/views/index.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
  abuse: "スパム報告"
 | 
			
		||||
  queue: "ジョブキュー"
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
@@ -1012,6 +1017,9 @@ admin/views/dashboard.vue:
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
admin/views/queue.vue:
 | 
			
		||||
  operation: "操作"
 | 
			
		||||
  remove-all-jobs: "すべてのジョブをクリア"
 | 
			
		||||
admin/views/abuse.vue:
 | 
			
		||||
  title: "スパム報告"
 | 
			
		||||
  target: "対象"
 | 
			
		||||
@@ -1225,6 +1233,39 @@ admin/views/announcements.vue:
 | 
			
		||||
    removed: "削除しました"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
admin/views/federation.vue:
 | 
			
		||||
  federation: "連合"
 | 
			
		||||
  host: "ホスト"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  following: "フォロー中"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
  status: "ステータス"
 | 
			
		||||
  latest-request-sent-at: "直近のリクエスト送信"
 | 
			
		||||
  latest-request-received-at: "直近のリクエスト受信"
 | 
			
		||||
  remove-all-following: "フォローを全解除"
 | 
			
		||||
  remove-all-following-info: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  lookup: "照会"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  instance-not-registered: "そのインスタンスは登録されていません"
 | 
			
		||||
  sort: "ソート"
 | 
			
		||||
  sorts:
 | 
			
		||||
    caughtAtAsc: "登録日時が古い順"
 | 
			
		||||
    caughtAtDesc: "登録日時が新しい順"
 | 
			
		||||
    notesAsc: "投稿が少ない順"
 | 
			
		||||
    notesDesc: "投稿が多い順"
 | 
			
		||||
    usersAsc: "ユーザーが少ない順"
 | 
			
		||||
    usersDesc: "ユーザーが多い順"
 | 
			
		||||
    followingAsc: "フォローが少ない順"
 | 
			
		||||
    followingDesc: "フォローが多い順"
 | 
			
		||||
    followersAsc: "フォロワーが少ない順"
 | 
			
		||||
    followersDesc: "フォロワーが多い順"
 | 
			
		||||
  state: "状態"
 | 
			
		||||
  states:
 | 
			
		||||
    all: "すべて"
 | 
			
		||||
    blocked: "ブロック"
 | 
			
		||||
  result-is-truncated: "上位{n}件を表示しています。"
 | 
			
		||||
desktop/views/pages/welcome.vue:
 | 
			
		||||
  about: "詳しく..."
 | 
			
		||||
  gotit: "わかった"
 | 
			
		||||
 
 | 
			
		||||
@@ -558,7 +558,11 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  email-verified: "メールアドレスが確認されました"
 | 
			
		||||
  email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。"
 | 
			
		||||
  export: "エクスポート"
 | 
			
		||||
  export-notes: "すべての投稿のエクスポート"
 | 
			
		||||
  export-targets:
 | 
			
		||||
    all-notes: "すべての投稿データ"
 | 
			
		||||
    following-list: "フォロー"
 | 
			
		||||
    mute-list: "ミュート"
 | 
			
		||||
    blocking-list: "ブロック"
 | 
			
		||||
  export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
 | 
			
		||||
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
@@ -1129,6 +1133,7 @@ admin/views/index.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
  abuse: "スパム報告"
 | 
			
		||||
  queue: "ジョブキュー"
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
@@ -1140,6 +1145,10 @@ admin/views/dashboard.vue:
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
 | 
			
		||||
admin/views/queue.vue:
 | 
			
		||||
  operation: "操作"
 | 
			
		||||
  remove-all-jobs: "すべてのジョブをクリア"
 | 
			
		||||
 | 
			
		||||
admin/views/abuse.vue:
 | 
			
		||||
  title: "スパム報告"
 | 
			
		||||
  target: "対象"
 | 
			
		||||
@@ -1362,6 +1371,60 @@ admin/views/announcements.vue:
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
 | 
			
		||||
admin/views/federation.vue:
 | 
			
		||||
  federation: "連合"
 | 
			
		||||
  host: "ホスト"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  following: "フォロー中"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
  status: "ステータス"
 | 
			
		||||
  latest-request-sent-at: "直近のリクエスト送信"
 | 
			
		||||
  latest-request-received-at: "直近のリクエスト受信"
 | 
			
		||||
  remove-all-following: "フォローを全解除"
 | 
			
		||||
  remove-all-following-info: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  lookup: "照会"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  instance-not-registered: "そのインスタンスは登録されていません"
 | 
			
		||||
  sort: "ソート"
 | 
			
		||||
  sorts:
 | 
			
		||||
    caughtAtAsc: "登録日時が古い順"
 | 
			
		||||
    caughtAtDesc: "登録日時が新しい順"
 | 
			
		||||
    notesAsc: "投稿が少ない順"
 | 
			
		||||
    notesDesc: "投稿が多い順"
 | 
			
		||||
    usersAsc: "ユーザーが少ない順"
 | 
			
		||||
    usersDesc: "ユーザーが多い順"
 | 
			
		||||
    followingAsc: "フォローが少ない順"
 | 
			
		||||
    followingDesc: "フォローが多い順"
 | 
			
		||||
    followersAsc: "フォロワーが少ない順"
 | 
			
		||||
    followersDesc: "フォロワーが多い順"
 | 
			
		||||
    driveUsageAsc: "ドライブ使用量が少ない順"
 | 
			
		||||
    driveUsageDesc: "ドライブ使用量が多い順"
 | 
			
		||||
    driveFilesAsc: "ドライブのファイル数が少ない順"
 | 
			
		||||
    driveFilesDesc: "ドライブのファイル数が多い順"
 | 
			
		||||
  state: "状態"
 | 
			
		||||
  states:
 | 
			
		||||
    all: "すべて"
 | 
			
		||||
    blocked: "ブロック"
 | 
			
		||||
  result-is-truncated: "上位{n}件を表示しています。"
 | 
			
		||||
  charts: "チャート"
 | 
			
		||||
  chart-srcs:
 | 
			
		||||
    requests: "リクエスト"
 | 
			
		||||
    users: "ユーザーの増減"
 | 
			
		||||
    users-total: "ユーザーの積算"
 | 
			
		||||
    notes: "投稿の増減"
 | 
			
		||||
    notes-total: "投稿の積算"
 | 
			
		||||
    ff: "フォロー/フォロワーの増減"
 | 
			
		||||
    ff-total: "フォロー/フォロワーの積算"
 | 
			
		||||
    drive-usage: "ドライブ使用量の増減"
 | 
			
		||||
    drive-usage-total: "ドライブ使用量の増減"
 | 
			
		||||
    drive-files: "ドライブファイル数の増減"
 | 
			
		||||
    drive-files-total: "ドライブファイル数の増減"
 | 
			
		||||
  chart-spans:
 | 
			
		||||
    hour: "1時間ごと"
 | 
			
		||||
    day: "1日ごと"
 | 
			
		||||
 | 
			
		||||
desktop/views/pages/welcome.vue:
 | 
			
		||||
  about: "詳しく..."
 | 
			
		||||
  gotit: "わかった"
 | 
			
		||||
 
 | 
			
		||||
@@ -510,7 +510,11 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  email-verified: "このメールアドレスOKや!"
 | 
			
		||||
  email-not-verified: "メールアドレスが確認されとらん。メールボックスもっぺん見てくれへん?"
 | 
			
		||||
  export: "エクスポート"
 | 
			
		||||
  export-notes: "すべての投稿のエクスポート"
 | 
			
		||||
  export-targets:
 | 
			
		||||
    all-notes: "すべての投稿データ"
 | 
			
		||||
    following-list: "フォロー"
 | 
			
		||||
    mute-list: "ミュート"
 | 
			
		||||
    blocking-list: "ブロック"
 | 
			
		||||
  export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
@@ -1003,6 +1007,7 @@ admin/views/index.vue:
 | 
			
		||||
  announcements: "知っといてや"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
  abuse: "スパム報告"
 | 
			
		||||
  queue: "ジョブキュー"
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
@@ -1012,6 +1017,9 @@ admin/views/dashboard.vue:
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "ワイのインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
admin/views/queue.vue:
 | 
			
		||||
  operation: "操作"
 | 
			
		||||
  remove-all-jobs: "すべてのジョブをクリア"
 | 
			
		||||
admin/views/abuse.vue:
 | 
			
		||||
  title: "スパム報告"
 | 
			
		||||
  target: "対象"
 | 
			
		||||
@@ -1225,6 +1233,39 @@ admin/views/announcements.vue:
 | 
			
		||||
    removed: "削除しました"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
admin/views/federation.vue:
 | 
			
		||||
  federation: "連合"
 | 
			
		||||
  host: "ホスト"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  following: "フォロー中"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
  status: "ステータス"
 | 
			
		||||
  latest-request-sent-at: "直近のリクエスト送信"
 | 
			
		||||
  latest-request-received-at: "直近のリクエスト受信"
 | 
			
		||||
  remove-all-following: "フォローを全解除"
 | 
			
		||||
  remove-all-following-info: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  lookup: "照会"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  instance-not-registered: "そのインスタンスは登録されていません"
 | 
			
		||||
  sort: "ソート"
 | 
			
		||||
  sorts:
 | 
			
		||||
    caughtAtAsc: "登録日時が古い順"
 | 
			
		||||
    caughtAtDesc: "登録日時が新しい順"
 | 
			
		||||
    notesAsc: "投稿が少ない順"
 | 
			
		||||
    notesDesc: "投稿が多い順"
 | 
			
		||||
    usersAsc: "ユーザーが少ない順"
 | 
			
		||||
    usersDesc: "ユーザーが多い順"
 | 
			
		||||
    followingAsc: "フォローが少ない順"
 | 
			
		||||
    followingDesc: "フォローが多い順"
 | 
			
		||||
    followersAsc: "フォロワーが少ない順"
 | 
			
		||||
    followersDesc: "フォロワーが多い順"
 | 
			
		||||
  state: "状態"
 | 
			
		||||
  states:
 | 
			
		||||
    all: "すべて"
 | 
			
		||||
    blocked: "ブロック"
 | 
			
		||||
  result-is-truncated: "上位{n}件を表示しています。"
 | 
			
		||||
desktop/views/pages/welcome.vue:
 | 
			
		||||
  about: "もうちょい……"
 | 
			
		||||
  gotit: "ほい"
 | 
			
		||||
 
 | 
			
		||||
@@ -510,7 +510,11 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  email-verified: "매일 주소가 확인되었습니다"
 | 
			
		||||
  email-not-verified: "메일 주소가 확인되지 않았습니다. 받은 편지함을 확인하여 주시기 바랍니다."
 | 
			
		||||
  export: "エクスポート"
 | 
			
		||||
  export-notes: "すべての投稿のエクスポート"
 | 
			
		||||
  export-targets:
 | 
			
		||||
    all-notes: "すべての投稿データ"
 | 
			
		||||
    following-list: "フォロー"
 | 
			
		||||
    mute-list: "ミュート"
 | 
			
		||||
    blocking-list: "ブロック"
 | 
			
		||||
  export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "사용자"
 | 
			
		||||
@@ -1003,6 +1007,7 @@ admin/views/index.vue:
 | 
			
		||||
  announcements: "공지사항"
 | 
			
		||||
  hashtags: "해시태그"
 | 
			
		||||
  abuse: "스팸 신고"
 | 
			
		||||
  queue: "ジョブキュー"
 | 
			
		||||
  back-to-misskey: "Misskey로 돌아가기"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "대시보드"
 | 
			
		||||
@@ -1012,6 +1017,9 @@ admin/views/dashboard.vue:
 | 
			
		||||
  instances: "인스턴스"
 | 
			
		||||
  this-instance: "이 인스턴스"
 | 
			
		||||
  federated: "연합"
 | 
			
		||||
admin/views/queue.vue:
 | 
			
		||||
  operation: "操作"
 | 
			
		||||
  remove-all-jobs: "すべてのジョブをクリア"
 | 
			
		||||
admin/views/abuse.vue:
 | 
			
		||||
  title: "스팸 신고"
 | 
			
		||||
  target: "대상"
 | 
			
		||||
@@ -1225,6 +1233,39 @@ admin/views/announcements.vue:
 | 
			
		||||
    removed: "삭제하였습니다"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
admin/views/federation.vue:
 | 
			
		||||
  federation: "連合"
 | 
			
		||||
  host: "ホスト"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  following: "フォロー中"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
  status: "ステータス"
 | 
			
		||||
  latest-request-sent-at: "直近のリクエスト送信"
 | 
			
		||||
  latest-request-received-at: "直近のリクエスト受信"
 | 
			
		||||
  remove-all-following: "フォローを全解除"
 | 
			
		||||
  remove-all-following-info: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  lookup: "照会"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  instance-not-registered: "そのインスタンスは登録されていません"
 | 
			
		||||
  sort: "ソート"
 | 
			
		||||
  sorts:
 | 
			
		||||
    caughtAtAsc: "登録日時が古い順"
 | 
			
		||||
    caughtAtDesc: "登録日時が新しい順"
 | 
			
		||||
    notesAsc: "投稿が少ない順"
 | 
			
		||||
    notesDesc: "投稿が多い順"
 | 
			
		||||
    usersAsc: "ユーザーが少ない順"
 | 
			
		||||
    usersDesc: "ユーザーが多い順"
 | 
			
		||||
    followingAsc: "フォローが少ない順"
 | 
			
		||||
    followingDesc: "フォローが多い順"
 | 
			
		||||
    followersAsc: "フォロワーが少ない順"
 | 
			
		||||
    followersDesc: "フォロワーが多い順"
 | 
			
		||||
  state: "状態"
 | 
			
		||||
  states:
 | 
			
		||||
    all: "すべて"
 | 
			
		||||
    blocked: "ブロック"
 | 
			
		||||
  result-is-truncated: "上位{n}件を表示しています。"
 | 
			
		||||
desktop/views/pages/welcome.vue:
 | 
			
		||||
  about: "자세히..."
 | 
			
		||||
  gotit: "알겠습니다"
 | 
			
		||||
 
 | 
			
		||||
@@ -510,7 +510,11 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  email-verified: "メールアドレスが確認されました"
 | 
			
		||||
  email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。"
 | 
			
		||||
  export: "エクスポート"
 | 
			
		||||
  export-notes: "すべての投稿のエクスポート"
 | 
			
		||||
  export-targets:
 | 
			
		||||
    all-notes: "すべての投稿データ"
 | 
			
		||||
    following-list: "フォロー"
 | 
			
		||||
    mute-list: "ミュート"
 | 
			
		||||
    blocking-list: "ブロック"
 | 
			
		||||
  export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
@@ -1003,6 +1007,7 @@ admin/views/index.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
  abuse: "スパム報告"
 | 
			
		||||
  queue: "ジョブキュー"
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
@@ -1012,6 +1017,9 @@ admin/views/dashboard.vue:
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
admin/views/queue.vue:
 | 
			
		||||
  operation: "操作"
 | 
			
		||||
  remove-all-jobs: "すべてのジョブをクリア"
 | 
			
		||||
admin/views/abuse.vue:
 | 
			
		||||
  title: "スパム報告"
 | 
			
		||||
  target: "対象"
 | 
			
		||||
@@ -1225,6 +1233,39 @@ admin/views/announcements.vue:
 | 
			
		||||
    removed: "削除しました"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
admin/views/federation.vue:
 | 
			
		||||
  federation: "連合"
 | 
			
		||||
  host: "ホスト"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  following: "フォロー中"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
  status: "ステータス"
 | 
			
		||||
  latest-request-sent-at: "直近のリクエスト送信"
 | 
			
		||||
  latest-request-received-at: "直近のリクエスト受信"
 | 
			
		||||
  remove-all-following: "フォローを全解除"
 | 
			
		||||
  remove-all-following-info: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  lookup: "照会"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  instance-not-registered: "そのインスタンスは登録されていません"
 | 
			
		||||
  sort: "ソート"
 | 
			
		||||
  sorts:
 | 
			
		||||
    caughtAtAsc: "登録日時が古い順"
 | 
			
		||||
    caughtAtDesc: "登録日時が新しい順"
 | 
			
		||||
    notesAsc: "投稿が少ない順"
 | 
			
		||||
    notesDesc: "投稿が多い順"
 | 
			
		||||
    usersAsc: "ユーザーが少ない順"
 | 
			
		||||
    usersDesc: "ユーザーが多い順"
 | 
			
		||||
    followingAsc: "フォローが少ない順"
 | 
			
		||||
    followingDesc: "フォローが多い順"
 | 
			
		||||
    followersAsc: "フォロワーが少ない順"
 | 
			
		||||
    followersDesc: "フォロワーが多い順"
 | 
			
		||||
  state: "状態"
 | 
			
		||||
  states:
 | 
			
		||||
    all: "すべて"
 | 
			
		||||
    blocked: "ブロック"
 | 
			
		||||
  result-is-truncated: "上位{n}件を表示しています。"
 | 
			
		||||
desktop/views/pages/welcome.vue:
 | 
			
		||||
  about: "詳しく..."
 | 
			
		||||
  gotit: "わかった"
 | 
			
		||||
 
 | 
			
		||||
@@ -510,7 +510,11 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  email-verified: "メールアドレスが確認されました"
 | 
			
		||||
  email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。"
 | 
			
		||||
  export: "エクスポート"
 | 
			
		||||
  export-notes: "すべての投稿のエクスポート"
 | 
			
		||||
  export-targets:
 | 
			
		||||
    all-notes: "すべての投稿データ"
 | 
			
		||||
    following-list: "フォロー"
 | 
			
		||||
    mute-list: "ミュート"
 | 
			
		||||
    blocking-list: "ブロック"
 | 
			
		||||
  export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
@@ -1003,6 +1007,7 @@ admin/views/index.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
  abuse: "スパム報告"
 | 
			
		||||
  queue: "ジョブキュー"
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
@@ -1012,6 +1017,9 @@ admin/views/dashboard.vue:
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
admin/views/queue.vue:
 | 
			
		||||
  operation: "操作"
 | 
			
		||||
  remove-all-jobs: "すべてのジョブをクリア"
 | 
			
		||||
admin/views/abuse.vue:
 | 
			
		||||
  title: "スパム報告"
 | 
			
		||||
  target: "対象"
 | 
			
		||||
@@ -1225,6 +1233,39 @@ admin/views/announcements.vue:
 | 
			
		||||
    removed: "削除しました"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
admin/views/federation.vue:
 | 
			
		||||
  federation: "連合"
 | 
			
		||||
  host: "ホスト"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  following: "フォロー中"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
  status: "ステータス"
 | 
			
		||||
  latest-request-sent-at: "直近のリクエスト送信"
 | 
			
		||||
  latest-request-received-at: "直近のリクエスト受信"
 | 
			
		||||
  remove-all-following: "フォローを全解除"
 | 
			
		||||
  remove-all-following-info: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  lookup: "照会"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  instance-not-registered: "そのインスタンスは登録されていません"
 | 
			
		||||
  sort: "ソート"
 | 
			
		||||
  sorts:
 | 
			
		||||
    caughtAtAsc: "登録日時が古い順"
 | 
			
		||||
    caughtAtDesc: "登録日時が新しい順"
 | 
			
		||||
    notesAsc: "投稿が少ない順"
 | 
			
		||||
    notesDesc: "投稿が多い順"
 | 
			
		||||
    usersAsc: "ユーザーが少ない順"
 | 
			
		||||
    usersDesc: "ユーザーが多い順"
 | 
			
		||||
    followingAsc: "フォローが少ない順"
 | 
			
		||||
    followingDesc: "フォローが多い順"
 | 
			
		||||
    followersAsc: "フォロワーが少ない順"
 | 
			
		||||
    followersDesc: "フォロワーが多い順"
 | 
			
		||||
  state: "状態"
 | 
			
		||||
  states:
 | 
			
		||||
    all: "すべて"
 | 
			
		||||
    blocked: "ブロック"
 | 
			
		||||
  result-is-truncated: "上位{n}件を表示しています。"
 | 
			
		||||
desktop/views/pages/welcome.vue:
 | 
			
		||||
  about: "詳しく..."
 | 
			
		||||
  gotit: "Skjønner!"
 | 
			
		||||
 
 | 
			
		||||
@@ -510,7 +510,11 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  email-verified: "Twój adres e-mail został zweryfikowany."
 | 
			
		||||
  email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。"
 | 
			
		||||
  export: "エクスポート"
 | 
			
		||||
  export-notes: "すべての投稿のエクスポート"
 | 
			
		||||
  export-targets:
 | 
			
		||||
    all-notes: "すべての投稿データ"
 | 
			
		||||
    following-list: "フォロー"
 | 
			
		||||
    mute-list: "ミュート"
 | 
			
		||||
    blocking-list: "ブロック"
 | 
			
		||||
  export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "Użytkownicy"
 | 
			
		||||
@@ -1003,6 +1007,7 @@ admin/views/index.vue:
 | 
			
		||||
  announcements: "Ogłoszenia"
 | 
			
		||||
  hashtags: "Hashtagi"
 | 
			
		||||
  abuse: "スパム報告"
 | 
			
		||||
  queue: "ジョブキュー"
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
@@ -1012,6 +1017,9 @@ admin/views/dashboard.vue:
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
admin/views/queue.vue:
 | 
			
		||||
  operation: "操作"
 | 
			
		||||
  remove-all-jobs: "すべてのジョブをクリア"
 | 
			
		||||
admin/views/abuse.vue:
 | 
			
		||||
  title: "スパム報告"
 | 
			
		||||
  target: "対象"
 | 
			
		||||
@@ -1225,6 +1233,39 @@ admin/views/announcements.vue:
 | 
			
		||||
    removed: "Usunięto"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
admin/views/federation.vue:
 | 
			
		||||
  federation: "連合"
 | 
			
		||||
  host: "ホスト"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  following: "フォロー中"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
  status: "ステータス"
 | 
			
		||||
  latest-request-sent-at: "直近のリクエスト送信"
 | 
			
		||||
  latest-request-received-at: "直近のリクエスト受信"
 | 
			
		||||
  remove-all-following: "フォローを全解除"
 | 
			
		||||
  remove-all-following-info: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  lookup: "照会"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  instance-not-registered: "そのインスタンスは登録されていません"
 | 
			
		||||
  sort: "ソート"
 | 
			
		||||
  sorts:
 | 
			
		||||
    caughtAtAsc: "登録日時が古い順"
 | 
			
		||||
    caughtAtDesc: "登録日時が新しい順"
 | 
			
		||||
    notesAsc: "投稿が少ない順"
 | 
			
		||||
    notesDesc: "投稿が多い順"
 | 
			
		||||
    usersAsc: "ユーザーが少ない順"
 | 
			
		||||
    usersDesc: "ユーザーが多い順"
 | 
			
		||||
    followingAsc: "フォローが少ない順"
 | 
			
		||||
    followingDesc: "フォローが多い順"
 | 
			
		||||
    followersAsc: "フォロワーが少ない順"
 | 
			
		||||
    followersDesc: "フォロワーが多い順"
 | 
			
		||||
  state: "状態"
 | 
			
		||||
  states:
 | 
			
		||||
    all: "すべて"
 | 
			
		||||
    blocked: "ブロック"
 | 
			
		||||
  result-is-truncated: "上位{n}件を表示しています。"
 | 
			
		||||
desktop/views/pages/welcome.vue:
 | 
			
		||||
  about: "O Misskey"
 | 
			
		||||
  gotit: "Rozumiem!"
 | 
			
		||||
 
 | 
			
		||||
@@ -510,7 +510,11 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  email-verified: "メールアドレスが確認されました"
 | 
			
		||||
  email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。"
 | 
			
		||||
  export: "エクスポート"
 | 
			
		||||
  export-notes: "すべての投稿のエクスポート"
 | 
			
		||||
  export-targets:
 | 
			
		||||
    all-notes: "すべての投稿データ"
 | 
			
		||||
    following-list: "フォロー"
 | 
			
		||||
    mute-list: "ミュート"
 | 
			
		||||
    blocking-list: "ブロック"
 | 
			
		||||
  export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
@@ -1003,6 +1007,7 @@ admin/views/index.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
  abuse: "スパム報告"
 | 
			
		||||
  queue: "ジョブキュー"
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
@@ -1012,6 +1017,9 @@ admin/views/dashboard.vue:
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
admin/views/queue.vue:
 | 
			
		||||
  operation: "操作"
 | 
			
		||||
  remove-all-jobs: "すべてのジョブをクリア"
 | 
			
		||||
admin/views/abuse.vue:
 | 
			
		||||
  title: "スパム報告"
 | 
			
		||||
  target: "対象"
 | 
			
		||||
@@ -1225,6 +1233,39 @@ admin/views/announcements.vue:
 | 
			
		||||
    removed: "削除しました"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
admin/views/federation.vue:
 | 
			
		||||
  federation: "連合"
 | 
			
		||||
  host: "ホスト"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  following: "フォロー中"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
  status: "ステータス"
 | 
			
		||||
  latest-request-sent-at: "直近のリクエスト送信"
 | 
			
		||||
  latest-request-received-at: "直近のリクエスト受信"
 | 
			
		||||
  remove-all-following: "フォローを全解除"
 | 
			
		||||
  remove-all-following-info: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  lookup: "照会"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  instance-not-registered: "そのインスタンスは登録されていません"
 | 
			
		||||
  sort: "ソート"
 | 
			
		||||
  sorts:
 | 
			
		||||
    caughtAtAsc: "登録日時が古い順"
 | 
			
		||||
    caughtAtDesc: "登録日時が新しい順"
 | 
			
		||||
    notesAsc: "投稿が少ない順"
 | 
			
		||||
    notesDesc: "投稿が多い順"
 | 
			
		||||
    usersAsc: "ユーザーが少ない順"
 | 
			
		||||
    usersDesc: "ユーザーが多い順"
 | 
			
		||||
    followingAsc: "フォローが少ない順"
 | 
			
		||||
    followingDesc: "フォローが多い順"
 | 
			
		||||
    followersAsc: "フォロワーが少ない順"
 | 
			
		||||
    followersDesc: "フォロワーが多い順"
 | 
			
		||||
  state: "状態"
 | 
			
		||||
  states:
 | 
			
		||||
    all: "すべて"
 | 
			
		||||
    blocked: "ブロック"
 | 
			
		||||
  result-is-truncated: "上位{n}件を表示しています。"
 | 
			
		||||
desktop/views/pages/welcome.vue:
 | 
			
		||||
  about: "詳しく..."
 | 
			
		||||
  gotit: "わかった"
 | 
			
		||||
 
 | 
			
		||||
@@ -510,7 +510,11 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  email-verified: "メールアドレスが確認されました"
 | 
			
		||||
  email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。"
 | 
			
		||||
  export: "エクスポート"
 | 
			
		||||
  export-notes: "すべての投稿のエクスポート"
 | 
			
		||||
  export-targets:
 | 
			
		||||
    all-notes: "すべての投稿データ"
 | 
			
		||||
    following-list: "フォロー"
 | 
			
		||||
    mute-list: "ミュート"
 | 
			
		||||
    blocking-list: "ブロック"
 | 
			
		||||
  export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
@@ -1003,6 +1007,7 @@ admin/views/index.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  hashtags: "ハッシュタグ"
 | 
			
		||||
  abuse: "スパム報告"
 | 
			
		||||
  queue: "ジョブキュー"
 | 
			
		||||
  back-to-misskey: "Misskeyに戻る"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "ダッシュボード"
 | 
			
		||||
@@ -1012,6 +1017,9 @@ admin/views/dashboard.vue:
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  this-instance: "このインスタンス"
 | 
			
		||||
  federated: "連合"
 | 
			
		||||
admin/views/queue.vue:
 | 
			
		||||
  operation: "操作"
 | 
			
		||||
  remove-all-jobs: "すべてのジョブをクリア"
 | 
			
		||||
admin/views/abuse.vue:
 | 
			
		||||
  title: "スパム報告"
 | 
			
		||||
  target: "対象"
 | 
			
		||||
@@ -1225,6 +1233,39 @@ admin/views/announcements.vue:
 | 
			
		||||
    removed: "削除しました"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
admin/views/federation.vue:
 | 
			
		||||
  federation: "連合"
 | 
			
		||||
  host: "ホスト"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  following: "フォロー中"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
  status: "ステータス"
 | 
			
		||||
  latest-request-sent-at: "直近のリクエスト送信"
 | 
			
		||||
  latest-request-received-at: "直近のリクエスト受信"
 | 
			
		||||
  remove-all-following: "フォローを全解除"
 | 
			
		||||
  remove-all-following-info: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  lookup: "照会"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  instance-not-registered: "そのインスタンスは登録されていません"
 | 
			
		||||
  sort: "ソート"
 | 
			
		||||
  sorts:
 | 
			
		||||
    caughtAtAsc: "登録日時が古い順"
 | 
			
		||||
    caughtAtDesc: "登録日時が新しい順"
 | 
			
		||||
    notesAsc: "投稿が少ない順"
 | 
			
		||||
    notesDesc: "投稿が多い順"
 | 
			
		||||
    usersAsc: "ユーザーが少ない順"
 | 
			
		||||
    usersDesc: "ユーザーが多い順"
 | 
			
		||||
    followingAsc: "フォローが少ない順"
 | 
			
		||||
    followingDesc: "フォローが多い順"
 | 
			
		||||
    followersAsc: "フォロワーが少ない順"
 | 
			
		||||
    followersDesc: "フォロワーが多い順"
 | 
			
		||||
  state: "状態"
 | 
			
		||||
  states:
 | 
			
		||||
    all: "すべて"
 | 
			
		||||
    blocked: "ブロック"
 | 
			
		||||
  result-is-truncated: "上位{n}件を表示しています。"
 | 
			
		||||
desktop/views/pages/welcome.vue:
 | 
			
		||||
  about: "詳しく..."
 | 
			
		||||
  gotit: "わかった"
 | 
			
		||||
 
 | 
			
		||||
@@ -354,10 +354,10 @@ common/views/components/note-menu.vue:
 | 
			
		||||
  remote: "显示原始投稿"
 | 
			
		||||
common/views/components/user-menu.vue:
 | 
			
		||||
  mention: "提到"
 | 
			
		||||
  mute: "免打扰"
 | 
			
		||||
  unmute: "解除免打扰"
 | 
			
		||||
  block: "屏蔽"
 | 
			
		||||
  unblock: "取消屏蔽"
 | 
			
		||||
  mute: "屏蔽"
 | 
			
		||||
  unmute: "解除屏蔽"
 | 
			
		||||
  block: "拉黑"
 | 
			
		||||
  unblock: "取消拉黑"
 | 
			
		||||
  push-to-list: "添加至列表"
 | 
			
		||||
  select-list: "请选择一个列表"
 | 
			
		||||
  report-abuse: "举报骚扰"
 | 
			
		||||
@@ -510,7 +510,11 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  email-verified: "电子邮件地址已验证"
 | 
			
		||||
  email-not-verified: "邮件地址尚未验证。 请检查您的邮箱。"
 | 
			
		||||
  export: "导出"
 | 
			
		||||
  export-notes: "导出所有帖子"
 | 
			
		||||
  export-targets:
 | 
			
		||||
    all-notes: "所有发帖"
 | 
			
		||||
    following-list: "关注列表"
 | 
			
		||||
    mute-list: "屏蔽列表"
 | 
			
		||||
    blocking-list: "黑名单"
 | 
			
		||||
  export-requested: "导出请求已提交。可能需要花一些时间。导出的文件将保存到网盘中。"
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "用户"
 | 
			
		||||
@@ -776,8 +780,8 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  notification: "通知"
 | 
			
		||||
  apps: "应用程序"
 | 
			
		||||
  tags: "标签"
 | 
			
		||||
  mute-and-block: "静音/屏蔽"
 | 
			
		||||
  blocking: "屏蔽中"
 | 
			
		||||
  mute-and-block: "屏蔽/拉黑"
 | 
			
		||||
  blocking: "已拉黑"
 | 
			
		||||
  security: "安全性"
 | 
			
		||||
  signin: "登录历史"
 | 
			
		||||
  password: "密码"
 | 
			
		||||
@@ -908,13 +912,13 @@ common/views/components/drive-settings.vue:
 | 
			
		||||
  in-use: "正在使用"
 | 
			
		||||
  stats: "统计"
 | 
			
		||||
common/views/components/mute-and-block.vue:
 | 
			
		||||
  mute-and-block: "静音/封锁"
 | 
			
		||||
  mute: "静音"
 | 
			
		||||
  block: "封锁中"
 | 
			
		||||
  no-muted-users: "没有静音的用户"
 | 
			
		||||
  no-blocked-users: "没有封锁的用户"
 | 
			
		||||
  word-mute: "文字静音"
 | 
			
		||||
  muted-words: "静音的关键字"
 | 
			
		||||
  mute-and-block: "屏蔽/拉黑"
 | 
			
		||||
  mute: "屏蔽"
 | 
			
		||||
  block: "拉黑中"
 | 
			
		||||
  no-muted-users: "无屏蔽用户"
 | 
			
		||||
  no-blocked-users: "无拉黑的用户"
 | 
			
		||||
  word-mute: "文字屏蔽"
 | 
			
		||||
  muted-words: "屏蔽关键字"
 | 
			
		||||
  muted-words-description: "使用空格分隔会产生AND规范,并且使用换行符分隔会产生OR规范"
 | 
			
		||||
  save: "保存"
 | 
			
		||||
common/views/components/password-settings.vue:
 | 
			
		||||
@@ -1003,6 +1007,7 @@ admin/views/index.vue:
 | 
			
		||||
  announcements: "公告"
 | 
			
		||||
  hashtags: "标签"
 | 
			
		||||
  abuse: "举报垃圾信息"
 | 
			
		||||
  queue: "作业队列"
 | 
			
		||||
  back-to-misskey: "返回 Misskey"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "Dashboard"
 | 
			
		||||
@@ -1012,6 +1017,9 @@ admin/views/dashboard.vue:
 | 
			
		||||
  instances: "例子"
 | 
			
		||||
  this-instance: "此实例"
 | 
			
		||||
  federated: "联合"
 | 
			
		||||
admin/views/queue.vue:
 | 
			
		||||
  operation: "操作"
 | 
			
		||||
  remove-all-jobs: "清除所有作业"
 | 
			
		||||
admin/views/abuse.vue:
 | 
			
		||||
  title: "举报垃圾信息"
 | 
			
		||||
  target: "目标"
 | 
			
		||||
@@ -1225,6 +1233,39 @@ admin/views/announcements.vue:
 | 
			
		||||
    removed: "已删除"
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "隐藏标签"
 | 
			
		||||
admin/views/federation.vue:
 | 
			
		||||
  federation: "联合"
 | 
			
		||||
  host: "主机名"
 | 
			
		||||
  notes: "帖子"
 | 
			
		||||
  users: "用户"
 | 
			
		||||
  following: "正在关注"
 | 
			
		||||
  followers: "关注者"
 | 
			
		||||
  status: "状态"
 | 
			
		||||
  latest-request-sent-at: "上次发送的请求"
 | 
			
		||||
  latest-request-received-at: "上次收到的请求"
 | 
			
		||||
  remove-all-following: "取消所有关注"
 | 
			
		||||
  remove-all-following-info: "取消{host}的所有关注者。当实例不存在时执行。"
 | 
			
		||||
  block: "拉黑"
 | 
			
		||||
  lookup: "查询"
 | 
			
		||||
  instances: "实例"
 | 
			
		||||
  instance-not-registered: "实例未注册"
 | 
			
		||||
  sort: "排序"
 | 
			
		||||
  sorts:
 | 
			
		||||
    caughtAtAsc: "注册时间从旧到新"
 | 
			
		||||
    caughtAtDesc: "注册时间从新到旧"
 | 
			
		||||
    notesAsc: "发帖数量从少到多"
 | 
			
		||||
    notesDesc: "发帖数量从多到少"
 | 
			
		||||
    usersAsc: "用户数从少到多"
 | 
			
		||||
    usersDesc: "用户数从多到少"
 | 
			
		||||
    followingAsc: "关注数从少到多"
 | 
			
		||||
    followingDesc: "关注数从多到少"
 | 
			
		||||
    followersAsc: "粉丝数从少到多"
 | 
			
		||||
    followersDesc: "粉丝数从多到少"
 | 
			
		||||
  state: "状态"
 | 
			
		||||
  states:
 | 
			
		||||
    all: "所有"
 | 
			
		||||
    blocked: "已拉黑"
 | 
			
		||||
  result-is-truncated: "显示最前面的{n}项。"
 | 
			
		||||
desktop/views/pages/welcome.vue:
 | 
			
		||||
  about: "更多信息..."
 | 
			
		||||
  gotit: "没问题! "
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								package.json
									
									
									
									
									
								
							@@ -1,8 +1,8 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "misskey",
 | 
			
		||||
	"author": "syuilo <i@syuilo.com>",
 | 
			
		||||
	"version": "10.82.1",
 | 
			
		||||
	"clientVersion": "2.0.14137",
 | 
			
		||||
	"version": "10.83.0",
 | 
			
		||||
	"clientVersion": "2.0.14211",
 | 
			
		||||
	"codename": "nighthike",
 | 
			
		||||
	"repository": {
 | 
			
		||||
		"type": "git",
 | 
			
		||||
@@ -28,7 +28,7 @@
 | 
			
		||||
	},
 | 
			
		||||
	"dependencies": {
 | 
			
		||||
		"@fortawesome/fontawesome-svg-core": "1.2.14",
 | 
			
		||||
		"@fortawesome/free-brands-svg-icons": "5.6.3",
 | 
			
		||||
		"@fortawesome/free-brands-svg-icons": "5.7.1",
 | 
			
		||||
		"@fortawesome/free-regular-svg-icons": "5.7.0",
 | 
			
		||||
		"@fortawesome/free-solid-svg-icons": "5.6.3",
 | 
			
		||||
		"@fortawesome/vue-fontawesome": "0.1.5",
 | 
			
		||||
@@ -99,7 +99,7 @@
 | 
			
		||||
		"@types/websocket": "0.0.40",
 | 
			
		||||
		"@types/ws": "6.0.1",
 | 
			
		||||
		"animejs": "3.0.1",
 | 
			
		||||
		"apexcharts": "3.2.1",
 | 
			
		||||
		"apexcharts": "3.2.2",
 | 
			
		||||
		"autobind-decorator": "2.4.0",
 | 
			
		||||
		"autosize": "4.0.2",
 | 
			
		||||
		"autwh": "0.1.0",
 | 
			
		||||
@@ -223,7 +223,7 @@
 | 
			
		||||
		"tmp": "0.0.33",
 | 
			
		||||
		"ts-loader": "5.3.3",
 | 
			
		||||
		"ts-node": "7.0.1",
 | 
			
		||||
		"tslint": "5.12.0",
 | 
			
		||||
		"tslint": "5.12.1",
 | 
			
		||||
		"tslint-sonarts": "1.9.0",
 | 
			
		||||
		"typescript": "3.2.4",
 | 
			
		||||
		"typescript-eslint-parser": "21.0.2",
 | 
			
		||||
@@ -232,7 +232,7 @@
 | 
			
		||||
		"uuid": "3.3.2",
 | 
			
		||||
		"v-animate-css": "0.0.3",
 | 
			
		||||
		"video-thumbnail-generator": "1.1.3",
 | 
			
		||||
		"vue": "2.6.2",
 | 
			
		||||
		"vue": "2.6.3",
 | 
			
		||||
		"vue-color": "2.7.0",
 | 
			
		||||
		"vue-content-loading": "1.5.3",
 | 
			
		||||
		"vue-cropperjs": "3.0.0",
 | 
			
		||||
@@ -245,7 +245,7 @@
 | 
			
		||||
		"vue-sequential-entrance": "1.1.3",
 | 
			
		||||
		"vue-style-loader": "4.1.2",
 | 
			
		||||
		"vue-svg-inline-loader": "1.2.10",
 | 
			
		||||
		"vue-template-compiler": "2.6.2",
 | 
			
		||||
		"vue-template-compiler": "2.6.3",
 | 
			
		||||
		"vuedraggable": "2.17.0",
 | 
			
		||||
		"vuewordcloud": "18.7.11",
 | 
			
		||||
		"vuex": "3.1.0",
 | 
			
		||||
 
 | 
			
		||||
@@ -5,14 +5,17 @@ program
 | 
			
		||||
	.version(pkg.version)
 | 
			
		||||
	.option('--no-daemons', 'Disable daemon processes (for debbuging)')
 | 
			
		||||
	.option('--disable-clustering', 'Disable clustering')
 | 
			
		||||
	.option('--disable-ap-queue', 'Disable creating job queue related to ap')
 | 
			
		||||
	.option('--disable-queue', 'Disable job queue processing')
 | 
			
		||||
	.option('--only-queue', 'Pocessing job queue only')
 | 
			
		||||
	.option('--quiet', 'Suppress all logs')
 | 
			
		||||
	.option('--verbose', 'Enable all logs')
 | 
			
		||||
	.option('--with-log-time', 'Include timestamp for each logs')
 | 
			
		||||
	.option('--slow', 'Delay all requests (for debbuging)')
 | 
			
		||||
	.option('--color', 'This option is a dummy for some external program\'s (e.g. forever) issue.')
 | 
			
		||||
	.parse(process.argv);
 | 
			
		||||
 | 
			
		||||
/*if (process.env.MK_DISABLE_AP_QUEUE)*/ program.disableApQueue = true;
 | 
			
		||||
if (process.env.MK_DISABLE_QUEUE) program.disableQueue = true;
 | 
			
		||||
if (process.env.MK_ONLY_QUEUE) program.onlyQueue = true;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -124,7 +124,7 @@ export default Vue.extend({
 | 
			
		||||
			this.meta = meta;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.$root.api('instances', {
 | 
			
		||||
		this.$root.api('federation/instances', {
 | 
			
		||||
			sort: '+notes'
 | 
			
		||||
		}).then(instances => {
 | 
			
		||||
			for (const i of instances) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										477
									
								
								src/client/app/admin/views/federation.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										477
									
								
								src/client/app/admin/views/federation.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,477 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div>
 | 
			
		||||
	<ui-card>
 | 
			
		||||
		<div slot="title"><fa :icon="faTerminal"/> {{ $t('federation') }}</div>
 | 
			
		||||
		<section class="fit-top">
 | 
			
		||||
			<ui-input class="target" v-model="target" type="text" @enter="showInstance">
 | 
			
		||||
				<span>{{ $t('host') }}</span>
 | 
			
		||||
			</ui-input>
 | 
			
		||||
			<ui-button @click="showInstance"><fa :icon="faSearch"/> {{ $t('lookup') }}</ui-button>
 | 
			
		||||
 | 
			
		||||
			<div class="instance" v-if="instance">
 | 
			
		||||
				<ui-input :value="instance.host" type="text" readonly>
 | 
			
		||||
					<span>{{ $t('host') }}</span>
 | 
			
		||||
				</ui-input>
 | 
			
		||||
				<ui-horizon-group inputs>
 | 
			
		||||
					<ui-input :value="instance.notesCount | number" type="text" readonly>
 | 
			
		||||
						<span>{{ $t('notes') }}</span>
 | 
			
		||||
					</ui-input>
 | 
			
		||||
					<ui-input :value="instance.usersCount | number" type="text" readonly>
 | 
			
		||||
						<span>{{ $t('users') }}</span>
 | 
			
		||||
					</ui-input>
 | 
			
		||||
				</ui-horizon-group>
 | 
			
		||||
				<ui-horizon-group inputs>
 | 
			
		||||
					<ui-input :value="instance.followingCount | number" type="text" readonly>
 | 
			
		||||
						<span>{{ $t('following') }}</span>
 | 
			
		||||
					</ui-input>
 | 
			
		||||
					<ui-input :value="instance.followersCount | number" type="text" readonly>
 | 
			
		||||
						<span>{{ $t('followers') }}</span>
 | 
			
		||||
					</ui-input>
 | 
			
		||||
				</ui-horizon-group>
 | 
			
		||||
				<ui-horizon-group inputs>
 | 
			
		||||
					<ui-input :value="instance.latestRequestSentAt" type="text" readonly>
 | 
			
		||||
						<span>{{ $t('latest-request-sent-at') }}</span>
 | 
			
		||||
					</ui-input>
 | 
			
		||||
					<ui-input :value="instance.latestStatus" type="text" readonly>
 | 
			
		||||
						<span>{{ $t('status') }}</span>
 | 
			
		||||
					</ui-input>
 | 
			
		||||
				</ui-horizon-group>
 | 
			
		||||
				<ui-input :value="instance.latestRequestReceivedAt" type="text" readonly>
 | 
			
		||||
					<span>{{ $t('latest-request-received-at') }}</span>
 | 
			
		||||
				</ui-input>
 | 
			
		||||
				<ui-switch v-model="instance.isBlocked" @change="updateInstance()">{{ $t('block') }}</ui-switch>
 | 
			
		||||
				<details>
 | 
			
		||||
					<summary>{{ $t('charts') }}</summary>
 | 
			
		||||
					<ui-horizon-group inputs>
 | 
			
		||||
						<ui-select v-model="chartSrc">
 | 
			
		||||
							<option value="requests">{{ $t('chart-srcs.requests') }}</option>
 | 
			
		||||
							<option value="users">{{ $t('chart-srcs.users') }}</option>
 | 
			
		||||
							<option value="users-total">{{ $t('chart-srcs.users-total') }}</option>
 | 
			
		||||
							<option value="notes">{{ $t('chart-srcs.notes') }}</option>
 | 
			
		||||
							<option value="notes-total">{{ $t('chart-srcs.notes-total') }}</option>
 | 
			
		||||
							<option value="ff">{{ $t('chart-srcs.ff') }}</option>
 | 
			
		||||
							<option value="ff-total">{{ $t('chart-srcs.ff-total') }}</option>
 | 
			
		||||
							<option value="drive-usage">{{ $t('chart-srcs.drive-usage') }}</option>
 | 
			
		||||
							<option value="drive-usage-total">{{ $t('chart-srcs.drive-usage-total') }}</option>
 | 
			
		||||
							<option value="drive-files">{{ $t('chart-srcs.drive-files') }}</option>
 | 
			
		||||
							<option value="drive-files-total">{{ $t('chart-srcs.drive-files-total') }}</option>
 | 
			
		||||
						</ui-select>
 | 
			
		||||
						<ui-select v-model="chartSpan">
 | 
			
		||||
							<option value="hour">{{ $t('chart-spans.hour') }}</option>
 | 
			
		||||
							<option value="day">{{ $t('chart-spans.day') }}</option>
 | 
			
		||||
						</ui-select>
 | 
			
		||||
					</ui-horizon-group>
 | 
			
		||||
					<div ref="chart"></div>
 | 
			
		||||
				</details>
 | 
			
		||||
				<details>
 | 
			
		||||
					<summary>{{ $t('remove-all-following') }}</summary>
 | 
			
		||||
					<ui-button @click="removeAllFollowing()" style="margin-top: 16px;"><fa :icon="faMinusCircle"/> {{ $t('remove-all-following') }}</ui-button>
 | 
			
		||||
					<ui-info warn>{{ $t('remove-all-following-info', { host: instance.host }) }}</ui-info>
 | 
			
		||||
				</details>
 | 
			
		||||
			</div>
 | 
			
		||||
		</section>
 | 
			
		||||
	</ui-card>
 | 
			
		||||
 | 
			
		||||
	<ui-card>
 | 
			
		||||
		<div slot="title"><fa :icon="faServer"/> {{ $t('instances') }}</div>
 | 
			
		||||
		<section class="fit-top">
 | 
			
		||||
			<ui-horizon-group inputs>
 | 
			
		||||
				<ui-select v-model="sort">
 | 
			
		||||
					<span slot="label">{{ $t('sort') }}</span>
 | 
			
		||||
					<option value="-caughtAt">{{ $t('sorts.caughtAtAsc') }}</option>
 | 
			
		||||
					<option value="+caughtAt">{{ $t('sorts.caughtAtDesc') }}</option>
 | 
			
		||||
					<option value="-notes">{{ $t('sorts.notesAsc') }}</option>
 | 
			
		||||
					<option value="+notes">{{ $t('sorts.notesDesc') }}</option>
 | 
			
		||||
					<option value="-users">{{ $t('sorts.usersAsc') }}</option>
 | 
			
		||||
					<option value="+users">{{ $t('sorts.usersDesc') }}</option>
 | 
			
		||||
					<option value="-following">{{ $t('sorts.followingAsc') }}</option>
 | 
			
		||||
					<option value="+following">{{ $t('sorts.followingDesc') }}</option>
 | 
			
		||||
					<option value="-followers">{{ $t('sorts.followersAsc') }}</option>
 | 
			
		||||
					<option value="+followers">{{ $t('sorts.followersDesc') }}</option>
 | 
			
		||||
					<option value="-driveUsage">{{ $t('sorts.driveUsageAsc') }}</option>
 | 
			
		||||
					<option value="+driveUsage">{{ $t('sorts.driveUsageDesc') }}</option>
 | 
			
		||||
					<option value="-driveFiles">{{ $t('sorts.driveFilesAsc') }}</option>
 | 
			
		||||
					<option value="+driveFiles">{{ $t('sorts.driveFilesDesc') }}</option>
 | 
			
		||||
				</ui-select>
 | 
			
		||||
				<ui-select v-model="state">
 | 
			
		||||
					<span slot="label">{{ $t('state') }}</span>
 | 
			
		||||
					<option value="all">{{ $t('states.all') }}</option>
 | 
			
		||||
					<option value="blocked">{{ $t('states.blocked') }}</option>
 | 
			
		||||
				</ui-select>
 | 
			
		||||
			</ui-horizon-group>
 | 
			
		||||
 | 
			
		||||
			<div class="instances">
 | 
			
		||||
				<header>
 | 
			
		||||
					<span>{{ $t('host') }}</span>
 | 
			
		||||
					<span>{{ $t('notes') }}</span>
 | 
			
		||||
					<span>{{ $t('users') }}</span>
 | 
			
		||||
					<span>{{ $t('following') }}</span>
 | 
			
		||||
					<span>{{ $t('followers') }}</span>
 | 
			
		||||
					<span>{{ $t('status') }}</span>
 | 
			
		||||
				</header>
 | 
			
		||||
				<div v-for="instance in instances">
 | 
			
		||||
					<span>{{ instance.host }}</span>
 | 
			
		||||
					<span>{{ instance.notesCount | number }}</span>
 | 
			
		||||
					<span>{{ instance.usersCount | number }}</span>
 | 
			
		||||
					<span>{{ instance.followingCount | number }}</span>
 | 
			
		||||
					<span>{{ instance.followersCount | number }}</span>
 | 
			
		||||
					<span>{{ instance.latestStatus }}</span>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<ui-info v-if="instances.length == limit">{{ $t('result-is-truncated', { n: limit }) }}</ui-info>
 | 
			
		||||
		</section>
 | 
			
		||||
	</ui-card>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../i18n';
 | 
			
		||||
import { faGlobe, faTerminal, faSearch, faMinusCircle, faServer } from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import ApexCharts from 'apexcharts';
 | 
			
		||||
import * as tinycolor from 'tinycolor2';
 | 
			
		||||
 | 
			
		||||
const chartLimit = 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({
 | 
			
		||||
	i18n: i18n('admin/views/federation.vue'),
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			instance: null,
 | 
			
		||||
			target: null,
 | 
			
		||||
			sort: '+caughtAt',
 | 
			
		||||
			state: 'all',
 | 
			
		||||
			limit: 50,
 | 
			
		||||
			instances: [],
 | 
			
		||||
			chart: null,
 | 
			
		||||
			chartSrc: 'requests',
 | 
			
		||||
			chartSpan: 'hour',
 | 
			
		||||
			chartInstance: null,
 | 
			
		||||
			faGlobe, faTerminal, faSearch, faMinusCircle, faServer
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		data(): any {
 | 
			
		||||
			if (this.chart == null) return null;
 | 
			
		||||
			switch (this.chartSrc) {
 | 
			
		||||
				case 'requests': return this.requestsChart();
 | 
			
		||||
				case 'users': return this.usersChart(false);
 | 
			
		||||
				case 'users-total': return this.usersChart(true);
 | 
			
		||||
				case 'notes': return this.notesChart(false);
 | 
			
		||||
				case 'notes-total': return this.notesChart(true);
 | 
			
		||||
				case 'ff': return this.ffChart(false);
 | 
			
		||||
				case 'ff-total': return this.ffChart(true);
 | 
			
		||||
				case 'drive-usage': return this.driveUsageChart(false);
 | 
			
		||||
				case 'drive-usage-total': return this.driveUsageChart(true);
 | 
			
		||||
				case 'drive-files': return this.driveFilesChart(false);
 | 
			
		||||
				case 'drive-files-total': return this.driveFilesChart(true);
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		stats(): any[] {
 | 
			
		||||
			const stats =
 | 
			
		||||
				this.chartSpan == 'day' ? this.chart.perDay :
 | 
			
		||||
				this.chartSpan == 'hour' ? this.chart.perHour :
 | 
			
		||||
				null;
 | 
			
		||||
 | 
			
		||||
			return stats;
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	watch: {
 | 
			
		||||
		sort() {
 | 
			
		||||
			this.fetchInstances();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		state() {
 | 
			
		||||
			this.fetchInstances();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		async instance() {
 | 
			
		||||
			this.now = new Date();
 | 
			
		||||
 | 
			
		||||
			const [perHour, perDay] = await Promise.all([
 | 
			
		||||
				this.$root.api('charts/instance', { host: this.instance.host, limit: chartLimit, span: 'hour' }),
 | 
			
		||||
				this.$root.api('charts/instance', { host: this.instance.host, limit: chartLimit, span: 'day' }),
 | 
			
		||||
			]);
 | 
			
		||||
 | 
			
		||||
			const chart = {
 | 
			
		||||
				perHour: perHour,
 | 
			
		||||
				perDay: perDay
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			this.chart = chart;
 | 
			
		||||
 | 
			
		||||
			this.renderChart();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		chartSrc() {
 | 
			
		||||
			this.renderChart();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		chartSpan() {
 | 
			
		||||
			this.renderChart();
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.fetchInstances();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.chartInstance.destroy();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		showInstance() {
 | 
			
		||||
			this.$root.api('federation/show-instance', {
 | 
			
		||||
				host: this.target
 | 
			
		||||
			}).then(instance => {
 | 
			
		||||
				if (instance == null) {
 | 
			
		||||
					this.$root.dialog({
 | 
			
		||||
						type: 'error',
 | 
			
		||||
						text: this.$t('instance-not-registered')
 | 
			
		||||
					});
 | 
			
		||||
				} else {
 | 
			
		||||
					this.instance = instance;
 | 
			
		||||
					this.target = '';
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		fetchInstances() {
 | 
			
		||||
			this.instances = [];
 | 
			
		||||
			this.$root.api('federation/instances', {
 | 
			
		||||
				state: this.state,
 | 
			
		||||
				sort: this.sort,
 | 
			
		||||
				limit: this.limit
 | 
			
		||||
			}).then(instances => {
 | 
			
		||||
				this.instances = instances;
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		removeAllFollowing() {
 | 
			
		||||
			this.$root.api('admin/federation/remove-all-following', {
 | 
			
		||||
				host: this.instance.host
 | 
			
		||||
			}).then(() => {
 | 
			
		||||
				this.$root.dialog({
 | 
			
		||||
					type: 'success',
 | 
			
		||||
					splash: true
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		updateInstance() {
 | 
			
		||||
			this.$root.api('admin/federation/update-instance', {
 | 
			
		||||
				host: this.instance.host,
 | 
			
		||||
				isBlocked: this.instance.isBlocked,
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		setSrc(src) {
 | 
			
		||||
			this.chartSrc = src;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		renderChart() {
 | 
			
		||||
			if (this.chartInstance) {
 | 
			
		||||
				this.chartInstance.destroy();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			this.chartInstance = new ApexCharts(this.$refs.chart, {
 | 
			
		||||
				chart: {
 | 
			
		||||
					type: 'area',
 | 
			
		||||
					height: 300,
 | 
			
		||||
					animations: {
 | 
			
		||||
						dynamicAnimation: {
 | 
			
		||||
							enabled: false
 | 
			
		||||
						}
 | 
			
		||||
					},
 | 
			
		||||
					toolbar: {
 | 
			
		||||
						show: false
 | 
			
		||||
					},
 | 
			
		||||
					zoom: {
 | 
			
		||||
						enabled: false
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				dataLabels: {
 | 
			
		||||
					enabled: false
 | 
			
		||||
				},
 | 
			
		||||
				grid: {
 | 
			
		||||
					clipMarkers: false,
 | 
			
		||||
					borderColor: 'rgba(0, 0, 0, 0.1)'
 | 
			
		||||
				},
 | 
			
		||||
				stroke: {
 | 
			
		||||
					curve: 'straight',
 | 
			
		||||
					width: 2
 | 
			
		||||
				},
 | 
			
		||||
				legend: {
 | 
			
		||||
					labels: {
 | 
			
		||||
						colors: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString()
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				xaxis: {
 | 
			
		||||
					type: 'datetime',
 | 
			
		||||
					labels: {
 | 
			
		||||
						style: {
 | 
			
		||||
							colors: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString()
 | 
			
		||||
						}
 | 
			
		||||
					},
 | 
			
		||||
					axisBorder: {
 | 
			
		||||
						color: 'rgba(0, 0, 0, 0.1)'
 | 
			
		||||
					},
 | 
			
		||||
					axisTicks: {
 | 
			
		||||
						color: 'rgba(0, 0, 0, 0.1)'
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				yaxis: {
 | 
			
		||||
					labels: {
 | 
			
		||||
						formatter: this.data.bytes ? v => Vue.filter('bytes')(v, 0) : v => Vue.filter('number')(v),
 | 
			
		||||
						style: {
 | 
			
		||||
							color: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString()
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				series: this.data.series
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			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.chartSpan == 'day' ? new Date(y, m, d - i) :
 | 
			
		||||
				this.chartSpan == 'hour' ? new Date(y, m, d, h - i) :
 | 
			
		||||
				null
 | 
			
		||||
			);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		format(arr) {
 | 
			
		||||
			return arr.map((v, i) => ({ x: this.getDate(i).getTime(), y: v }));
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		requestsChart(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Incoming',
 | 
			
		||||
					data: this.format(this.stats.requests.received)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Outgoing (succeeded)',
 | 
			
		||||
					data: this.format(this.stats.requests.succeeded)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Outgoing (failed)',
 | 
			
		||||
					data: this.format(this.stats.requests.failed)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		usersChart(total: boolean): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Users',
 | 
			
		||||
					type: 'area',
 | 
			
		||||
					data: this.format(total
 | 
			
		||||
						? this.stats.users.total
 | 
			
		||||
						: sum(this.stats.users.inc, negate(this.stats.users.dec))
 | 
			
		||||
					)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		notesChart(total: boolean): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Notes',
 | 
			
		||||
					type: 'area',
 | 
			
		||||
					data: this.format(total
 | 
			
		||||
						? this.stats.notes.total
 | 
			
		||||
						: sum(this.stats.notes.inc, negate(this.stats.notes.dec))
 | 
			
		||||
					)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		ffChart(total: boolean): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Following',
 | 
			
		||||
					type: 'area',
 | 
			
		||||
					data: this.format(total
 | 
			
		||||
						? this.stats.following.total
 | 
			
		||||
						: sum(this.stats.following.inc, negate(this.stats.following.dec))
 | 
			
		||||
					)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Followers',
 | 
			
		||||
					type: 'area',
 | 
			
		||||
					data: this.format(total
 | 
			
		||||
						? this.stats.followers.total
 | 
			
		||||
						: sum(this.stats.followers.inc, negate(this.stats.followers.dec))
 | 
			
		||||
					)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		driveUsageChart(total: boolean): any {
 | 
			
		||||
			return {
 | 
			
		||||
				bytes: true,
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Drive usage',
 | 
			
		||||
					type: 'area',
 | 
			
		||||
					data: this.format(total
 | 
			
		||||
						? this.stats.drive.totalUsage
 | 
			
		||||
						: sum(this.stats.drive.incUsage, negate(this.stats.drive.decUsage))
 | 
			
		||||
					)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		driveFilesChart(total: boolean): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Drive files',
 | 
			
		||||
					type: 'area',
 | 
			
		||||
					data: this.format(total
 | 
			
		||||
						? this.stats.drive.totalFiles
 | 
			
		||||
						: sum(this.stats.drive.incFiles, negate(this.stats.drive.decFiles))
 | 
			
		||||
					)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.target
 | 
			
		||||
	margin-bottom 16px !important
 | 
			
		||||
 | 
			
		||||
.instances
 | 
			
		||||
	width 100%
 | 
			
		||||
 | 
			
		||||
	> header
 | 
			
		||||
		display flex
 | 
			
		||||
 | 
			
		||||
		> *
 | 
			
		||||
			color var(--text)
 | 
			
		||||
			font-weight bold
 | 
			
		||||
 | 
			
		||||
	> div
 | 
			
		||||
		display flex
 | 
			
		||||
 | 
			
		||||
	> * > *
 | 
			
		||||
		flex 1
 | 
			
		||||
		overflow auto
 | 
			
		||||
 | 
			
		||||
		&:first-child
 | 
			
		||||
			min-width 200px
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -20,10 +20,11 @@
 | 
			
		||||
		<ul>
 | 
			
		||||
			<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }"><fa icon="home" fixed-width/>{{ $t('dashboard') }}</li>
 | 
			
		||||
			<li @click="nav('instance')" :class="{ active: page == 'instance' }"><fa icon="cog" fixed-width/>{{ $t('instance') }}</li>
 | 
			
		||||
			<li @click="nav('queue')" :class="{ active: page == 'queue' }"><fa :icon="faTasks" fixed-width/>{{ $t('queue') }}</li>
 | 
			
		||||
			<li @click="nav('moderators')" :class="{ active: page == 'moderators' }"><fa :icon="faHeadset" fixed-width/>{{ $t('moderators') }}</li>
 | 
			
		||||
			<li @click="nav('users')" :class="{ active: page == 'users' }"><fa icon="users" fixed-width/>{{ $t('users') }}</li>
 | 
			
		||||
			<li @click="nav('drive')" :class="{ active: page == 'drive' }"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</li>
 | 
			
		||||
			<!-- <li @click="nav('federation')" :class="{ active: page == 'federation' }"><fa :icon="faShareAlt" fixed-width/>{{ $t('federation') }}</li> -->
 | 
			
		||||
			<li @click="nav('federation')" :class="{ active: page == 'federation' }"><fa :icon="faGlobe" fixed-width/>{{ $t('federation') }}</li>
 | 
			
		||||
			<li @click="nav('emoji')" :class="{ active: page == 'emoji' }"><fa :icon="faGrin" fixed-width/>{{ $t('emoji') }}</li>
 | 
			
		||||
			<li @click="nav('announcements')" :class="{ active: page == 'announcements' }"><fa icon="broadcast-tower" fixed-width/>{{ $t('announcements') }}</li>
 | 
			
		||||
			<li @click="nav('hashtags')" :class="{ active: page == 'hashtags' }"><fa icon="hashtag" fixed-width/>{{ $t('hashtags') }}</li>
 | 
			
		||||
@@ -40,12 +41,14 @@
 | 
			
		||||
		<div class="page">
 | 
			
		||||
			<div v-if="page == 'dashboard'"><x-dashboard/></div>
 | 
			
		||||
			<div v-if="page == 'instance'"><x-instance/></div>
 | 
			
		||||
			<div v-if="page == 'queue'"><x-queue/></div>
 | 
			
		||||
			<div v-if="page == 'moderators'"><x-moderators/></div>
 | 
			
		||||
			<div v-if="page == 'users'"><x-users/></div>
 | 
			
		||||
			<div v-if="page == 'emoji'"><x-emoji/></div>
 | 
			
		||||
			<div v-if="page == 'announcements'"><x-announcements/></div>
 | 
			
		||||
			<div v-if="page == 'hashtags'"><x-hashtags/></div>
 | 
			
		||||
			<div v-if="page == 'drive'"><x-drive/></div>
 | 
			
		||||
			<div v-if="page == 'federation'"><x-federation/></div>
 | 
			
		||||
			<div v-if="page == 'abuse'"><x-abuse/></div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</main>
 | 
			
		||||
@@ -58,6 +61,7 @@ import i18n from '../../i18n';
 | 
			
		||||
import { version } from '../../config';
 | 
			
		||||
import XDashboard from "./dashboard.vue";
 | 
			
		||||
import XInstance from "./instance.vue";
 | 
			
		||||
import XQueue from "./queue.vue";
 | 
			
		||||
import XModerators from "./moderators.vue";
 | 
			
		||||
import XEmoji from "./emoji.vue";
 | 
			
		||||
import XAnnouncements from "./announcements.vue";
 | 
			
		||||
@@ -65,7 +69,9 @@ import XHashtags from "./hashtags.vue";
 | 
			
		||||
import XUsers from "./users.vue";
 | 
			
		||||
import XDrive from "./drive.vue";
 | 
			
		||||
import XAbuse from "./abuse.vue";
 | 
			
		||||
import { faHeadset, faArrowLeft, faShareAlt, faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import XFederation from "./federation.vue";
 | 
			
		||||
 | 
			
		||||
import { faHeadset, faArrowLeft, faGlobe, faExclamationCircle, faTasks } from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import { faGrin } from '@fortawesome/free-regular-svg-icons';
 | 
			
		||||
 | 
			
		||||
// Detect the user agent
 | 
			
		||||
@@ -77,6 +83,7 @@ export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XDashboard,
 | 
			
		||||
		XInstance,
 | 
			
		||||
		XQueue,
 | 
			
		||||
		XModerators,
 | 
			
		||||
		XEmoji,
 | 
			
		||||
		XAnnouncements,
 | 
			
		||||
@@ -84,6 +91,7 @@ export default Vue.extend({
 | 
			
		||||
		XUsers,
 | 
			
		||||
		XDrive,
 | 
			
		||||
		XAbuse,
 | 
			
		||||
		XFederation,
 | 
			
		||||
	},
 | 
			
		||||
	provide: {
 | 
			
		||||
		isMobile
 | 
			
		||||
@@ -97,8 +105,9 @@ export default Vue.extend({
 | 
			
		||||
			faGrin,
 | 
			
		||||
			faArrowLeft,
 | 
			
		||||
			faHeadset,
 | 
			
		||||
			faShareAlt,
 | 
			
		||||
			faExclamationCircle
 | 
			
		||||
			faGlobe,
 | 
			
		||||
			faExclamationCircle,
 | 
			
		||||
			faTasks
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
 
 | 
			
		||||
@@ -6,8 +6,10 @@
 | 
			
		||||
			<ui-input v-model="username" type="text">
 | 
			
		||||
				<span slot="prefix">@</span>
 | 
			
		||||
			</ui-input>
 | 
			
		||||
			<ui-button @click="add" :disabled="changing">{{ $t('add-moderator.add') }}</ui-button>
 | 
			
		||||
			<ui-button @click="remove" :disabled="changing">{{ $t('add-moderator.remove') }}</ui-button>
 | 
			
		||||
			<ui-horizon-group>
 | 
			
		||||
				<ui-button @click="add" :disabled="changing">{{ $t('add-moderator.add') }}</ui-button>
 | 
			
		||||
				<ui-button @click="remove" :disabled="changing">{{ $t('add-moderator.remove') }}</ui-button>
 | 
			
		||||
			</ui-horizon-group>
 | 
			
		||||
		</section>
 | 
			
		||||
	</ui-card>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										43
									
								
								src/client/app/admin/views/queue.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/client/app/admin/views/queue.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div>
 | 
			
		||||
	<ui-card>
 | 
			
		||||
		<div slot="title">{{ $t('operation') }}</div>
 | 
			
		||||
		<section>
 | 
			
		||||
			<ui-button @click="removeAllJobs">{{ $t('remove-all-jobs') }}</ui-button>
 | 
			
		||||
		</section>
 | 
			
		||||
	</ui-card>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../i18n';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n('admin/views/queue.vue'),
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		async removeAllJobs() {
 | 
			
		||||
			const process = async () => {
 | 
			
		||||
				await this.$root.api('admin/queue/clear');
 | 
			
		||||
				this.$root.dialog({
 | 
			
		||||
					type: 'success',
 | 
			
		||||
					splash: true
 | 
			
		||||
				});
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			await process().catch(e => {
 | 
			
		||||
				this.$root.dialog({
 | 
			
		||||
					type: 'error',
 | 
			
		||||
					text: e.toString()
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
@@ -420,7 +420,7 @@ export default Vue.extend({
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
		> [data-icon]
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -92,7 +92,13 @@
 | 
			
		||||
		<header>{{ $t('export') }}</header>
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<ui-button @click="exportNotes()">{{ $t('export-notes') }}</ui-button>
 | 
			
		||||
			<ui-select v-model="exportTarget">
 | 
			
		||||
				<option value="notes">{{ $t('export-targets.all-notes') }}</option>
 | 
			
		||||
				<option value="following">{{ $t('export-targets.following-list') }}</option>
 | 
			
		||||
				<option value="mute">{{ $t('export-targets.mute-list') }}</option>
 | 
			
		||||
				<option value="blocking">{{ $t('export-targets.blocking-list') }}</option>
 | 
			
		||||
			</ui-select>
 | 
			
		||||
			<ui-button @click="doExport()"><fa :icon="faDownload"/> {{ $t('export') }}</ui-button>
 | 
			
		||||
		</div>
 | 
			
		||||
	</section>
 | 
			
		||||
</ui-card>
 | 
			
		||||
@@ -105,6 +111,7 @@ import { apiUrl, host } from '../../../config';
 | 
			
		||||
import { toUnicode } from 'punycode';
 | 
			
		||||
import langmap from 'langmap';
 | 
			
		||||
import { unique } from '../../../../../prelude/array';
 | 
			
		||||
import { faDownload } from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n('common/views/components/profile-editor.vue'),
 | 
			
		||||
@@ -131,7 +138,9 @@ export default Vue.extend({
 | 
			
		||||
			autoAcceptFollowed: false,
 | 
			
		||||
			saving: false,
 | 
			
		||||
			avatarUploading: false,
 | 
			
		||||
			bannerUploading: false
 | 
			
		||||
			bannerUploading: false,
 | 
			
		||||
			exportTarget: 'notes',
 | 
			
		||||
			faDownload
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -262,8 +271,13 @@ export default Vue.extend({
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		exportNotes() {
 | 
			
		||||
			this.$root.api('i/export-notes', {});
 | 
			
		||||
		doExport() {
 | 
			
		||||
			this.$root.api(
 | 
			
		||||
				this.exportTarget == 'notes' ? 'i/export-notes' :
 | 
			
		||||
				this.exportTarget == 'following' ? 'i/export-following' :
 | 
			
		||||
				this.exportTarget == 'mute' ? 'i/export-mute' :
 | 
			
		||||
				this.exportTarget == 'blocking' ? 'i/export-blocking' :
 | 
			
		||||
				null, {});
 | 
			
		||||
 | 
			
		||||
			this.$root.dialog({
 | 
			
		||||
				type: 'info',
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,147 @@
 | 
			
		||||
<template>
 | 
			
		||||
<span
 | 
			
		||||
	class="reaction"
 | 
			
		||||
	:class="{ reacted: note.myReaction == reaction }"
 | 
			
		||||
	@click="toggleReaction(reaction)"
 | 
			
		||||
	v-if="count > 0"
 | 
			
		||||
	v-particle="!isMe"
 | 
			
		||||
>
 | 
			
		||||
	<mk-reaction-icon :reaction="reaction" ref="icon"/>
 | 
			
		||||
	<span>{{ count }}</span>
 | 
			
		||||
</span>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import Icon from './reaction-icon.vue';
 | 
			
		||||
import anime from 'animejs';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: {
 | 
			
		||||
		reaction: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: true,
 | 
			
		||||
		},
 | 
			
		||||
		count: {
 | 
			
		||||
			type: Number,
 | 
			
		||||
			required: true,
 | 
			
		||||
		},
 | 
			
		||||
		note: {
 | 
			
		||||
			type: Object,
 | 
			
		||||
			required: true,
 | 
			
		||||
		},
 | 
			
		||||
		canToggle: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: true,
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	computed: {
 | 
			
		||||
		isMe(): boolean {
 | 
			
		||||
			return this.$store.getters.isSignedIn && this.$store.state.i.id === this.note.userId;
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	watch: {
 | 
			
		||||
		count() {
 | 
			
		||||
			this.anime();
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		toggleReaction() {
 | 
			
		||||
			if (this.isMe) return;
 | 
			
		||||
			if (!this.canToggle) return;
 | 
			
		||||
 | 
			
		||||
			const oldReaction = this.note.myReaction;
 | 
			
		||||
			if (oldReaction) {
 | 
			
		||||
				this.$root.api('notes/reactions/delete', {
 | 
			
		||||
					noteId: this.note.id
 | 
			
		||||
				}).then(() => {
 | 
			
		||||
					if (oldReaction !== this.reaction) {
 | 
			
		||||
						this.$root.api('notes/reactions/create', {
 | 
			
		||||
							noteId: this.note.id,
 | 
			
		||||
							reaction: this.reaction
 | 
			
		||||
						});
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
			} else {
 | 
			
		||||
				this.$root.api('notes/reactions/create', {
 | 
			
		||||
					noteId: this.note.id,
 | 
			
		||||
					reaction: this.reaction
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		anime() {
 | 
			
		||||
			if (this.$store.state.device.reduceMotion) return;
 | 
			
		||||
			if (document.hidden) return;
 | 
			
		||||
 | 
			
		||||
			this.$nextTick(() => {
 | 
			
		||||
				const rect = this.$refs.icon.$el.getBoundingClientRect();
 | 
			
		||||
 | 
			
		||||
				const x = rect.left;
 | 
			
		||||
				const y = rect.top;
 | 
			
		||||
 | 
			
		||||
				const icon = new Icon({
 | 
			
		||||
					parent: this,
 | 
			
		||||
					propsData: {
 | 
			
		||||
						reaction: this.reaction
 | 
			
		||||
					}
 | 
			
		||||
				}).$mount();
 | 
			
		||||
 | 
			
		||||
				icon.$el.style.position = 'absolute';
 | 
			
		||||
				icon.$el.style.zIndex = 100;
 | 
			
		||||
				icon.$el.style.top = (y + window.scrollY) + 'px';
 | 
			
		||||
				icon.$el.style.left = (x + window.scrollX) + 'px';
 | 
			
		||||
				icon.$el.style.fontSize = window.getComputedStyle(this.$refs.icon.$el).fontSize;
 | 
			
		||||
 | 
			
		||||
				document.body.appendChild(icon.$el);
 | 
			
		||||
 | 
			
		||||
				anime({
 | 
			
		||||
					targets: icon.$el,
 | 
			
		||||
					opacity: [1, 0],
 | 
			
		||||
					translateY: [0, -64],
 | 
			
		||||
					duration: 1000,
 | 
			
		||||
					easing: 'linear',
 | 
			
		||||
					complete: () => {
 | 
			
		||||
						icon.destroyDom();
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.reaction
 | 
			
		||||
	display inline-block
 | 
			
		||||
	height 32px
 | 
			
		||||
	margin 2px
 | 
			
		||||
	padding 0 6px
 | 
			
		||||
	border-radius 4px
 | 
			
		||||
	cursor pointer
 | 
			
		||||
 | 
			
		||||
	*
 | 
			
		||||
		user-select none
 | 
			
		||||
		pointer-events none
 | 
			
		||||
 | 
			
		||||
	&.reacted
 | 
			
		||||
		background var(--primary)
 | 
			
		||||
 | 
			
		||||
		> span
 | 
			
		||||
			color var(--primaryForeground)
 | 
			
		||||
 | 
			
		||||
	&:not(.reacted)
 | 
			
		||||
		background var(--reactionViewerButtonBg)
 | 
			
		||||
 | 
			
		||||
		&:hover
 | 
			
		||||
			background var(--reactionViewerButtonHoverBg)
 | 
			
		||||
 | 
			
		||||
	> .mk-reaction-icon
 | 
			
		||||
		font-size 1.4em
 | 
			
		||||
 | 
			
		||||
	> span
 | 
			
		||||
		font-size 1.1em
 | 
			
		||||
		line-height 32px
 | 
			
		||||
		vertical-align middle
 | 
			
		||||
		color var(--text)
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,139 +1,37 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-reactions-viewer" :class="{ isMe }">
 | 
			
		||||
	<template v-if="reactions">
 | 
			
		||||
		<span :class="{ reacted: note.myReaction == 'like' }" @click="toggleReaction('like')" v-if="reactions.like" v-particle="!isMe"><mk-reaction-icon reaction="like" ref="like"/><span>{{ reactions.like }}</span></span>
 | 
			
		||||
		<span :class="{ reacted: note.myReaction == 'love' }" @click="toggleReaction('love')" v-if="reactions.love" v-particle="!isMe"><mk-reaction-icon reaction="love" ref="love"/><span>{{ reactions.love }}</span></span>
 | 
			
		||||
		<span :class="{ reacted: note.myReaction == 'laugh' }" @click="toggleReaction('laugh')" v-if="reactions.laugh" v-particle="!isMe"><mk-reaction-icon reaction="laugh" ref="laugh"/><span>{{ reactions.laugh }}</span></span>
 | 
			
		||||
		<span :class="{ reacted: note.myReaction == 'hmm' }" @click="toggleReaction('hmm')" v-if="reactions.hmm" v-particle="!isMe"><mk-reaction-icon reaction="hmm" ref="hmm"/><span>{{ reactions.hmm }}</span></span>
 | 
			
		||||
		<span :class="{ reacted: note.myReaction == 'surprise' }" @click="toggleReaction('surprise')" v-if="reactions.surprise" v-particle="!isMe"><mk-reaction-icon reaction="surprise" ref="surprise"/><span>{{ reactions.surprise }}</span></span>
 | 
			
		||||
		<span :class="{ reacted: note.myReaction == 'congrats' }" @click="toggleReaction('congrats')" v-if="reactions.congrats" v-particle="!isMe"><mk-reaction-icon reaction="congrats" ref="congrats"/><span>{{ reactions.congrats }}</span></span>
 | 
			
		||||
		<span :class="{ reacted: note.myReaction == 'angry' }" @click="toggleReaction('angry')" v-if="reactions.angry" v-particle="!isMe"><mk-reaction-icon reaction="angry" ref="angry"/><span>{{ reactions.angry }}</span></span>
 | 
			
		||||
		<span :class="{ reacted: note.myReaction == 'confused' }" @click="toggleReaction('confused')" v-if="reactions.confused" v-particle="!isMe"><mk-reaction-icon reaction="confused" ref="confused"/><span>{{ reactions.confused }}</span></span>
 | 
			
		||||
		<span :class="{ reacted: note.myReaction == 'rip' }" @click="toggleReaction('rip')" v-if="reactions.rip" v-particle="!isMe"><mk-reaction-icon reaction="rip" ref="rip"/><span>{{ reactions.rip }}</span></span>
 | 
			
		||||
		<span :class="{ reacted: note.myReaction == 'pudding' }" @click="toggleReaction('pudding')" v-if="reactions.pudding" v-particle="!isMe"><mk-reaction-icon reaction="pudding" ref="pudding"/><span>{{ reactions.pudding }}</span></span>
 | 
			
		||||
	</template>
 | 
			
		||||
	<x-reaction v-for="(count, reaction) in reactions" :reaction="reaction" :count="count" :note="note" :key="reaction"/>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import Icon from './reaction-icon.vue';
 | 
			
		||||
import anime from 'animejs';
 | 
			
		||||
import XReaction from './reactions-viewer.reaction.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XReaction
 | 
			
		||||
	},
 | 
			
		||||
	props: {
 | 
			
		||||
		note: {
 | 
			
		||||
			type: Object,
 | 
			
		||||
			required: true
 | 
			
		||||
		}
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	computed: {
 | 
			
		||||
		reactions(): any {
 | 
			
		||||
			return this.note.reactionCounts;
 | 
			
		||||
		},
 | 
			
		||||
		isMe(): boolean {
 | 
			
		||||
			return this.$store.getters.isSignedIn && (this.$store.state.i.id === this.note.userId);
 | 
			
		||||
		}
 | 
			
		||||
			return this.$store.getters.isSignedIn && this.$store.state.i.id === this.note.userId;
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	watch: {
 | 
			
		||||
		'reactions.like'() {
 | 
			
		||||
			this.anime('like');
 | 
			
		||||
		},
 | 
			
		||||
		'reactions.love'() {
 | 
			
		||||
			this.anime('love');
 | 
			
		||||
		},
 | 
			
		||||
		'reactions.laugh'() {
 | 
			
		||||
			this.anime('laugh');
 | 
			
		||||
		},
 | 
			
		||||
		'reactions.hmm'() {
 | 
			
		||||
			this.anime('hmm');
 | 
			
		||||
		},
 | 
			
		||||
		'reactions.surprise'() {
 | 
			
		||||
			this.anime('surprise');
 | 
			
		||||
		},
 | 
			
		||||
		'reactions.congrats'() {
 | 
			
		||||
			this.anime('congrats');
 | 
			
		||||
		},
 | 
			
		||||
		'reactions.angry'() {
 | 
			
		||||
			this.anime('angry');
 | 
			
		||||
		},
 | 
			
		||||
		'reactions.confused'() {
 | 
			
		||||
			this.anime('confused');
 | 
			
		||||
		},
 | 
			
		||||
		'reactions.rip'() {
 | 
			
		||||
			this.anime('rip');
 | 
			
		||||
		},
 | 
			
		||||
		'reactions.pudding'() {
 | 
			
		||||
			this.anime('pudding');
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		toggleReaction(reaction: string) {
 | 
			
		||||
			if (this.isMe) return;
 | 
			
		||||
 | 
			
		||||
			const oldReaction = this.note.myReaction;
 | 
			
		||||
			if (oldReaction) {
 | 
			
		||||
				this.$root.api('notes/reactions/delete', {
 | 
			
		||||
					noteId: this.note.id
 | 
			
		||||
				}).then(() => {
 | 
			
		||||
					if (oldReaction !== reaction) {
 | 
			
		||||
						this.$root.api('notes/reactions/create', {
 | 
			
		||||
							noteId: this.note.id,
 | 
			
		||||
							reaction: reaction
 | 
			
		||||
						});
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
			} else {
 | 
			
		||||
				this.$root.api('notes/reactions/create', {
 | 
			
		||||
					noteId: this.note.id,
 | 
			
		||||
					reaction: reaction
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		anime(reaction: string) {
 | 
			
		||||
			if (this.$store.state.device.reduceMotion) return;
 | 
			
		||||
			if (document.hidden) return;
 | 
			
		||||
 | 
			
		||||
			this.$nextTick(() => {
 | 
			
		||||
				const rect = this.$refs[reaction].$el.getBoundingClientRect();
 | 
			
		||||
 | 
			
		||||
				const x = rect.left;
 | 
			
		||||
				const y = rect.top;
 | 
			
		||||
 | 
			
		||||
				const icon = new Icon({
 | 
			
		||||
					parent: this,
 | 
			
		||||
					propsData: {
 | 
			
		||||
						reaction: reaction
 | 
			
		||||
					}
 | 
			
		||||
				}).$mount();
 | 
			
		||||
 | 
			
		||||
				icon.$el.style.position = 'absolute';
 | 
			
		||||
				icon.$el.style.zIndex = 100;
 | 
			
		||||
				icon.$el.style.top = (y + window.scrollY) + 'px';
 | 
			
		||||
				icon.$el.style.left = (x + window.scrollX) + 'px';
 | 
			
		||||
				icon.$el.style.fontSize = window.getComputedStyle(this.$refs[reaction].$el).fontSize;
 | 
			
		||||
 | 
			
		||||
				document.body.appendChild(icon.$el);
 | 
			
		||||
 | 
			
		||||
				anime({
 | 
			
		||||
					targets: icon.$el,
 | 
			
		||||
					opacity: [1, 0],
 | 
			
		||||
					translateY: [0, -64],
 | 
			
		||||
					duration: 1000,
 | 
			
		||||
					easing: 'linear',
 | 
			
		||||
					complete: () => {
 | 
			
		||||
						icon.destroyDom();
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.mk-reactions-viewer
 | 
			
		||||
	margin 6px 0
 | 
			
		||||
	margin: 4px -2px
 | 
			
		||||
 | 
			
		||||
	&:empty
 | 
			
		||||
		display none
 | 
			
		||||
@@ -144,38 +42,4 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
			&:hover
 | 
			
		||||
				background var(--reactionViewerButtonBg) !important
 | 
			
		||||
 | 
			
		||||
	> span
 | 
			
		||||
		display inline-block
 | 
			
		||||
		height 32px
 | 
			
		||||
		margin-right 6px
 | 
			
		||||
		padding 0 6px
 | 
			
		||||
		border-radius 4px
 | 
			
		||||
		cursor pointer
 | 
			
		||||
 | 
			
		||||
		*
 | 
			
		||||
			user-select none
 | 
			
		||||
			pointer-events none
 | 
			
		||||
 | 
			
		||||
		&.reacted
 | 
			
		||||
			background var(--primary)
 | 
			
		||||
 | 
			
		||||
			> span
 | 
			
		||||
				color var(--primaryForeground)
 | 
			
		||||
 | 
			
		||||
		&:not(.reacted)
 | 
			
		||||
			background var(--reactionViewerButtonBg)
 | 
			
		||||
 | 
			
		||||
			&:hover
 | 
			
		||||
				background var(--reactionViewerButtonHoverBg)
 | 
			
		||||
 | 
			
		||||
		> .mk-reaction-icon
 | 
			
		||||
			font-size 1.4em
 | 
			
		||||
 | 
			
		||||
		> span
 | 
			
		||||
			font-size 1.1em
 | 
			
		||||
			line-height 32px
 | 
			
		||||
			vertical-align middle
 | 
			
		||||
			color var(--text)
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -74,7 +74,7 @@ export default Vue.extend({
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
		> [data-icon]
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -117,7 +117,7 @@ export default define({
 | 
			
		||||
	margin 0
 | 
			
		||||
	padding 16px
 | 
			
		||||
	text-align center
 | 
			
		||||
	color #aaa
 | 
			
		||||
	color var(--text)
 | 
			
		||||
 | 
			
		||||
	> [data-icon]
 | 
			
		||||
		margin-right 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -86,7 +86,7 @@ export default define({
 | 
			
		||||
			margin 0
 | 
			
		||||
			padding 16px
 | 
			
		||||
			text-align center
 | 
			
		||||
			color #aaa
 | 
			
		||||
			color var(--text)
 | 
			
		||||
 | 
			
		||||
			> [data-icon]
 | 
			
		||||
				margin-right 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -88,7 +88,7 @@ export default define({
 | 
			
		||||
	margin 0
 | 
			
		||||
	padding 16px
 | 
			
		||||
	text-align center
 | 
			
		||||
	color #aaa
 | 
			
		||||
	color var(--text)
 | 
			
		||||
 | 
			
		||||
	> [data-icon]
 | 
			
		||||
		margin-right 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ export const hostname = address.hostname;
 | 
			
		||||
export const url = address.origin;
 | 
			
		||||
export const apiUrl = url + '/api';
 | 
			
		||||
export const wsUrl = url.replace('http://', 'ws://').replace('https://', 'wss://') + '/streaming';
 | 
			
		||||
export const lang = localStorage.getItem('lang');
 | 
			
		||||
export const lang = localStorage.getItem('lang') || window.lang; // windowは後方互換性のため
 | 
			
		||||
export const langs = _LANGS_;
 | 
			
		||||
export const locale = JSON.parse(localStorage.getItem('locale'));
 | 
			
		||||
export const copyright = _COPYRIGHT_;
 | 
			
		||||
 
 | 
			
		||||
@@ -78,7 +78,7 @@ export default Vue.extend({
 | 
			
		||||
	margin 0
 | 
			
		||||
	padding 16px
 | 
			
		||||
	text-align center
 | 
			
		||||
	color #aaa
 | 
			
		||||
	color var(--text)
 | 
			
		||||
 | 
			
		||||
	> [data-icon]
 | 
			
		||||
		margin-right 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -120,13 +120,13 @@ export default Vue.extend({
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
	> .fetching
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
		> [data-icon]
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -598,6 +598,7 @@ export default Vue.extend({
 | 
			
		||||
		padding 16px 0 0 0
 | 
			
		||||
		overflow auto
 | 
			
		||||
		z-index 1
 | 
			
		||||
		font-size 15px
 | 
			
		||||
 | 
			
		||||
		&.inWindow
 | 
			
		||||
			box-shadow var(--shadowRight)
 | 
			
		||||
 
 | 
			
		||||
@@ -218,6 +218,6 @@ export default Vue.extend({
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -74,7 +74,7 @@ export default Vue.extend({
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
		> i
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@ export default Vue.extend({
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
		> i
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -98,7 +98,7 @@ export default Vue.extend({
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
		> i
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -132,7 +132,7 @@ export default Vue.extend({
 | 
			
		||||
				padding 0 12px
 | 
			
		||||
				text-align center
 | 
			
		||||
				font-size 0.8em
 | 
			
		||||
				color #aaa
 | 
			
		||||
				color var(--text)
 | 
			
		||||
 | 
			
		||||
			> .instance
 | 
			
		||||
				box-shadow var(--shadow)
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
		<button slot="func" :title="$t('title')" @click="fetch">
 | 
			
		||||
			<fa v-if="!fetching &&  more" icon="arrow-right"/>
 | 
			
		||||
			<fa v-if="!fetching && !more" icon="sync"/>
 | 
			
		||||
		</button> 
 | 
			
		||||
		</button>
 | 
			
		||||
 | 
			
		||||
		<div class="mkw-polls--body">
 | 
			
		||||
			<div class="poll" v-if="!fetching && poll != null">
 | 
			
		||||
@@ -92,13 +92,13 @@ export default define({
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
	> .fetching
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
		> [data-icon]
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -89,13 +89,13 @@ export default define({
 | 
			
		||||
			margin 0
 | 
			
		||||
			padding 16px
 | 
			
		||||
			text-align center
 | 
			
		||||
			color #aaa
 | 
			
		||||
			color var(--text)
 | 
			
		||||
 | 
			
		||||
		> .fetching
 | 
			
		||||
			margin 0
 | 
			
		||||
			padding 16px
 | 
			
		||||
			text-align center
 | 
			
		||||
			color #aaa
 | 
			
		||||
			color var(--text)
 | 
			
		||||
 | 
			
		||||
			> [data-icon]
 | 
			
		||||
				margin-right 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -129,13 +129,13 @@ export default define({
 | 
			
		||||
			margin 0
 | 
			
		||||
			padding 16px
 | 
			
		||||
			text-align center
 | 
			
		||||
			color #aaa
 | 
			
		||||
			color var(--text)
 | 
			
		||||
 | 
			
		||||
		> .fetching
 | 
			
		||||
			margin 0
 | 
			
		||||
			padding 16px
 | 
			
		||||
			text-align center
 | 
			
		||||
			color #aaa
 | 
			
		||||
			color var(--text)
 | 
			
		||||
 | 
			
		||||
			> [data-icon]
 | 
			
		||||
				margin-right 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -83,13 +83,13 @@ export default Vue.extend({
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
	> .fetching
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
		> [data-icon]
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -88,6 +88,6 @@ export default Vue.extend({
 | 
			
		||||
		> .mk-time
 | 
			
		||||
			display inline-block
 | 
			
		||||
			padding 8px
 | 
			
		||||
			color #aaa
 | 
			
		||||
			color var(--text)
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -184,7 +184,7 @@ export default Vue.extend({
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
	> .placeholder
 | 
			
		||||
		padding 16px
 | 
			
		||||
 
 | 
			
		||||
@@ -171,6 +171,7 @@ export default Vue.extend({
 | 
			
		||||
		overflow auto
 | 
			
		||||
		-webkit-overflow-scrolling touch
 | 
			
		||||
		background var(--secondary)
 | 
			
		||||
		font-size 15px
 | 
			
		||||
 | 
			
		||||
	.me
 | 
			
		||||
		display block
 | 
			
		||||
 
 | 
			
		||||
@@ -121,13 +121,13 @@ export default Vue.extend({
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
	> .fetching
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
		> [data-icon]
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -57,7 +57,7 @@ export default Vue.extend({
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
		> i
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ export default Vue.extend({
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
		> i
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -52,7 +52,7 @@ export default Vue.extend({
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
		> i
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -89,7 +89,7 @@ export default Vue.extend({
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
		> i
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@ export const colorfulTheme: Theme = require('../theme/colorful.json5');
 | 
			
		||||
export const rainyTheme: Theme = require('../theme/rainy.json5');
 | 
			
		||||
export const mauveTheme: Theme = require('../theme/mauve.json5');
 | 
			
		||||
export const grayTheme: Theme = require('../theme/gray.json5');
 | 
			
		||||
export const tweetDeckTheme: Theme = require('../theme/tweet-deck.json5');
 | 
			
		||||
 | 
			
		||||
export const builtinThemes = [
 | 
			
		||||
	lightTheme,
 | 
			
		||||
@@ -38,6 +39,7 @@ export const builtinThemes = [
 | 
			
		||||
	rainyTheme,
 | 
			
		||||
	mauveTheme,
 | 
			
		||||
	grayTheme,
 | 
			
		||||
	tweetDeckTheme,
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export function applyTheme(theme: Theme, persisted = true) {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
	id: '2b0a0654-cdb4-4c9a-8244-736b647d3c2a',
 | 
			
		||||
 | 
			
		||||
	name: 'Japanese Sushi Set',
 | 
			
		||||
	author: 'noizenecio & syuilo',
 | 
			
		||||
	author: 'Noizenecio',
 | 
			
		||||
 | 
			
		||||
	base: 'dark',
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
	id: '252b2caf-86c2-4c3f-a73f-e1fc1cfa5298',
 | 
			
		||||
 | 
			
		||||
	name: 'Mauve',
 | 
			
		||||
	author: 'とわこ & syuilo',
 | 
			
		||||
	author: 'とわこ',
 | 
			
		||||
 | 
			
		||||
	base: 'dark',
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
	id: 'e9c8c01d-9c15-48d0-9b5c-3d00843b5b36',
 | 
			
		||||
 | 
			
		||||
	name: 'Lavender',
 | 
			
		||||
	author: 'sokuyuku & syuilo',
 | 
			
		||||
	author: 'sokuyuku',
 | 
			
		||||
 | 
			
		||||
	base: 'light',
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@
 | 
			
		||||
 | 
			
		||||
	name: 'Rainy',
 | 
			
		||||
	author: 'syuilo',
 | 
			
		||||
	desc: 'It\'s a rainy day.',
 | 
			
		||||
 | 
			
		||||
	base: 'light',
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										44
									
								
								src/client/theme/tweet-deck.json5
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/client/theme/tweet-deck.json5
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
{
 | 
			
		||||
	name: 'Tweet Deck',
 | 
			
		||||
	id: '06f82fb4-0dad-4d70-8a3f-56cae91e1163',
 | 
			
		||||
	author: 'simirall',
 | 
			
		||||
	desc: 'Tweet like a pro.',
 | 
			
		||||
	base: 'dark',
 | 
			
		||||
	vars: {
 | 
			
		||||
		primary: '#1da1f2',
 | 
			
		||||
		secondary: '#15202b',
 | 
			
		||||
		text: '#fdfdfd',
 | 
			
		||||
	},
 | 
			
		||||
	props: {
 | 
			
		||||
		bg: '#10171e',
 | 
			
		||||
		faceHeader: '$secondary',
 | 
			
		||||
		faceTextButton: '$primary',
 | 
			
		||||
		renoteGradient: '$secondary',
 | 
			
		||||
		renoteText: '#17bf63',
 | 
			
		||||
		quoteBorder: '#38444d',
 | 
			
		||||
		noteHeaderAdminFg: '$primary',
 | 
			
		||||
		noteHeaderAdminBg: '$secondary',
 | 
			
		||||
		noteActionsReplyHover: '$primary',
 | 
			
		||||
		noteActionsRenoteHover: '#17bf63',
 | 
			
		||||
		noteActionsReactionHover: '#e0245e',
 | 
			
		||||
		calendarWeek: '$primary',
 | 
			
		||||
		calendarSaturdayOrSunday: '#e0245e',
 | 
			
		||||
		announcementsBg: '$secondary',
 | 
			
		||||
		announcementsTitle: '$primary',
 | 
			
		||||
		suspendedInfoBg: '$secondary',
 | 
			
		||||
		suspendedInfoFg: '$primary',
 | 
			
		||||
		remoteInfoBg: '$secondary',
 | 
			
		||||
		remoteInfoFg: '$primary',
 | 
			
		||||
		desktopHeaderBg: '#1c2938',
 | 
			
		||||
		desktopHeaderFg: '#a9adae',
 | 
			
		||||
		desktopHeaderHoverFg: '#fff',
 | 
			
		||||
		desktopPostFormTransparentButtonFg: '#a9adae',
 | 
			
		||||
		desktopTimelineSrc: '$primary',
 | 
			
		||||
		desktopTimelineSrcHover: '#fff',
 | 
			
		||||
		deckAcrylicColumnBg: 'rgba(0, 0, 0, 0.0)',
 | 
			
		||||
		reversiBannerGradientStart: '$primary',
 | 
			
		||||
		reversiBannerGradientEnd: '$primary',
 | 
			
		||||
		reversiGameEmptyCellMyTurn: ':lighten<5<$primary',
 | 
			
		||||
		reversiGameEmptyCellCanPut: ':lighten<4<$primary',
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
@@ -26,6 +26,7 @@ export default function load() {
 | 
			
		||||
	const mixin = {} as Mixin;
 | 
			
		||||
 | 
			
		||||
	const url = validateUrl(config.url);
 | 
			
		||||
 | 
			
		||||
	config.url = normalizeUrl(config.url);
 | 
			
		||||
 | 
			
		||||
	mixin.host = url.host;
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ import { showMachineInfo } from './misc/show-machine-info';
 | 
			
		||||
 | 
			
		||||
const logger = new Logger('core', 'cyan');
 | 
			
		||||
const bootLogger = logger.createSubLogger('boot', 'magenta');
 | 
			
		||||
const clusterLog = logger.createSubLogger('cluster', 'orange');
 | 
			
		||||
const clusterLogger = logger.createSubLogger('cluster', 'orange');
 | 
			
		||||
const ev = new Xev();
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -249,19 +249,19 @@ function spawnWorker(): Promise<void> {
 | 
			
		||||
 | 
			
		||||
// Listen new workers
 | 
			
		||||
cluster.on('fork', worker => {
 | 
			
		||||
	clusterLog.debug(`Process forked: [${worker.id}]`);
 | 
			
		||||
	clusterLogger.debug(`Process forked: [${worker.id}]`);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Listen online workers
 | 
			
		||||
cluster.on('online', worker => {
 | 
			
		||||
	clusterLog.debug(`Process is now online: [${worker.id}]`);
 | 
			
		||||
	clusterLogger.debug(`Process is now online: [${worker.id}]`);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Listen for dying workers
 | 
			
		||||
cluster.on('exit', worker => {
 | 
			
		||||
	// Replace the dead worker,
 | 
			
		||||
	// we're not sentimental
 | 
			
		||||
	clusterLog.error(chalk.red(`[${worker.id}] died :(`));
 | 
			
		||||
	clusterLogger.error(chalk.red(`[${worker.id}] died :(`));
 | 
			
		||||
	cluster.fork();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ export default class Logger {
 | 
			
		||||
 | 
			
		||||
	private log(level: string, message: string, important = false, subDomains: string[] = []): void {
 | 
			
		||||
		if (program.quiet) return;
 | 
			
		||||
		if (process.env.NODE_ENV === 'test') return;
 | 
			
		||||
		const domain = this.color ? chalk.keyword(this.color)(this.domain) : chalk.white(this.domain);
 | 
			
		||||
		const domains = [domain].concat(subDomains);
 | 
			
		||||
		if (this.parentLogger) {
 | 
			
		||||
@@ -28,7 +29,8 @@ export default class Logger {
 | 
			
		||||
		} else {
 | 
			
		||||
			const time = dateformat(new Date(), 'HH:MM:ss');
 | 
			
		||||
			const process = cluster.isMaster ? '*' : cluster.worker.id;
 | 
			
		||||
			const log = `${chalk.gray(time)} ${level} ${process}\t[${domains.join(' ')}]\t${message}`;
 | 
			
		||||
			let log = `${level} ${process}\t[${domains.join(' ')}]\t${message}`;
 | 
			
		||||
			if (program.withLogTime) log = chalk.gray(time) + ' ' + log;
 | 
			
		||||
			console.log(important ? chalk.bold(log) : log);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ DriveFile.createIndex('md5');
 | 
			
		||||
DriveFile.createIndex('metadata.uri');
 | 
			
		||||
DriveFile.createIndex('metadata.userId');
 | 
			
		||||
DriveFile.createIndex('metadata.folderId');
 | 
			
		||||
DriveFile.createIndex('metadata._user.host');
 | 
			
		||||
export default DriveFile;
 | 
			
		||||
 | 
			
		||||
export const DriveFileChunk = monkDb.get('driveFiles.chunks');
 | 
			
		||||
 
 | 
			
		||||
@@ -32,4 +32,44 @@ export interface IInstance {
 | 
			
		||||
	 * このインスタンスから受け取った投稿数
 | 
			
		||||
	 */
 | 
			
		||||
	notesCount: number;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * このインスタンスのユーザーからフォローされている、自インスタンスのユーザーの数
 | 
			
		||||
	 */
 | 
			
		||||
	followingCount: number;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * このインスタンスのユーザーをフォローしている、自インスタンスのユーザーの数
 | 
			
		||||
	 */
 | 
			
		||||
	followersCount: number;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * ドライブ使用量
 | 
			
		||||
	 */
 | 
			
		||||
	driveUsage: number;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * ドライブのファイル数
 | 
			
		||||
	 */
 | 
			
		||||
	driveFiles: number;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 直近のリクエスト送信日時
 | 
			
		||||
	 */
 | 
			
		||||
	latestRequestSentAt?: Date;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 直近のリクエスト送信時のHTTPステータスコード
 | 
			
		||||
	 */
 | 
			
		||||
	latestStatus?: number;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 直近のリクエスト受信日時
 | 
			
		||||
	 */
 | 
			
		||||
	latestRequestReceivedAt?: Date;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * このインスタンスをブロックしているか
 | 
			
		||||
	 */
 | 
			
		||||
	isBlocked: boolean;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ const User = db.get<IUser>('users');
 | 
			
		||||
 | 
			
		||||
User.createIndex('username');
 | 
			
		||||
User.createIndex('usernameLower');
 | 
			
		||||
User.createIndex('host');
 | 
			
		||||
User.createIndex(['username', 'host'], { unique: true });
 | 
			
		||||
User.createIndex(['usernameLower', 'host'], { unique: true });
 | 
			
		||||
User.createIndex('token', { sparse: true, unique: true });
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import * as Queue from 'bee-queue';
 | 
			
		||||
import config from '../config';
 | 
			
		||||
import * as httpSignature from 'http-signature';
 | 
			
		||||
 | 
			
		||||
import config from '../config';
 | 
			
		||||
import { ILocalUser } from '../models/user';
 | 
			
		||||
import { program } from '../argv';
 | 
			
		||||
import handler from './processors';
 | 
			
		||||
@@ -31,26 +32,41 @@ function initializeQueue() {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createHttpJob(data: any) {
 | 
			
		||||
	if (queueAvailable) {
 | 
			
		||||
export function deliver(user: ILocalUser, content: any, to: any) {
 | 
			
		||||
	if (content == null) return;
 | 
			
		||||
 | 
			
		||||
	const data = {
 | 
			
		||||
		type: 'deliver',
 | 
			
		||||
		user,
 | 
			
		||||
		content,
 | 
			
		||||
		to
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	if (queueAvailable && !program.disableApQueue) {
 | 
			
		||||
		return queue.createJob(data)
 | 
			
		||||
			.retries(4)
 | 
			
		||||
			.backoff('exponential', 16384) // 16s
 | 
			
		||||
			.retries(8)
 | 
			
		||||
			.backoff('exponential', 1000)
 | 
			
		||||
			.save();
 | 
			
		||||
	} else {
 | 
			
		||||
		return handler({ data }, () => {});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function deliver(user: ILocalUser, content: any, to: any) {
 | 
			
		||||
	if (content == null) return;
 | 
			
		||||
export function processInbox(activity: any, signature: httpSignature.IParsedSignature) {
 | 
			
		||||
	const data = {
 | 
			
		||||
		type: 'processInbox',
 | 
			
		||||
		activity: activity,
 | 
			
		||||
		signature
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	createHttpJob({
 | 
			
		||||
		type: 'deliver',
 | 
			
		||||
		user,
 | 
			
		||||
		content,
 | 
			
		||||
		to
 | 
			
		||||
	});
 | 
			
		||||
	if (queueAvailable && !program.disableApQueue) {
 | 
			
		||||
		return queue.createJob(data)
 | 
			
		||||
			.retries(3)
 | 
			
		||||
			.backoff('exponential', 500)
 | 
			
		||||
			.save();
 | 
			
		||||
	} else {
 | 
			
		||||
		return handler({ data }, () => {});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createExportNotesJob(user: ILocalUser) {
 | 
			
		||||
@@ -63,6 +79,36 @@ export function createExportNotesJob(user: ILocalUser) {
 | 
			
		||||
		.save();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createExportFollowingJob(user: ILocalUser) {
 | 
			
		||||
	if (!queueAvailable) throw 'queue unavailable';
 | 
			
		||||
 | 
			
		||||
	return queue.createJob({
 | 
			
		||||
		type: 'exportFollowing',
 | 
			
		||||
		user: user
 | 
			
		||||
	})
 | 
			
		||||
		.save();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createExportMuteJob(user: ILocalUser) {
 | 
			
		||||
	if (!queueAvailable) throw 'queue unavailable';
 | 
			
		||||
 | 
			
		||||
	return queue.createJob({
 | 
			
		||||
		type: 'exportMute',
 | 
			
		||||
		user: user
 | 
			
		||||
	})
 | 
			
		||||
		.save();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createExportBlockingJob(user: ILocalUser) {
 | 
			
		||||
	if (!queueAvailable) throw 'queue unavailable';
 | 
			
		||||
 | 
			
		||||
	return queue.createJob({
 | 
			
		||||
		type: 'exportBlocking',
 | 
			
		||||
		user: user
 | 
			
		||||
	})
 | 
			
		||||
		.save();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function() {
 | 
			
		||||
	if (queueAvailable && enableQueue) {
 | 
			
		||||
		queue.process(128, handler);
 | 
			
		||||
@@ -71,3 +117,9 @@ export default function() {
 | 
			
		||||
 | 
			
		||||
	return queue;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function destroy() {
 | 
			
		||||
	queue.destroy().then(n => {
 | 
			
		||||
		queueLogger.succ(`All job removed (${n} jobs)`);
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										89
									
								
								src/queue/processors/export-blocking.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/queue/processors/export-blocking.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
import * as bq from 'bee-queue';
 | 
			
		||||
import * as tmp from 'tmp';
 | 
			
		||||
import * as fs from 'fs';
 | 
			
		||||
import * as mongo from 'mongodb';
 | 
			
		||||
 | 
			
		||||
import { queueLogger } from '../logger';
 | 
			
		||||
import addFile from '../../services/drive/add-file';
 | 
			
		||||
import User from '../../models/user';
 | 
			
		||||
import dateFormat = require('dateformat');
 | 
			
		||||
import Blocking from '../../models/blocking';
 | 
			
		||||
import config from '../../config';
 | 
			
		||||
 | 
			
		||||
const logger = queueLogger.createSubLogger('export-blocking');
 | 
			
		||||
 | 
			
		||||
export async function exportBlocking(job: bq.Job, done: any): Promise<void> {
 | 
			
		||||
	logger.info(`Exporting blocking of ${job.data.user._id} ...`);
 | 
			
		||||
 | 
			
		||||
	const user = await User.findOne({
 | 
			
		||||
		_id: new mongo.ObjectID(job.data.user._id.toString())
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// Create temp file
 | 
			
		||||
	const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
 | 
			
		||||
		tmp.file((e, path, fd, cleanup) => {
 | 
			
		||||
			if (e) return rej(e);
 | 
			
		||||
			res([path, cleanup]);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	logger.info(`Temp file is ${path}`);
 | 
			
		||||
 | 
			
		||||
	const stream = fs.createWriteStream(path, { flags: 'a' });
 | 
			
		||||
 | 
			
		||||
	let exportedCount = 0;
 | 
			
		||||
	let ended = false;
 | 
			
		||||
	let cursor: any = null;
 | 
			
		||||
 | 
			
		||||
	while (!ended) {
 | 
			
		||||
		const blockings = await Blocking.find({
 | 
			
		||||
			blockerId: user._id,
 | 
			
		||||
			...(cursor ? { _id: { $gt: cursor } } : {})
 | 
			
		||||
		}, {
 | 
			
		||||
			limit: 100,
 | 
			
		||||
			sort: {
 | 
			
		||||
				_id: 1
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		if (blockings.length === 0) {
 | 
			
		||||
			ended = true;
 | 
			
		||||
			job.reportProgress(100);
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cursor = blockings[blockings.length - 1]._id;
 | 
			
		||||
 | 
			
		||||
		for (const block of blockings) {
 | 
			
		||||
			const u = await User.findOne({ _id: block.blockeeId }, { fields: { username: true, host: true } });
 | 
			
		||||
			const content = u.host ? `${u.username}@${u.host}` : `${u.username}@${config.host}`;
 | 
			
		||||
			await new Promise((res, rej) => {
 | 
			
		||||
				stream.write(content + '\n', err => {
 | 
			
		||||
					if (err) {
 | 
			
		||||
						logger.error(err);
 | 
			
		||||
						rej(err);
 | 
			
		||||
					} else {
 | 
			
		||||
						res();
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
			exportedCount++;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const total = await Blocking.count({
 | 
			
		||||
			blockerId: user._id,
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		job.reportProgress(exportedCount / total);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	stream.end();
 | 
			
		||||
	logger.succ(`Exported to: ${path}`);
 | 
			
		||||
 | 
			
		||||
	const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv';
 | 
			
		||||
	const driveFile = await addFile(user, path, fileName);
 | 
			
		||||
 | 
			
		||||
	logger.succ(`Exported to: ${driveFile._id}`);
 | 
			
		||||
	cleanup();
 | 
			
		||||
	done();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										89
									
								
								src/queue/processors/export-following.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/queue/processors/export-following.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
import * as bq from 'bee-queue';
 | 
			
		||||
import * as tmp from 'tmp';
 | 
			
		||||
import * as fs from 'fs';
 | 
			
		||||
import * as mongo from 'mongodb';
 | 
			
		||||
 | 
			
		||||
import { queueLogger } from '../logger';
 | 
			
		||||
import addFile from '../../services/drive/add-file';
 | 
			
		||||
import User from '../../models/user';
 | 
			
		||||
import dateFormat = require('dateformat');
 | 
			
		||||
import Following from '../../models/following';
 | 
			
		||||
import config from '../../config';
 | 
			
		||||
 | 
			
		||||
const logger = queueLogger.createSubLogger('export-following');
 | 
			
		||||
 | 
			
		||||
export async function exportFollowing(job: bq.Job, done: any): Promise<void> {
 | 
			
		||||
	logger.info(`Exporting following of ${job.data.user._id} ...`);
 | 
			
		||||
 | 
			
		||||
	const user = await User.findOne({
 | 
			
		||||
		_id: new mongo.ObjectID(job.data.user._id.toString())
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// Create temp file
 | 
			
		||||
	const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
 | 
			
		||||
		tmp.file((e, path, fd, cleanup) => {
 | 
			
		||||
			if (e) return rej(e);
 | 
			
		||||
			res([path, cleanup]);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	logger.info(`Temp file is ${path}`);
 | 
			
		||||
 | 
			
		||||
	const stream = fs.createWriteStream(path, { flags: 'a' });
 | 
			
		||||
 | 
			
		||||
	let exportedCount = 0;
 | 
			
		||||
	let ended = false;
 | 
			
		||||
	let cursor: any = null;
 | 
			
		||||
 | 
			
		||||
	while (!ended) {
 | 
			
		||||
		const followings = await Following.find({
 | 
			
		||||
			followerId: user._id,
 | 
			
		||||
			...(cursor ? { _id: { $gt: cursor } } : {})
 | 
			
		||||
		}, {
 | 
			
		||||
			limit: 100,
 | 
			
		||||
			sort: {
 | 
			
		||||
				_id: 1
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		if (followings.length === 0) {
 | 
			
		||||
			ended = true;
 | 
			
		||||
			job.reportProgress(100);
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cursor = followings[followings.length - 1]._id;
 | 
			
		||||
 | 
			
		||||
		for (const following of followings) {
 | 
			
		||||
			const u = await User.findOne({ _id: following.followeeId }, { fields: { username: true, host: true } });
 | 
			
		||||
			const content = u.host ? `${u.username}@${u.host}` : `${u.username}@${config.host}`;
 | 
			
		||||
			await new Promise((res, rej) => {
 | 
			
		||||
				stream.write(content + '\n', err => {
 | 
			
		||||
					if (err) {
 | 
			
		||||
						logger.error(err);
 | 
			
		||||
						rej(err);
 | 
			
		||||
					} else {
 | 
			
		||||
						res();
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
			exportedCount++;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const total = await Following.count({
 | 
			
		||||
			followerId: user._id,
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		job.reportProgress(exportedCount / total);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	stream.end();
 | 
			
		||||
	logger.succ(`Exported to: ${path}`);
 | 
			
		||||
 | 
			
		||||
	const fileName = 'following-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv';
 | 
			
		||||
	const driveFile = await addFile(user, path, fileName);
 | 
			
		||||
 | 
			
		||||
	logger.succ(`Exported to: ${driveFile._id}`);
 | 
			
		||||
	cleanup();
 | 
			
		||||
	done();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										89
									
								
								src/queue/processors/export-mute.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/queue/processors/export-mute.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
import * as bq from 'bee-queue';
 | 
			
		||||
import * as tmp from 'tmp';
 | 
			
		||||
import * as fs from 'fs';
 | 
			
		||||
import * as mongo from 'mongodb';
 | 
			
		||||
 | 
			
		||||
import { queueLogger } from '../logger';
 | 
			
		||||
import addFile from '../../services/drive/add-file';
 | 
			
		||||
import User from '../../models/user';
 | 
			
		||||
import dateFormat = require('dateformat');
 | 
			
		||||
import Mute from '../../models/mute';
 | 
			
		||||
import config from '../../config';
 | 
			
		||||
 | 
			
		||||
const logger = queueLogger.createSubLogger('export-mute');
 | 
			
		||||
 | 
			
		||||
export async function exportMute(job: bq.Job, done: any): Promise<void> {
 | 
			
		||||
	logger.info(`Exporting mute of ${job.data.user._id} ...`);
 | 
			
		||||
 | 
			
		||||
	const user = await User.findOne({
 | 
			
		||||
		_id: new mongo.ObjectID(job.data.user._id.toString())
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// Create temp file
 | 
			
		||||
	const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
 | 
			
		||||
		tmp.file((e, path, fd, cleanup) => {
 | 
			
		||||
			if (e) return rej(e);
 | 
			
		||||
			res([path, cleanup]);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	logger.info(`Temp file is ${path}`);
 | 
			
		||||
 | 
			
		||||
	const stream = fs.createWriteStream(path, { flags: 'a' });
 | 
			
		||||
 | 
			
		||||
	let exportedCount = 0;
 | 
			
		||||
	let ended = false;
 | 
			
		||||
	let cursor: any = null;
 | 
			
		||||
 | 
			
		||||
	while (!ended) {
 | 
			
		||||
		const mutes = await Mute.find({
 | 
			
		||||
			muterId: user._id,
 | 
			
		||||
			...(cursor ? { _id: { $gt: cursor } } : {})
 | 
			
		||||
		}, {
 | 
			
		||||
			limit: 100,
 | 
			
		||||
			sort: {
 | 
			
		||||
				_id: 1
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		if (mutes.length === 0) {
 | 
			
		||||
			ended = true;
 | 
			
		||||
			job.reportProgress(100);
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cursor = mutes[mutes.length - 1]._id;
 | 
			
		||||
 | 
			
		||||
		for (const mute of mutes) {
 | 
			
		||||
			const u = await User.findOne({ _id: mute.muteeId }, { fields: { username: true, host: true } });
 | 
			
		||||
			const content = u.host ? `${u.username}@${u.host}` : `${u.username}@${config.host}`;
 | 
			
		||||
			await new Promise((res, rej) => {
 | 
			
		||||
				stream.write(content + '\n', err => {
 | 
			
		||||
					if (err) {
 | 
			
		||||
						logger.error(err);
 | 
			
		||||
						rej(err);
 | 
			
		||||
					} else {
 | 
			
		||||
						res();
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
			exportedCount++;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const total = await Mute.count({
 | 
			
		||||
			muterId: user._id,
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		job.reportProgress(exportedCount / total);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	stream.end();
 | 
			
		||||
	logger.succ(`Exported to: ${path}`);
 | 
			
		||||
 | 
			
		||||
	const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv';
 | 
			
		||||
	const driveFile = await addFile(user, path, fileName);
 | 
			
		||||
 | 
			
		||||
	logger.succ(`Exported to: ${driveFile._id}`);
 | 
			
		||||
	cleanup();
 | 
			
		||||
	done();
 | 
			
		||||
}
 | 
			
		||||
@@ -100,7 +100,7 @@ export async function exportNotes(job: bq.Job, done: any): Promise<void> {
 | 
			
		||||
	stream.end();
 | 
			
		||||
	logger.succ(`Exported to: ${path}`);
 | 
			
		||||
 | 
			
		||||
	const fileName = dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.json';
 | 
			
		||||
	const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.json';
 | 
			
		||||
	const driveFile = await addFile(user, path, fileName);
 | 
			
		||||
 | 
			
		||||
	logger.succ(`Exported to: ${driveFile._id}`);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,19 +2,50 @@ import * as bq from 'bee-queue';
 | 
			
		||||
 | 
			
		||||
import request from '../../../remote/activitypub/request';
 | 
			
		||||
import { queueLogger } from '../../logger';
 | 
			
		||||
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
 | 
			
		||||
import Instance from '../../../models/instance';
 | 
			
		||||
import instanceChart from '../../../services/chart/instance';
 | 
			
		||||
 | 
			
		||||
export default async (job: bq.Job, done: any): Promise<void> => {
 | 
			
		||||
	const { host } = new URL(job.data.to);
 | 
			
		||||
 | 
			
		||||
	try {
 | 
			
		||||
		await request(job.data.user, job.data.to, job.data.content);
 | 
			
		||||
 | 
			
		||||
		// Update stats
 | 
			
		||||
		registerOrFetchInstanceDoc(host).then(i => {
 | 
			
		||||
			Instance.update({ _id: i._id }, {
 | 
			
		||||
				$set: {
 | 
			
		||||
					latestRequestSentAt: new Date(),
 | 
			
		||||
					latestStatus: 200
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			instanceChart.requestSent(i.host, true);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		done();
 | 
			
		||||
	} catch (res) {
 | 
			
		||||
		// Update stats
 | 
			
		||||
		registerOrFetchInstanceDoc(host).then(i => {
 | 
			
		||||
			Instance.update({ _id: i._id }, {
 | 
			
		||||
				$set: {
 | 
			
		||||
					latestRequestSentAt: new Date(),
 | 
			
		||||
					latestStatus: res != null && res.hasOwnProperty('statusCode') ? res.statusCode : null
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			instanceChart.requestSent(i.host, false);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		if (res != null && res.hasOwnProperty('statusCode')) {
 | 
			
		||||
			queueLogger.warn(`deliver failed: ${res.statusCode} ${res.statusMessage} to=${job.data.to}`);
 | 
			
		||||
 | 
			
		||||
			if (res.statusCode >= 400 && res.statusCode < 500) {
 | 
			
		||||
				// HTTPステータスコード4xxはクライアントエラーであり、それはつまり
 | 
			
		||||
				// 何回再送しても成功することはないということなのでエラーにはしないでおく
 | 
			
		||||
				done();
 | 
			
		||||
			} else {
 | 
			
		||||
				queueLogger.warn(`deliver failed: ${res.statusCode} ${res.statusMessage} to=${job.data.to}`);
 | 
			
		||||
				done(res.statusMessage);
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,9 @@ import { toUnicode } from 'punycode';
 | 
			
		||||
import { URL } from 'url';
 | 
			
		||||
import { publishApLogStream } from '../../../services/stream';
 | 
			
		||||
import Logger from '../../../misc/logger';
 | 
			
		||||
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
 | 
			
		||||
import Instance from '../../../models/instance';
 | 
			
		||||
import instanceChart from '../../../services/chart/instance';
 | 
			
		||||
 | 
			
		||||
const logger = new Logger('inbox');
 | 
			
		||||
 | 
			
		||||
@@ -43,6 +46,15 @@ export default async (job: bq.Job, done: any): Promise<void> => {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// ブロックしてたら中断
 | 
			
		||||
		// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
 | 
			
		||||
		const instance = await Instance.findOne({ host: host.toLowerCase() });
 | 
			
		||||
		if (instance && instance.isBlocked) {
 | 
			
		||||
			logger.warn(`Blocked request: ${host}`);
 | 
			
		||||
			done();
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		user = await User.findOne({ usernameLower: username, host: host.toLowerCase() }) as IRemoteUser;
 | 
			
		||||
	} else {
 | 
			
		||||
		// アクティビティ内のホストの検証
 | 
			
		||||
@@ -55,6 +67,15 @@ export default async (job: bq.Job, done: any): Promise<void> => {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// ブロックしてたら中断
 | 
			
		||||
		// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
 | 
			
		||||
		const instance = await Instance.findOne({ host: host.toLowerCase() });
 | 
			
		||||
		if (instance && instance.isBlocked) {
 | 
			
		||||
			logger.warn(`Blocked request: ${host}`);
 | 
			
		||||
			done();
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		user = await User.findOne({
 | 
			
		||||
			host: { $ne: null },
 | 
			
		||||
			'publicKey.id': signature.keyId
 | 
			
		||||
@@ -101,6 +122,17 @@ export default async (job: bq.Job, done: any): Promise<void> => {
 | 
			
		||||
	});
 | 
			
		||||
	//#endregion
 | 
			
		||||
 | 
			
		||||
	// Update stats
 | 
			
		||||
	registerOrFetchInstanceDoc(user.host).then(i => {
 | 
			
		||||
		Instance.update({ _id: i._id }, {
 | 
			
		||||
			$set: {
 | 
			
		||||
				latestRequestReceivedAt: new Date()
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		instanceChart.requestReceived(i.host);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// アクティビティを処理
 | 
			
		||||
	try {
 | 
			
		||||
		await perform(user, activity);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,18 @@
 | 
			
		||||
import deliver from './http/deliver';
 | 
			
		||||
import processInbox from './http/process-inbox';
 | 
			
		||||
import { exportNotes } from './export-notes';
 | 
			
		||||
import { exportFollowing } from './export-following';
 | 
			
		||||
import { exportMute } from './export-mute';
 | 
			
		||||
import { exportBlocking } from './export-blocking';
 | 
			
		||||
import { queueLogger } from '../logger';
 | 
			
		||||
 | 
			
		||||
const handlers: any = {
 | 
			
		||||
	deliver,
 | 
			
		||||
	processInbox,
 | 
			
		||||
	exportNotes,
 | 
			
		||||
	exportFollowing,
 | 
			
		||||
	exportMute,
 | 
			
		||||
	exportBlocking,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default (job: any, done: any) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,10 +9,11 @@ import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type'
 | 
			
		||||
import { IDriveFile } from '../../../models/drive-file';
 | 
			
		||||
import Meta from '../../../models/meta';
 | 
			
		||||
import { fromHtml } from '../../../mfm/fromHtml';
 | 
			
		||||
import usersChart from '../../../chart/users';
 | 
			
		||||
import usersChart from '../../../services/chart/users';
 | 
			
		||||
import instanceChart from '../../../services/chart/instance';
 | 
			
		||||
import { URL } from 'url';
 | 
			
		||||
import { resolveNote, extractEmojis } from './note';
 | 
			
		||||
import registerInstance from '../../../services/register-instance';
 | 
			
		||||
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
 | 
			
		||||
import Instance from '../../../models/instance';
 | 
			
		||||
import getDriveFileUrl from '../../../misc/get-drive-file-url';
 | 
			
		||||
import { IEmoji } from '../../../models/emoji';
 | 
			
		||||
@@ -188,15 +189,14 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Register host
 | 
			
		||||
	registerInstance(host).then(i => {
 | 
			
		||||
	registerOrFetchInstanceDoc(host).then(i => {
 | 
			
		||||
		Instance.update({ _id: i._id }, {
 | 
			
		||||
			$inc: {
 | 
			
		||||
				usersCount: 1
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		// TODO
 | 
			
		||||
		//perInstanceChart.newUser();
 | 
			
		||||
		instanceChart.newUser(i.host);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	//#region Increment users count
 | 
			
		||||
 
 | 
			
		||||
@@ -4,11 +4,13 @@ import { URL } from 'url';
 | 
			
		||||
import * as crypto from 'crypto';
 | 
			
		||||
import { lookup, IRunOptions } from 'lookup-dns-cache';
 | 
			
		||||
import * as promiseAny from 'promise-any';
 | 
			
		||||
import { toUnicode } from 'punycode';
 | 
			
		||||
 | 
			
		||||
import config from '../../config';
 | 
			
		||||
import { ILocalUser } from '../../models/user';
 | 
			
		||||
import { publishApLogStream } from '../../services/stream';
 | 
			
		||||
import { apLogger } from './logger';
 | 
			
		||||
import Instance from '../../models/instance';
 | 
			
		||||
 | 
			
		||||
export const logger = apLogger.createSubLogger('deliver');
 | 
			
		||||
 | 
			
		||||
@@ -19,6 +21,11 @@ export default (user: ILocalUser, url: string, object: any) => new Promise(async
 | 
			
		||||
 | 
			
		||||
	const { protocol, host, hostname, port, pathname, search } = new URL(url);
 | 
			
		||||
 | 
			
		||||
	// ブロックしてたら中断
 | 
			
		||||
	// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
 | 
			
		||||
	const instance = await Instance.findOne({ host: toUnicode(host) });
 | 
			
		||||
	if (instance && instance.isBlocked) return;
 | 
			
		||||
 | 
			
		||||
	const data = JSON.stringify(object);
 | 
			
		||||
 | 
			
		||||
	const sha256 = crypto.createHash('sha256');
 | 
			
		||||
@@ -43,11 +50,11 @@ export default (user: ILocalUser, url: string, object: any) => new Promise(async
 | 
			
		||||
			'Digest': `SHA-256=${hash}`
 | 
			
		||||
		}
 | 
			
		||||
	}, res => {
 | 
			
		||||
		logger.info(`${url} --> ${res.statusCode}`);
 | 
			
		||||
 | 
			
		||||
		if (res.statusCode >= 400) {
 | 
			
		||||
			logger.warn(`${url} --> ${res.statusCode}`);
 | 
			
		||||
			reject(res);
 | 
			
		||||
		} else {
 | 
			
		||||
			logger.succ(`${url} --> ${res.statusCode}`);
 | 
			
		||||
			resolve();
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ import * as Router from 'koa-router';
 | 
			
		||||
import * as json from 'koa-json-body';
 | 
			
		||||
import * as httpSignature from 'http-signature';
 | 
			
		||||
 | 
			
		||||
import { createHttpJob } from '../queue';
 | 
			
		||||
import { renderActivity } from '../remote/activitypub/renderer';
 | 
			
		||||
import Note from '../models/note';
 | 
			
		||||
import User, { isLocalUser, ILocalUser, IUser } from '../models/user';
 | 
			
		||||
@@ -17,6 +16,7 @@ import Followers from './activitypub/followers';
 | 
			
		||||
import Following from './activitypub/following';
 | 
			
		||||
import Featured from './activitypub/featured';
 | 
			
		||||
import renderQuestion from '../remote/activitypub/renderer/question';
 | 
			
		||||
import { processInbox } from '../queue';
 | 
			
		||||
 | 
			
		||||
// Init router
 | 
			
		||||
const router = new Router();
 | 
			
		||||
@@ -35,11 +35,7 @@ function inbox(ctx: Router.IRouterContext) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	createHttpJob({
 | 
			
		||||
		type: 'processInbox',
 | 
			
		||||
		activity: ctx.request.body,
 | 
			
		||||
		signature
 | 
			
		||||
	});
 | 
			
		||||
	processInbox(ctx.request.body, signature);
 | 
			
		||||
 | 
			
		||||
	ctx.status = 202;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,33 @@
 | 
			
		||||
import $ from 'cafy';
 | 
			
		||||
import define from '../../../define';
 | 
			
		||||
import Following from '../../../../../models/following';
 | 
			
		||||
import User from '../../../../../models/user';
 | 
			
		||||
import deleteFollowing from '../../../../../services/following/delete';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
	requireCredential: true,
 | 
			
		||||
	requireModerator: true,
 | 
			
		||||
 | 
			
		||||
	params: {
 | 
			
		||||
		host: {
 | 
			
		||||
			validator: $.str
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
 | 
			
		||||
	const followings = await Following.find({
 | 
			
		||||
		'_follower.host': ps.host
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	const pairs = await Promise.all(followings.map(f => Promise.all([
 | 
			
		||||
		User.findOne({ _id: f.followerId }),
 | 
			
		||||
		User.findOne({ _id: f.followeeId })
 | 
			
		||||
	])));
 | 
			
		||||
 | 
			
		||||
	for (const pair of pairs) {
 | 
			
		||||
		deleteFollowing(pair[0], pair[1]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res();
 | 
			
		||||
}));
 | 
			
		||||
							
								
								
									
										34
									
								
								src/server/api/endpoints/admin/federation/update-instance.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/server/api/endpoints/admin/federation/update-instance.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
import $ from 'cafy';
 | 
			
		||||
import define from '../../../define';
 | 
			
		||||
import Instance from '../../../../../models/instance';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
	requireCredential: true,
 | 
			
		||||
	requireModerator: true,
 | 
			
		||||
 | 
			
		||||
	params: {
 | 
			
		||||
		host: {
 | 
			
		||||
			validator: $.str
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		isBlocked: {
 | 
			
		||||
			validator: $.bool
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
 | 
			
		||||
	const instance = await Instance.findOne({ host: ps.host });
 | 
			
		||||
 | 
			
		||||
	if (instance == null) {
 | 
			
		||||
		return rej('instance not found');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Instance.update({ host: ps.host }, {
 | 
			
		||||
		$set: {
 | 
			
		||||
			isBlocked: ps.isBlocked
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	res();
 | 
			
		||||
}));
 | 
			
		||||
							
								
								
									
										15
									
								
								src/server/api/endpoints/admin/queue/clear.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/server/api/endpoints/admin/queue/clear.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
import define from '../../../define';
 | 
			
		||||
import { destroy } from '../../../../../queue';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
	requireCredential: true,
 | 
			
		||||
	requireModerator: true,
 | 
			
		||||
 | 
			
		||||
	params: {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default define(meta, (ps) => new Promise(async (res, rej) => {
 | 
			
		||||
	destroy();
 | 
			
		||||
 | 
			
		||||
	res();
 | 
			
		||||
}));
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import $ from 'cafy';
 | 
			
		||||
import define from '../../define';
 | 
			
		||||
import activeUsersChart from '../../../../chart/active-users';
 | 
			
		||||
import activeUsersChart from '../../../../services/chart/active-users';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
	stability: 'stable',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import $ from 'cafy';
 | 
			
		||||
import define from '../../define';
 | 
			
		||||
import driveChart from '../../../../chart/drive';
 | 
			
		||||
import driveChart from '../../../../services/chart/drive';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
	stability: 'stable',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import $ from 'cafy';
 | 
			
		||||
import define from '../../define';
 | 
			
		||||
import federationChart from '../../../../chart/federation';
 | 
			
		||||
import federationChart from '../../../../services/chart/federation';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
	stability: 'stable',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import $ from 'cafy';
 | 
			
		||||
import define from '../../define';
 | 
			
		||||
import hashtagChart from '../../../../chart/hashtag';
 | 
			
		||||
import hashtagChart from '../../../../services/chart/hashtag';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
	stability: 'stable',
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										42
									
								
								src/server/api/endpoints/charts/instance.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/server/api/endpoints/charts/instance.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
import $ from 'cafy';
 | 
			
		||||
import define from '../../define';
 | 
			
		||||
import instanceChart from '../../../../services/chart/instance';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
	stability: 'stable',
 | 
			
		||||
 | 
			
		||||
	desc: {
 | 
			
		||||
		'ja-JP': 'インスタンスごとのチャートを取得します。'
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	params: {
 | 
			
		||||
		span: {
 | 
			
		||||
			validator: $.str.or(['day', 'hour']),
 | 
			
		||||
			desc: {
 | 
			
		||||
				'ja-JP': '集計のスパン (day または hour)'
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		limit: {
 | 
			
		||||
			validator: $.num.optional.range(1, 500),
 | 
			
		||||
			default: 30,
 | 
			
		||||
			desc: {
 | 
			
		||||
				'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		host: {
 | 
			
		||||
			validator: $.str,
 | 
			
		||||
			desc: {
 | 
			
		||||
				'ja-JP': '対象のインスタンスのホスト',
 | 
			
		||||
				'en-US': 'Target instance host'
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default define(meta, (ps) => new Promise(async (res, rej) => {
 | 
			
		||||
	const stats = await instanceChart.getChart(ps.span as any, ps.limit, ps.host);
 | 
			
		||||
 | 
			
		||||
	res(stats);
 | 
			
		||||
}));
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import $ from 'cafy';
 | 
			
		||||
import define from '../../define';
 | 
			
		||||
import networkChart from '../../../../chart/network';
 | 
			
		||||
import networkChart from '../../../../services/chart/network';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
	stability: 'stable',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import $ from 'cafy';
 | 
			
		||||
import define from '../../define';
 | 
			
		||||
import notesChart from '../../../../chart/notes';
 | 
			
		||||
import notesChart from '../../../../services/chart/notes';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
	stability: 'stable',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import $ from 'cafy';
 | 
			
		||||
import define from '../../../define';
 | 
			
		||||
import perUserDriveChart from '../../../../../chart/per-user-drive';
 | 
			
		||||
import perUserDriveChart from '../../../../../services/chart/per-user-drive';
 | 
			
		||||
import ID, { transform } from '../../../../../misc/cafy-id';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import $ from 'cafy';
 | 
			
		||||
import define from '../../../define';
 | 
			
		||||
import perUserFollowingChart from '../../../../../chart/per-user-following';
 | 
			
		||||
import perUserFollowingChart from '../../../../../services/chart/per-user-following';
 | 
			
		||||
import ID, { transform } from '../../../../../misc/cafy-id';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import $ from 'cafy';
 | 
			
		||||
import define from '../../../define';
 | 
			
		||||
import perUserNotesChart from '../../../../../chart/per-user-notes';
 | 
			
		||||
import perUserNotesChart from '../../../../../services/chart/per-user-notes';
 | 
			
		||||
import ID, { transform } from '../../../../../misc/cafy-id';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import $ from 'cafy';
 | 
			
		||||
import define from '../../../define';
 | 
			
		||||
import perUserReactionsChart from '../../../../../chart/per-user-reactions';
 | 
			
		||||
import perUserReactionsChart from '../../../../../services/chart/per-user-reactions';
 | 
			
		||||
import ID, { transform } from '../../../../../misc/cafy-id';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user