Compare commits
	
		
			243 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					aa19767ebc | ||
| 
						 | 
					143190ab38 | ||
| 
						 | 
					baf94f86c4 | ||
| 
						 | 
					d36b129369 | ||
| 
						 | 
					f36d88246a | ||
| 
						 | 
					03f87140b3 | ||
| 
						 | 
					1dc07f6b72 | ||
| 
						 | 
					0aa0a9d24b | ||
| 
						 | 
					a9a93db2b4 | ||
| 
						 | 
					f187df3933 | ||
| 
						 | 
					8abe8042d7 | ||
| 
						 | 
					58fd46ff6f | ||
| 
						 | 
					fef8b662c1 | ||
| 
						 | 
					8de2f4ce76 | ||
| 
						 | 
					e5e344e1cd | ||
| 
						 | 
					e70d7edf41 | ||
| 
						 | 
					71d4d51fb2 | ||
| 
						 | 
					aaf38f1cbe | ||
| 
						 | 
					0e0d6692c0 | ||
| 
						 | 
					29f927fe72 | ||
| 
						 | 
					ee39d9594e | ||
| 
						 | 
					cefd2a4c54 | ||
| 
						 | 
					a08c20d9af | ||
| 
						 | 
					dc11f1afbf | ||
| 
						 | 
					b0f2b209a2 | ||
| 
						 | 
					a25fdfd519 | ||
| 
						 | 
					c1aa58596d | ||
| 
						 | 
					b6a3eb2445 | ||
| 
						 | 
					310f4d2edb | ||
| 
						 | 
					701fee3139 | ||
| 
						 | 
					593c2b9517 | ||
| 
						 | 
					96b2267cb8 | ||
| 
						 | 
					84730a071a | ||
| 
						 | 
					d0b0cf8dfb | ||
| 
						 | 
					749200d22b | ||
| 
						 | 
					a47baad943 | ||
| 
						 | 
					50abb51ece | ||
| 
						 | 
					1f890c5bed | ||
| 
						 | 
					97f23af86d | ||
| 
						 | 
					d77aa1f26a | ||
| 
						 | 
					0b075ad4e9 | ||
| 
						 | 
					423f776ed0 | ||
| 
						 | 
					084fd8152b | ||
| 
						 | 
					89d35c2e63 | ||
| 
						 | 
					be33581642 | ||
| 
						 | 
					2d6d9f30e1 | ||
| 
						 | 
					85721065fd | ||
| 
						 | 
					9d65768d4d | ||
| 
						 | 
					13f69e4291 | ||
| 
						 | 
					6a0affcec1 | ||
| 
						 | 
					ab6a84cd45 | ||
| 
						 | 
					ba93bf7478 | ||
| 
						 | 
					1c4e1af7c3 | ||
| 
						 | 
					a85f4c4fc4 | ||
| 
						 | 
					9d6c8806af | ||
| 
						 | 
					ff52ea2a7c | ||
| 
						 | 
					f247ee9dd3 | ||
| 
						 | 
					f4cec53ba1 | ||
| 
						 | 
					ad70b50fee | ||
| 
						 | 
					ea7b2b3141 | ||
| 
						 | 
					c2f932e28b | ||
| 
						 | 
					c637882578 | ||
| 
						 | 
					ef7221e39e | ||
| 
						 | 
					6b571a7799 | ||
| 
						 | 
					0638b6cb69 | ||
| 
						 | 
					a39c1706a1 | ||
| 
						 | 
					c741e27057 | ||
| 
						 | 
					ede854c215 | ||
| 
						 | 
					6a953b4d94 | ||
| 
						 | 
					1d763096c7 | ||
| 
						 | 
					630d873ec0 | ||
| 
						 | 
					d427957ea7 | ||
| 
						 | 
					2430cc0e2c | ||
| 
						 | 
					e57ee24864 | ||
| 
						 | 
					3bc05ab3f2 | ||
| 
						 | 
					4e50dcfa93 | ||
| 
						 | 
					fcf5531e5b | ||
| 
						 | 
					3bef69ee58 | ||
| 
						 | 
					311a4f28b0 | ||
| 
						 | 
					01e692b353 | ||
| 
						 | 
					3b445af6fc | ||
| 
						 | 
					1e43ece637 | ||
| 
						 | 
					6f1048c006 | ||
| 
						 | 
					d686e70f2b | ||
| 
						 | 
					70f524b82d | ||
| 
						 | 
					79c6475028 | ||
| 
						 | 
					7dee5309dc | ||
| 
						 | 
					958ec7b03f | ||
| 
						 | 
					9153434906 | ||
| 
						 | 
					3a08364c24 | ||
| 
						 | 
					a9beeab502 | ||
| 
						 | 
					38c901069a | ||
| 
						 | 
					3f7606060e | ||
| 
						 | 
					777f20e9be | ||
| 
						 | 
					8e39aecffe | ||
| 
						 | 
					9e1ab54097 | ||
| 
						 | 
					0f9e09f4bd | ||
| 
						 | 
					743ebc17b9 | ||
| 
						 | 
					9bc4af76b8 | ||
| 
						 | 
					46fa26426d | ||
| 
						 | 
					58d0dc1795 | ||
| 
						 | 
					bc11702f7d | ||
| 
						 | 
					6288de5813 | ||
| 
						 | 
					bbf59c7d9f | ||
| 
						 | 
					d7787bacf7 | ||
| 
						 | 
					23ae0515c4 | ||
| 
						 | 
					fe88b34b8a | ||
| 
						 | 
					74aa031a22 | ||
| 
						 | 
					6aeed212d9 | ||
| 
						 | 
					45b972c059 | ||
| 
						 | 
					7ecfc007a9 | ||
| 
						 | 
					fc78c75bab | ||
| 
						 | 
					59493a0cd9 | ||
| 
						 | 
					6060c6d56e | ||
| 
						 | 
					6cdbb27169 | ||
| 
						 | 
					ed8b073e54 | ||
| 
						 | 
					7dd193636c | ||
| 
						 | 
					01d018510c | ||
| 
						 | 
					1c273a0a75 | ||
| 
						 | 
					fa2c7658a0 | ||
| 
						 | 
					84ca3a7d45 | ||
| 
						 | 
					902c73e6ac | ||
| 
						 | 
					3b626f72e4 | ||
| 
						 | 
					f5ce137a6b | ||
| 
						 | 
					3ce9d12361 | ||
| 
						 | 
					2fe2f3b1eb | ||
| 
						 | 
					17b3ee41db | ||
| 
						 | 
					56d2a5d5d3 | ||
| 
						 | 
					3a17ff0983 | ||
| 
						 | 
					9a9270bbe9 | ||
| 
						 | 
					512eee4f51 | ||
| 
						 | 
					db01fa0eef | ||
| 
						 | 
					0c49a1ebd5 | ||
| 
						 | 
					636d6394e3 | ||
| 
						 | 
					c67c091b3a | ||
| 
						 | 
					333604898c | ||
| 
						 | 
					076ac3b614 | ||
| 
						 | 
					0e1468b159 | ||
| 
						 | 
					66409029e7 | ||
| 
						 | 
					8ec6b2ec11 | ||
| 
						 | 
					14736620ec | ||
| 
						 | 
					831ca53b63 | ||
| 
						 | 
					6138d46509 | ||
| 
						 | 
					c3003cb363 | ||
| 
						 | 
					4277e53433 | ||
| 
						 | 
					6516bd2ade | ||
| 
						 | 
					27d22f954a | ||
| 
						 | 
					88f5e8e8e2 | ||
| 
						 | 
					fd2ae6d3cf | ||
| 
						 | 
					238edd36f7 | ||
| 
						 | 
					5d847f9808 | ||
| 
						 | 
					ac914af9c3 | ||
| 
						 | 
					636f90ca0c | ||
| 
						 | 
					29469bb7c6 | ||
| 
						 | 
					4f043b1841 | ||
| 
						 | 
					85008303f5 | ||
| 
						 | 
					3432d6e615 | ||
| 
						 | 
					855c990a17 | ||
| 
						 | 
					eef2dc2f62 | ||
| 
						 | 
					fa54140973 | ||
| 
						 | 
					e770d32916 | ||
| 
						 | 
					8aeabf530c | ||
| 
						 | 
					6fbf1cfc28 | ||
| 
						 | 
					5480df35bc | ||
| 
						 | 
					8b5104d564 | ||
| 
						 | 
					3c1192d6bf | ||
| 
						 | 
					eb8ef35122 | ||
| 
						 | 
					8a31e5fd0f | ||
| 
						 | 
					751728db84 | ||
| 
						 | 
					e695f54ef0 | ||
| 
						 | 
					b2ed45ae38 | ||
| 
						 | 
					5e36f75f8a | ||
| 
						 | 
					7ac13a386c | ||
| 
						 | 
					56c8ad9df3 | ||
| 
						 | 
					f1ab918ecd | ||
| 
						 | 
					42af8c7695 | ||
| 
						 | 
					0de2e150cb | ||
| 
						 | 
					1ac498c8fe | ||
| 
						 | 
					545d29a40a | ||
| 
						 | 
					73bbef2922 | ||
| 
						 | 
					26567cdeb2 | ||
| 
						 | 
					84941cbb97 | ||
| 
						 | 
					cd5b24d4eb | ||
| 
						 | 
					c8abd512e1 | ||
| 
						 | 
					de9bd2651b | ||
| 
						 | 
					c432310cae | ||
| 
						 | 
					1c95cdffdc | ||
| 
						 | 
					c3ec668e16 | ||
| 
						 | 
					2af79e9855 | ||
| 
						 | 
					f6ac6f9c6f | ||
| 
						 | 
					d8c835fa51 | ||
| 
						 | 
					a97c14a7b7 | ||
| 
						 | 
					54ecf97c22 | ||
| 
						 | 
					9c4e64b7b5 | ||
| 
						 | 
					ef44eda69e | ||
| 
						 | 
					f1a7ab639b | ||
| 
						 | 
					0d8286cb2a | ||
| 
						 | 
					8e4ad4b919 | ||
| 
						 | 
					9a09ed6290 | ||
| 
						 | 
					9ca36021b0 | ||
| 
						 | 
					eaebb95827 | ||
| 
						 | 
					b8cce2067c | ||
| 
						 | 
					07a0631964 | ||
| 
						 | 
					ebc2b05231 | ||
| 
						 | 
					4c79dd4e96 | ||
| 
						 | 
					abc57519a7 | ||
| 
						 | 
					a23a10d375 | ||
| 
						 | 
					01e7716170 | ||
| 
						 | 
					0a29ce13b6 | ||
| 
						 | 
					6d0ee61661 | ||
| 
						 | 
					9c684fd6c4 | ||
| 
						 | 
					cbb8edd5ed | ||
| 
						 | 
					f933fa0e78 | ||
| 
						 | 
					b3e5198f23 | ||
| 
						 | 
					6e042ca344 | ||
| 
						 | 
					9381842af2 | ||
| 
						 | 
					a6644c540a | ||
| 
						 | 
					063427a660 | ||
| 
						 | 
					6846067a5d | ||
| 
						 | 
					b7273c90ae | ||
| 
						 | 
					a6f5e23069 | ||
| 
						 | 
					f37f22b163 | ||
| 
						 | 
					9440fdb2d0 | ||
| 
						 | 
					96c19b2607 | ||
| 
						 | 
					b34c1379e9 | ||
| 
						 | 
					0fc965f342 | ||
| 
						 | 
					617db05808 | ||
| 
						 | 
					6a20ab687c | ||
| 
						 | 
					6c3bcdad54 | ||
| 
						 | 
					aeea275ec2 | ||
| 
						 | 
					b3c6e28717 | ||
| 
						 | 
					70691e1523 | ||
| 
						 | 
					2cb032b0e0 | ||
| 
						 | 
					ebbf5268ac | ||
| 
						 | 
					b2030e8403 | ||
| 
						 | 
					82493bb741 | ||
| 
						 | 
					aa15901c23 | ||
| 
						 | 
					0bd4d069a2 | ||
| 
						 | 
					ab871c6991 | ||
| 
						 | 
					feec5e88fc | ||
| 
						 | 
					a091cbb93a | ||
| 
						 | 
					a59ab79da0 | ||
| 
						 | 
					8ca4d39440 | 
@@ -116,8 +116,25 @@ autoAdmin: true
 | 
			
		||||
# Whether disable HSTS
 | 
			
		||||
#disableHsts: true
 | 
			
		||||
 | 
			
		||||
# Clustering
 | 
			
		||||
# Number of worker processes
 | 
			
		||||
#clusterLimit: 1
 | 
			
		||||
 | 
			
		||||
# Job concurrency per worker
 | 
			
		||||
# deliverJobConcurrency: 128;
 | 
			
		||||
# inboxJobConcurrency: 16;
 | 
			
		||||
 | 
			
		||||
# IP address family used for outgoing request (ipv4, ipv6 or dual)
 | 
			
		||||
#outgoingAddressFamily: ipv4
 | 
			
		||||
 | 
			
		||||
# Syslog option
 | 
			
		||||
#syslog:
 | 
			
		||||
#  host: localhost
 | 
			
		||||
#  port: 514
 | 
			
		||||
 | 
			
		||||
# Proxy for HTTP/HTTPS
 | 
			
		||||
#proxy: http://127.0.0.1:3128
 | 
			
		||||
 | 
			
		||||
# Proxy for SMTP/SMTPS
 | 
			
		||||
#proxySmtp: http://127.0.0.1:3128   # use HTTP/1.1 CONNECT
 | 
			
		||||
#proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4
 | 
			
		||||
#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,7 @@
 | 
			
		||||
*.svg -diff -text
 | 
			
		||||
*.psd -diff -text
 | 
			
		||||
*.ai -diff -text
 | 
			
		||||
yarn.lock -diff -text
 | 
			
		||||
package-lock.json -diff -text
 | 
			
		||||
*.mqo -diff -text
 | 
			
		||||
*.glb -diff -text
 | 
			
		||||
*.blend -diff -text
 | 
			
		||||
*.afdesign -diff -text
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,6 @@
 | 
			
		||||
# Visual Studio Code
 | 
			
		||||
/.vscode
 | 
			
		||||
!/.vscode/extensions.json
 | 
			
		||||
 | 
			
		||||
# Intelij-IDEA
 | 
			
		||||
/.idea
 | 
			
		||||
@@ -7,9 +8,6 @@
 | 
			
		||||
# Node.js
 | 
			
		||||
/node_modules
 | 
			
		||||
 | 
			
		||||
# yarn
 | 
			
		||||
yarn.lock
 | 
			
		||||
 | 
			
		||||
# config
 | 
			
		||||
/.config/*
 | 
			
		||||
!/.config/example.yml
 | 
			
		||||
@@ -32,3 +30,10 @@ api-docs.json
 | 
			
		||||
.DS_Store
 | 
			
		||||
/files
 | 
			
		||||
ormconfig.json
 | 
			
		||||
 | 
			
		||||
# blender backups
 | 
			
		||||
*.blend1
 | 
			
		||||
*.blend2
 | 
			
		||||
*.blend3
 | 
			
		||||
*.blend4
 | 
			
		||||
*.blend5
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
v12.6.0
 | 
			
		||||
v12.9.1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							@@ -2,10 +2,10 @@
 | 
			
		||||
	"recommendations": [
 | 
			
		||||
		"ducksoupdev.vue2",
 | 
			
		||||
		"editorconfig.editorconfig",
 | 
			
		||||
		"eg2.tslint",
 | 
			
		||||
		"eg2.vscode-npm-script",
 | 
			
		||||
		"hollowtree.vue-snippets",
 | 
			
		||||
		"ms-vscode.typescript-javascript-grammar",
 | 
			
		||||
		"ms-vscode.vscode-typescript-tslint-plugin",
 | 
			
		||||
		"octref.vetur",
 | 
			
		||||
		"sysoev.language-stylus"
 | 
			
		||||
	]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										190
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										190
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,9 +1,187 @@
 | 
			
		||||
ChangeLog
 | 
			
		||||
=========
 | 
			
		||||
 | 
			
		||||
If you encounter any problems with updating, please try the following:
 | 
			
		||||
1. `npm run clean` or `npm run cleanall`
 | 
			
		||||
2. Retry update (Don't forget `npm i`)
 | 
			
		||||
11.31.4 (2019/09/03)
 | 
			
		||||
--------------------
 | 
			
		||||
### 🐛Fixes
 | 
			
		||||
* 誰がリアクションしたか見れるやつの表示を改善
 | 
			
		||||
 | 
			
		||||
11.31.3 (2019/09/03)
 | 
			
		||||
--------------------
 | 
			
		||||
### 🐛Fixes
 | 
			
		||||
* 誰がリアクションしたか見れるやつの表示を改善
 | 
			
		||||
 | 
			
		||||
11.31.2 (2019/09/03)
 | 
			
		||||
--------------------
 | 
			
		||||
### 🐛Fixes
 | 
			
		||||
* 誰がリアクションしたか見れるやつの表示を改善
 | 
			
		||||
 | 
			
		||||
11.31.1 (2019/09/03)
 | 
			
		||||
--------------------
 | 
			
		||||
### 🐛Fixes
 | 
			
		||||
* 誰がリアクションしたか見れるやつの表示を改善
 | 
			
		||||
 | 
			
		||||
11.31.0 (2019/09/02)
 | 
			
		||||
--------------------
 | 
			
		||||
### ✨Improvements
 | 
			
		||||
* Syslogサポート
 | 
			
		||||
* チャートの同期機能をAPI経由で使えるように
 | 
			
		||||
* SMTPでProxyを使用できるように
 | 
			
		||||
* リアクションにホバーすることで誰がリアクションしたか見れるように
 | 
			
		||||
* リプライ時、返信元のlocalOnly属性を引き継ぐように
 | 
			
		||||
* 引用付き、ローカルのみなどの案内文にアイコン追加
 | 
			
		||||
* AP deliver/inbox job の並列度を変更できるように
 | 
			
		||||
* clusterLimitの既定値を1に
 | 
			
		||||
* AP inbox ジョブの並列度を下げる
 | 
			
		||||
* CWが付いた投稿はAP上でNote.sensitiveフラグを付けるように
 | 
			
		||||
* メモウィジェットの内容を自動で保存するように
 | 
			
		||||
* ページURLが他と重複してたらエラーを投げるように
 | 
			
		||||
* ページURLが空の時エラーを投げるように
 | 
			
		||||
* リアクションが解除されたときはアニメーションしないように
 | 
			
		||||
* 設定の各セクションごとにURLを割り当てるように
 | 
			
		||||
* 管理画面の各セクションごとにURLを割り当てるように
 | 
			
		||||
 | 
			
		||||
### 🐛Fixes
 | 
			
		||||
* 未実装のTLのRenoteクエリを実装
 | 
			
		||||
* タイムラインAPIのexcludeNsfwオプションを実装
 | 
			
		||||
* ユーザーページの投稿一覧の私の投稿にRenoteが表示される問題を修正
 | 
			
		||||
* meta APIでemojiプロパティに不要な情報が含まれているのを修正
 | 
			
		||||
* モバイル版でドライブのファイルを削除したときの挙動がおかしい問題を修正
 | 
			
		||||
* visiblity-chooserにlocalOnly属性が伝わらなかったのを修正
 | 
			
		||||
* 言語指定したときコードブロックが表示されない問題を修正
 | 
			
		||||
* トークのメッセージがはみ出す問題を修正
 | 
			
		||||
* CWの中のサムネイルのサイズが変なのを少し修正
 | 
			
		||||
* リアクションが初めて付いた時のエフェクトが消えている問題を修正
 | 
			
		||||
* 無効になっているスイッチを操作できる問題を修正
 | 
			
		||||
* Mキー連打で画面が真っ暗問題を修正
 | 
			
		||||
* AmazonのURLプレビューが出来ない問題を修正
 | 
			
		||||
* 表記ゆれを修正
 | 
			
		||||
 | 
			
		||||
11.30.0 (2019/08/24)
 | 
			
		||||
--------------------
 | 
			
		||||
### ✨Improvements
 | 
			
		||||
* Room: 家具をすべてしまうボタンを追加
 | 
			
		||||
* Room: カップ麺追加
 | 
			
		||||
* Room: ホログラフィックディスプレイ追加
 | 
			
		||||
* Room: エナジードリンク追加
 | 
			
		||||
* Room: ピンギンの色を変えられるように
 | 
			
		||||
* Room: プレビューの見やすさを向上
 | 
			
		||||
* Room保存時にダイアログを表示するように
 | 
			
		||||
* Roomから移動するときに未保存ならば警告するように
 | 
			
		||||
 | 
			
		||||
### 🐛Fixes
 | 
			
		||||
* MisskeyRoomからページを戻した時、テキスト入力画面で選択位置変更ができない問題を修正
 | 
			
		||||
 | 
			
		||||
11.29.0 (2019/08/19)
 | 
			
		||||
--------------------
 | 
			
		||||
### ✨Improvements
 | 
			
		||||
* Room: ソファ追加
 | 
			
		||||
* Room: 螺旋階段追加
 | 
			
		||||
* Room: ゴミ箱追加
 | 
			
		||||
 | 
			
		||||
### 🐛Fixes
 | 
			
		||||
* Room: 部屋を離れても裏でレンダリングが続く問題を修正
 | 
			
		||||
* Room: アバターのレンダリングを修正
 | 
			
		||||
* Room: ライティングの調整
 | 
			
		||||
 | 
			
		||||
11.28.2 (2019/08/18)
 | 
			
		||||
--------------------
 | 
			
		||||
### 🐛Fixes
 | 
			
		||||
* 他人の部屋なのに部屋編集UIが表示されるのを修正
 | 
			
		||||
* オブジェクトストレージを使用している場合Roomで画像を読み込めない問題を修正
 | 
			
		||||
 | 
			
		||||
11.28.1 (2019/08/18)
 | 
			
		||||
--------------------
 | 
			
		||||
### 🐛Fixes
 | 
			
		||||
* オブジェクトストレージを使用している場合Roomで画像を読み込めない問題を修正
 | 
			
		||||
* Roomで家具を移動など確定せずに「しまう」と部屋ごと消える問題を修正
 | 
			
		||||
 | 
			
		||||
11.28.0 (2019/08/18)
 | 
			
		||||
--------------------
 | 
			
		||||
### ✨Improvements
 | 
			
		||||
* 自分の部屋を作れるように
 | 
			
		||||
* Delキーを押して投稿を削除するときに確認ダイアログを出すように
 | 
			
		||||
* Elasticsearchのインデックス名をconfigで変更できるように
 | 
			
		||||
 | 
			
		||||
### 🐛Fixes
 | 
			
		||||
* コンテンツを遡ってる途中に新しいアイテムが先頭に追加されると上限に達している場合末尾のアイテムが削除される問題を修正
 | 
			
		||||
* ユーザー名が突き抜けるのを修正
 | 
			
		||||
* ユーザー一覧とかでサイレンス・凍結されたユーザーの状態が表示されてなかったのを修正
 | 
			
		||||
* タイトルやアイコンがきちんと設定されないことがあるのを修正
 | 
			
		||||
* ドライブアップロード直後に取得できるURLがoriginalじゃない問題を修正
 | 
			
		||||
* リモートユーザー向けのNoteUnreadsレコードが作成される問題を修正
 | 
			
		||||
* Hashtagがupdateできない問題を修正
 | 
			
		||||
* ハッシュタグの更新がタグの数だけ並列で行われてDBを重くしてしまうことがあるのを修正
 | 
			
		||||
 | 
			
		||||
11.27.1 (2019/08/01)
 | 
			
		||||
--------------------
 | 
			
		||||
### 🐛Fixes
 | 
			
		||||
* オブジェクトストレージに関する問題を修正
 | 
			
		||||
 | 
			
		||||
11.27.0 (2019/07/29)
 | 
			
		||||
--------------------
 | 
			
		||||
### ✨Improvements
 | 
			
		||||
* 「削除して編集」機能を追加
 | 
			
		||||
* HTTPリクエストのKeep-AliveとPrxoy対応(サーバーのパフォーマンス向上)
 | 
			
		||||
* 通知を種類でフィルタリングして表示できるように
 | 
			
		||||
* モバイルで通知ページを表示することができるように
 | 
			
		||||
* 非ログイン時の警告処理
 | 
			
		||||
 | 
			
		||||
### 🐛Fixes
 | 
			
		||||
* リモートの絵文字が更新されない問題を修正
 | 
			
		||||
* リンクバリデーションリンクが一瞬表示されてしまう問題を修正
 | 
			
		||||
* 選択していない状態でウィジェットが追加できる問題を修正
 | 
			
		||||
 | 
			
		||||
11.26.2 (2019/07/25)
 | 
			
		||||
--------------------
 | 
			
		||||
### 🐛Fixes
 | 
			
		||||
* すでに使われたことのあるユーザー名を再度使えないように
 | 
			
		||||
* モバイルのウィジェットページが常に i/update-client-setting を呼び続ける問題を修正
 | 
			
		||||
* 投稿フォームのヘッダに添付ファイル数がちゃんと表示されない問題を修正
 | 
			
		||||
 | 
			
		||||
11.26.1 (2019/07/21)
 | 
			
		||||
--------------------
 | 
			
		||||
### 🐛Fixes
 | 
			
		||||
* リモートアンケートの期限が保存されないのを修正
 | 
			
		||||
* 自分をブロックしているユーザーがおすすめユーザーに表示されている問題を修正
 | 
			
		||||
* スクロールしてると絵文字ピッカーの位置がずれる問題を修正
 | 
			
		||||
* 投稿フォームが画面外にはみ出さないように
 | 
			
		||||
* 投稿フォームの「引用付き」の表示が見づらい問題の修正
 | 
			
		||||
* 投稿フォームにもうリノートや引用ノートのデータがある場合はリンクを貼っても「引用として添付しますか?」のダイアログボックスが出ないように
 | 
			
		||||
* 「タイムライン上部に投稿フォームを表示する」機能の使用時、ノートを投稿しても引用ノートのデータが残る問題の修正
 | 
			
		||||
* デスクトップ版のアクティビティウィジェットの日付とデータの表示が変だったのを修正
 | 
			
		||||
 | 
			
		||||
11.26.0 (2019/07/19)
 | 
			
		||||
--------------------
 | 
			
		||||
### ✨Improvements
 | 
			
		||||
* モデレーターログを記録して確認できるように
 | 
			
		||||
* プロフィールに追加情報を設定できるように
 | 
			
		||||
* Mastodonのリンクの所有者認証に対応
 | 
			
		||||
* AP: Delete Person アクティビティを配信するように
 | 
			
		||||
* AP: Delete アクティビティの後にフォロー解除するように
 | 
			
		||||
* AP: アカウント削除でもDelete activityを配信するように
 | 
			
		||||
* Pages: ラジオボタンを追加
 | 
			
		||||
* AdminページのUsers Viewでユーザーのレコードをクリックすることですぐユーザーを照会できるように
 | 
			
		||||
* AdminページのUsers Viewでユーザー一覧からユーザー名とホスト情報で検索できるように
 | 
			
		||||
* 特定ホストへのメンションの特別処理をクライアントに追加
 | 
			
		||||
* 設定画面でデスクトップ・モバイルモード変更時はすぐにrefreshするか伺うように
 | 
			
		||||
* ペーストされたファイル名のテンプレート変更時すぐどのようになるか見れるように
 | 
			
		||||
* (コ`・ヘ・´ケ)を追加
 | 
			
		||||
 | 
			
		||||
### 🐛Fixes
 | 
			
		||||
* ログインのログが正しく保存されない問題を修正
 | 
			
		||||
* 同じユーザー名のユーザーが作成できてしまうことがある問題を修正
 | 
			
		||||
* 報告されたレポート内容が表示されない問題を修正
 | 
			
		||||
* リモートのプロフィールの追加情報が表示されない問題を修正
 | 
			
		||||
* 「見つける」のタグが大文字小文字区別されている問題を修正
 | 
			
		||||
* 管理画面のインスタンス一覧でソートが正しく機能していない問題を修正
 | 
			
		||||
* プロフィール設定でバナーに動画を設定すると以降編集ができない問題を修正
 | 
			
		||||
* ウェブ検索エンジンの設定でグリッチが発生する問題を修正
 | 
			
		||||
* MFMの引用がインライン表示になっている問題を修正
 | 
			
		||||
* アンケートの期限入力部分のタイトル表示がおかしい問題を修正
 | 
			
		||||
* 画面上の項目がすべていなくなると実際はロードされてないだけのファイルやフォルダーがあるにも関わらず「もっと読み込む」ボタンがなくなり「このフォルダーは空です」っていうplaceholderが表示されてしまう問題を修正
 | 
			
		||||
* proxy-media後のContent-Typeが違う問題を修正
 | 
			
		||||
* ビルド時にエラーが出るのを修正
 | 
			
		||||
 | 
			
		||||
11.25.1 (2019/07/09)
 | 
			
		||||
--------------------
 | 
			
		||||
@@ -641,9 +819,9 @@ mongodb:
 | 
			
		||||
  db: misskey
 | 
			
		||||
```
 | 
			
		||||
3. migration ブランチに切り替え
 | 
			
		||||
4. `npm i`
 | 
			
		||||
5. `npm run build`
 | 
			
		||||
6. `npm run migrate`
 | 
			
		||||
4. `yarn install`
 | 
			
		||||
5. `yarn build`
 | 
			
		||||
6. `yarn migrate`
 | 
			
		||||
7. master ブランチに戻す
 | 
			
		||||
8. enjoy
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,18 +7,24 @@ Feature suggestions and bug reports are filed in https://github.com/syuilo/missk
 | 
			
		||||
* Please search existing issues to avoid duplication. If your issue is already filed, please add your reaction or comment to the existing one.
 | 
			
		||||
* If you have multiple independent issues, please submit them separately.
 | 
			
		||||
 | 
			
		||||
## Branches
 | 
			
		||||
* **master** branch is tracking the latest release and used for production purposes.
 | 
			
		||||
* **develop** branch is where we work for the next release.
 | 
			
		||||
* **l10n_develop** branch is reserved for localization management.
 | 
			
		||||
 | 
			
		||||
## Localization (l10n)
 | 
			
		||||
Misskey uses [Crowdin](https://crowdin.com/project/misskey) for localization management.
 | 
			
		||||
You can improve our translations with your Crowdin account.
 | 
			
		||||
Changes you make in Crowdin will be merged into develop branch.
 | 
			
		||||
Your changes in Crowdin are automatically submitted as a PR (with the title "New Crowdin translations") to the repository.
 | 
			
		||||
The owner [@syuilo](https://github.com/syuilo) merges the PR into the develop branch before the next release.
 | 
			
		||||
 | 
			
		||||
If you can't find the language you want to contribute with, please open an issue.
 | 
			
		||||
If your language is not listed in Crowdin, please open an issue.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## Internationalization (i18n)
 | 
			
		||||
Misskey uses [vue-i18n](https://github.com/kazupon/vue-i18n).
 | 
			
		||||
Misskey uses the Vue.js plugin [Vue I18n](https://github.com/kazupon/vue-i18n).
 | 
			
		||||
Documentation of Vue I18n is available at http://kazupon.github.io/vue-i18n/introduction.html .
 | 
			
		||||
 | 
			
		||||
## Documentation
 | 
			
		||||
* Documents for contributors are located in [`/docs`](/docs).
 | 
			
		||||
@@ -29,9 +35,25 @@ Misskey uses [vue-i18n](https://github.com/kazupon/vue-i18n).
 | 
			
		||||
* Test codes are located in [`/test`](/test).
 | 
			
		||||
 | 
			
		||||
## Continuous integration
 | 
			
		||||
Misskey uses CircleCI for automated test.
 | 
			
		||||
Misskey uses CircleCI for executing automated tests.
 | 
			
		||||
Configuration files are located in [`/.circleci`](/.circleci).
 | 
			
		||||
 | 
			
		||||
## Adding MisskeyRoom items
 | 
			
		||||
* Use English for material, object and texture names.
 | 
			
		||||
* Use meter for unit of length.
 | 
			
		||||
* Your PR should include all source files (e.g. `.png`, `.blend`) of your models (for later editing).
 | 
			
		||||
* Your PR must include the glTF binary files (`.glb`) of your models.
 | 
			
		||||
* Add a locale key `room.furnitures.YOUR_ITEM` at [`/locales/ja-JP.yml`](/locales/ja-JP.yml).
 | 
			
		||||
* Add a furniture definition at [`/src/client/app/common/scripts/room/furnitures.json5`](/src/client/app/common/scripts/room/furnitures.json5).
 | 
			
		||||
 | 
			
		||||
If you have no experience on 3D modeling, we suggest to use the free 3DCG software [Blender](https://www.blender.org/).
 | 
			
		||||
You can find information on glTF 2.0 at [glTF 2.0 — Blender Manual]( https://docs.blender.org/manual/en/dev/addons/io_scene_gltf2.html).
 | 
			
		||||
 | 
			
		||||
## FAQ
 | 
			
		||||
### How to resolve conflictions occurred at yarn.lock?
 | 
			
		||||
 | 
			
		||||
Just execute `yarn` to fix it.
 | 
			
		||||
 | 
			
		||||
## Glossary
 | 
			
		||||
### AP
 | 
			
		||||
Stands for _**A**ctivity**P**ub_.
 | 
			
		||||
@@ -51,11 +73,15 @@ Convert な(na) to にゃ(nya)
 | 
			
		||||
#### Denyaize
 | 
			
		||||
Revert Nyaize
 | 
			
		||||
 | 
			
		||||
## Code style
 | 
			
		||||
### セミコロンを省略しない
 | 
			
		||||
ASI Hazardを避けるためでもある
 | 
			
		||||
## TypeScript Coding Style
 | 
			
		||||
### Do not omit semicolons
 | 
			
		||||
This is to avoid Automatic Semicolon Insertion (ASI) hazard.
 | 
			
		||||
 | 
			
		||||
### 中括弧を省略しない
 | 
			
		||||
Ref:
 | 
			
		||||
* https://www.ecma-international.org/ecma-262/#sec-automatic-semicolon-insertion
 | 
			
		||||
* https://github.com/tc39/ecma262/pull/1062
 | 
			
		||||
 | 
			
		||||
### Do not omit curly brackets
 | 
			
		||||
Bad:
 | 
			
		||||
``` ts
 | 
			
		||||
if (foo)
 | 
			
		||||
@@ -73,18 +99,38 @@ if (foo) {
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
ただし**`if`が一行**の時だけは省略しても良い
 | 
			
		||||
As a special case, you can omit the curly brackets if
 | 
			
		||||
 | 
			
		||||
* the body of the `if`-statement have only one statement and,
 | 
			
		||||
* the `if`-statement does not have `else`-clause.
 | 
			
		||||
 | 
			
		||||
Good:
 | 
			
		||||
``` ts
 | 
			
		||||
if (foo) bar;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### `export default`を使わない
 | 
			
		||||
インテリセンスと相性が悪かったりするため
 | 
			
		||||
Make sure that the condition and the body statement are on the same line.
 | 
			
		||||
 | 
			
		||||
参考:
 | 
			
		||||
* https://gfx.hatenablog.com/entry/2017/11/24/135343
 | 
			
		||||
### Do not use `==` when it can simply be replaced with `===`.
 | 
			
		||||
🥰
 | 
			
		||||
 | 
			
		||||
### Use only boolean (or null related) values in the condition of an `if`-statement.
 | 
			
		||||
Bad:
 | 
			
		||||
``` ts
 | 
			
		||||
if (foo.length)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Good:
 | 
			
		||||
``` ts
 | 
			
		||||
if (foo.length > 0)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Do not use `export default`
 | 
			
		||||
This is because the current language support does not work well with `export default`.
 | 
			
		||||
 | 
			
		||||
Ref:
 | 
			
		||||
* https://basarat.gitbooks.io/typescript/docs/tips/defaultIsBad.html
 | 
			
		||||
* https://gfx.hatenablog.com/entry/2017/11/24/135343
 | 
			
		||||
 | 
			
		||||
Bad:
 | 
			
		||||
``` ts
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
FROM node:12.6-alpine AS base
 | 
			
		||||
FROM node:12.9.1-alpine AS base
 | 
			
		||||
 | 
			
		||||
ENV NODE_ENV=production
 | 
			
		||||
 | 
			
		||||
@@ -23,9 +23,9 @@ RUN apk add --no-cache \
 | 
			
		||||
    zlib-dev
 | 
			
		||||
 | 
			
		||||
COPY package.json ./
 | 
			
		||||
RUN npm i
 | 
			
		||||
RUN yarn install
 | 
			
		||||
COPY . ./
 | 
			
		||||
RUN npm run build
 | 
			
		||||
RUN yarn build
 | 
			
		||||
 | 
			
		||||
FROM base AS runner
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								README.md
									
									
									
									
									
								
							@@ -108,74 +108,64 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan" width="100"></td>
 | 
			
		||||
<td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi" width="100"></td>
 | 
			
		||||
<td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1.jpe?token-time=2145916800&token-hash=bqwLTk0Wo0hUJJ8J5y7ii05bLzz-_CDA7Bo0Mp4RFU0%3D" alt="ne_moni" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/4.jpe?token-time=2145916800&token-hash=zEyJqVM7u9d8Ri-65fJYSJcWF1jBH1nJ5a3taRzrTmw%3D" alt="Melilot" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon" width="100"></td>
 | 
			
		||||
</tr><tr>
 | 
			
		||||
<td><a href="https://www.patreon.com/rane_hs">Hiroshi Seki</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/weepjp">weepjp</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=19045173">kiritan</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=776209">Denshi</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=557245">mkatze</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/osapon">osapon</a></td>
 | 
			
		||||
</tr></table>
 | 
			
		||||
<table><tr>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon" width="100"></td>
 | 
			
		||||
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1.png?token-time=2145916800&token-hash=9nEQje_eMvUjq9a7L3uBqW-MQbS-rRMaMgd7UYVoFNM%3D" alt="mydarkstar" width="100"></td>
 | 
			
		||||
<td><img src="https://c8.patreon.com/2/200/12718187" alt="Peter G." width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1.jpe?token-time=2145916800&token-hash=UQRWf01TwHDV4Cls1K0YAOAjM29ssif7hLVq0ESQ0hs%3D" alt="nemu" width="100"></td>
 | 
			
		||||
<td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td>
 | 
			
		||||
<td><img src="https://c8.patreon.com/2/200/17463605" alt="Sampot" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td>
 | 
			
		||||
</tr><tr>
 | 
			
		||||
<td><a href="https://www.patreon.com/osapon">osapon</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=12718187">Peter G.</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=13039004">nemu</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=17463605">Sampot</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td>
 | 
			
		||||
</tr></table>
 | 
			
		||||
<table><tr>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpe?token-time=2145916800&token-hash=CPxGQhKIlEaa6WUcgbyHixyKEhakiw9RFdOhsIJBQ_o%3D" alt="takimura" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/83884b38afc74d4cbe83c30a13b10edd/1.png?token-time=2145916800&token-hash=R5Tog8RWg0rguRoCIoir3lThokrdPvs8Utfikhc0nhY%3D" alt="Atsuko Tominaga" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%3D" alt="Atsuko Tominaga" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1.jpe?token-time=2145916800&token-hash=EWxXhVbZYH7KB4IDT3joc8TbIg8zPO40x1r5IDn3R7c%3D" alt="Hiratake" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpe?token-time=2145916800&token-hash=qA8j97lIZNc-74AuZ0p4F3ms6sKPeKjtNt2vEuwpsyo%3D" alt="Hekovic" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/10789744/97175095d8f04c0f86225ff47cb98d40/1.jpeg?token-time=2145916800&token-hash=l4AoMR7Nj7K4yAHrkrk2hAoggPkbSPm12m1nmbe9Pb8%3D" alt="Naoki Hirayama" width="100"></td>
 | 
			
		||||
</tr><tr>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/takimura">takimura</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</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>
 | 
			
		||||
<td><a href="https://www.patreon.com/noellabo">noellabo</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/Corset">CG</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/hekovic">Hekovic</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/spinlock">Naoki Hirayama</a></td>
 | 
			
		||||
</tr></table>
 | 
			
		||||
<table><tr>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1.jpeg?token-time=2145916800&token-hash=L55UhJ0rcuNAH3w_ryeeGN4hC6taoOixyAhraEi0bzw%3D" alt="dansup" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1.jpeg?token-time=2145916800&token-hash=d8jBQLMOHD87KtXs5C9fk1o58DMF73pQ-dYH3uZJPBE%3D" alt="Gargron" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
 | 
			
		||||
<td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
 | 
			
		||||
</tr><tr>
 | 
			
		||||
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/mastodon">Gargron</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=23932002">nenohi</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
 | 
			
		||||
</tr></table>
 | 
			
		||||
 | 
			
		||||
**Last updated:** Mon, 01 Jul 2019 21:44:06 UTC
 | 
			
		||||
**Last updated:** Sun, 01 Sep 2019 22:11:05 UTC
 | 
			
		||||
<!-- PATREON_END -->
 | 
			
		||||
 | 
			
		||||
:four_leaf_clover: Copyright
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 148 KiB  | 
@@ -68,7 +68,7 @@ Build misskey with the following:
 | 
			
		||||
*5.* Init DB
 | 
			
		||||
----------------------------------------------------------------
 | 
			
		||||
``` shell
 | 
			
		||||
docker-compose run --rm web npm run init
 | 
			
		||||
docker-compose run --rm web yarn run init
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
*6.* That is it.
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,7 @@ cp docker_example.env docker.env
 | 
			
		||||
*5.* データベースを初期化
 | 
			
		||||
----------------------------------------------------------------
 | 
			
		||||
``` shell
 | 
			
		||||
docker-compose run --rm web npm run init
 | 
			
		||||
docker-compose run --rm web yarn run init
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
*6.* 以上です!
 | 
			
		||||
 
 | 
			
		||||
@@ -25,10 +25,9 @@ server {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
server {
 | 
			
		||||
    listen 443 http2;
 | 
			
		||||
    listen [::]:443 http2;
 | 
			
		||||
    listen 443 ssl http2;
 | 
			
		||||
    listen [::]:443 ssl http2;
 | 
			
		||||
    server_name example.tld;
 | 
			
		||||
    ssl on;
 | 
			
		||||
    ssl_session_cache shared:ssl_session_cache:10m;
 | 
			
		||||
 | 
			
		||||
    # To use Let's Encrypt certificate
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@ Please install and setup these softwares:
 | 
			
		||||
* **[Redis](https://redis.io/)**
 | 
			
		||||
 | 
			
		||||
##### Optional
 | 
			
		||||
* [Yarn](https://yarnpkg.com/) *Optional but recommended for security reason. If you won't install it, use `npx yarn` instead of `yarn`.*
 | 
			
		||||
* [Elasticsearch](https://www.elastic.co/) - required to enable the search feature
 | 
			
		||||
* [FFmpeg](https://www.ffmpeg.org/)
 | 
			
		||||
 | 
			
		||||
@@ -50,7 +51,7 @@ Please install and setup these softwares:
 | 
			
		||||
 | 
			
		||||
5. Install misskey dependencies.
 | 
			
		||||
 | 
			
		||||
	`npm install`
 | 
			
		||||
	`yarn`
 | 
			
		||||
 | 
			
		||||
*4.* Configure Misskey
 | 
			
		||||
----------------------------------------------------------------
 | 
			
		||||
@@ -65,21 +66,20 @@ Please install and setup these softwares:
 | 
			
		||||
 | 
			
		||||
Build misskey with the following:
 | 
			
		||||
 | 
			
		||||
`NODE_ENV=production npm run build`
 | 
			
		||||
`NODE_ENV=production yarn build`
 | 
			
		||||
 | 
			
		||||
If you're on Debian, you will need to install the `build-essential`, `python` package.
 | 
			
		||||
 | 
			
		||||
If you're still encountering errors about some modules, use node-gyp:
 | 
			
		||||
 | 
			
		||||
1. `npm install -g node-gyp`
 | 
			
		||||
2. `node-gyp configure`
 | 
			
		||||
3. `node-gyp build`
 | 
			
		||||
4. `NODE_ENV=production npm run build`
 | 
			
		||||
1. `npx node-gyp configure`
 | 
			
		||||
2. `npx node-gyp build`
 | 
			
		||||
3. `NODE_ENV=production yarn build`
 | 
			
		||||
 | 
			
		||||
*6.* Init DB
 | 
			
		||||
----------------------------------------------------------------
 | 
			
		||||
``` shell
 | 
			
		||||
npm run init
 | 
			
		||||
yarn run init
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
*7.* That is it.
 | 
			
		||||
@@ -130,12 +130,16 @@ You can check if the service is running with `systemctl status misskey`.
 | 
			
		||||
### How to update your Misskey server to the latest version
 | 
			
		||||
1. `git checkout master`
 | 
			
		||||
2. `git pull`
 | 
			
		||||
3. `npm install`
 | 
			
		||||
4. `NODE_ENV=production npm run build`
 | 
			
		||||
5. `npm run migrate`
 | 
			
		||||
3. `yarn install`
 | 
			
		||||
4. `NODE_ENV=production yarn build`
 | 
			
		||||
5. `yarn migrate`
 | 
			
		||||
6. Restart your Misskey process to apply changes
 | 
			
		||||
7. Enjoy
 | 
			
		||||
 | 
			
		||||
If you encounter any problems with updating, please try the following:
 | 
			
		||||
1. `yarn clean` or `yarn cleanall`
 | 
			
		||||
2. Retry update (Don't forget `yarn install`
 | 
			
		||||
 | 
			
		||||
----------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
If you have any questions or troubles, feel free to contact us!
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,8 @@ Installez les paquets suivants :
 | 
			
		||||
* **[Redis](https://redis.io/)**
 | 
			
		||||
 | 
			
		||||
##### Optionnels
 | 
			
		||||
* [Elasticsearch](https://www.elastic.co/) - requis pour pouvoir activer la fonctionnalité de recherche
 | 
			
		||||
* [Yarn](https://yarnpkg.com/) - *recommander pour des raisons de sécurité. Si vous ne l'installez pas, utilisez `npx yarn` au lieu de` yarn`.*
 | 
			
		||||
* [Elasticsearch](https://www.elastic.co/) - *requis pour pouvoir activer la fonctionnalité de recherche.*
 | 
			
		||||
* [FFmpeg](https://www.ffmpeg.org/)
 | 
			
		||||
 | 
			
		||||
*3.* Installation de Misskey
 | 
			
		||||
@@ -50,7 +51,7 @@ Installez les paquets suivants :
 | 
			
		||||
 
 | 
			
		||||
5. Installez les dépendances de misskey.
 | 
			
		||||
 | 
			
		||||
	`npm install`
 | 
			
		||||
	`yarn install`
 | 
			
		||||
 | 
			
		||||
*4.* Création du fichier de configuration
 | 
			
		||||
----------------------------------------------------------------
 | 
			
		||||
@@ -65,23 +66,22 @@ Installez les paquets suivants :
 | 
			
		||||
 | 
			
		||||
Construisez Misskey comme ceci :
 | 
			
		||||
 | 
			
		||||
`NODE_ENV=production npm run build`
 | 
			
		||||
`NODE_ENV=production yarn build`
 | 
			
		||||
 | 
			
		||||
Si vous êtes sous Debian, vous serez amené à installer les paquets `build-essential` et `python`.
 | 
			
		||||
 | 
			
		||||
Si vous rencontrez des erreurs concernant certains modules, utilisez node-gyp:
 | 
			
		||||
 | 
			
		||||
1. `npm install -g node-gyp`
 | 
			
		||||
2. `node-gyp configure`
 | 
			
		||||
3. `node-gyp build`
 | 
			
		||||
4. `NODE_ENV=production npm run build`
 | 
			
		||||
1. `npx node-gyp configure`
 | 
			
		||||
2. `npx node-gyp build`
 | 
			
		||||
3. `NODE_ENV=production yarn build`
 | 
			
		||||
 | 
			
		||||
*6.* C'est tout.
 | 
			
		||||
----------------------------------------------------------------
 | 
			
		||||
Excellent ! Maintenant, vous avez un environnement prêt pour lancer Misskey
 | 
			
		||||
 | 
			
		||||
### Lancement conventionnel
 | 
			
		||||
Lancez tout simplement `NODE_ENV=production npm start`. Bonne chance et amusez-vous bien !
 | 
			
		||||
Lancez tout simplement `NODE_ENV=production yarn start`. Bonne chance et amusez-vous bien !
 | 
			
		||||
 | 
			
		||||
### Démarrage avec systemd
 | 
			
		||||
 | 
			
		||||
@@ -124,9 +124,9 @@ Vous pouvez vérifier si le service a démarré en utilisant la commande `system
 | 
			
		||||
### Méthode de mise à jour vers la plus récente version de Misskey
 | 
			
		||||
1. `git checkout master`
 | 
			
		||||
2. `git pull`
 | 
			
		||||
3. `npm install`
 | 
			
		||||
4. `NODE_ENV=production npm run build`
 | 
			
		||||
5. `npm run migrate`
 | 
			
		||||
3. `yarn install`
 | 
			
		||||
4. `NODE_ENV=production yarn build`
 | 
			
		||||
5. `yarn migrate`
 | 
			
		||||
 | 
			
		||||
----------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,8 @@ adduser --disabled-password --disabled-login misskey
 | 
			
		||||
* **[Redis](https://redis.io/)**
 | 
			
		||||
 | 
			
		||||
##### オプション
 | 
			
		||||
* [Yarn](https://yarnpkg.com/)
 | 
			
		||||
	* セキュリティの観点から推奨されます。 yarn をインストールしない方針の場合は、文章中の `yarn` を適宜 `npx yarn` と読み替えてください。
 | 
			
		||||
* [Elasticsearch](https://www.elastic.co/)
 | 
			
		||||
	* 検索機能を有効にするためにはインストールが必要です。
 | 
			
		||||
* [FFmpeg](https://www.ffmpeg.org/)
 | 
			
		||||
@@ -51,7 +53,7 @@ adduser --disabled-password --disabled-login misskey
 | 
			
		||||
 | 
			
		||||
5. Misskeyの依存パッケージをインストール
 | 
			
		||||
 | 
			
		||||
	`npm install`
 | 
			
		||||
	`yarn install`
 | 
			
		||||
 | 
			
		||||
*4.* 設定ファイルを作成する
 | 
			
		||||
----------------------------------------------------------------
 | 
			
		||||
@@ -66,20 +68,19 @@ adduser --disabled-password --disabled-login misskey
 | 
			
		||||
 | 
			
		||||
次のコマンドでMisskeyをビルドしてください:
 | 
			
		||||
 | 
			
		||||
`NODE_ENV=production npm run build`
 | 
			
		||||
`NODE_ENV=production yarn build`
 | 
			
		||||
 | 
			
		||||
Debianをお使いであれば、`build-essential`パッケージをインストールする必要があります。
 | 
			
		||||
 | 
			
		||||
何らかのモジュールでエラーが発生する場合はnode-gypを使ってください:
 | 
			
		||||
1. `npm install -g node-gyp`
 | 
			
		||||
2. `node-gyp configure`
 | 
			
		||||
3. `node-gyp build`
 | 
			
		||||
4. `NODE_ENV=production npm run build`
 | 
			
		||||
1. `npx node-gyp configure`
 | 
			
		||||
2. `npx node-gyp build`
 | 
			
		||||
3. `NODE_ENV=production yarn build`
 | 
			
		||||
 | 
			
		||||
*6.* データベースを初期化
 | 
			
		||||
----------------------------------------------------------------
 | 
			
		||||
``` shell
 | 
			
		||||
npm run init
 | 
			
		||||
yarn run init
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
*7.* 以上です!
 | 
			
		||||
@@ -87,7 +88,7 @@ npm run init
 | 
			
		||||
お疲れ様でした。これでMisskeyを動かす準備は整いました。
 | 
			
		||||
 | 
			
		||||
### 通常起動
 | 
			
		||||
`NODE_ENV=production npm start`するだけです。GLHF!
 | 
			
		||||
`NODE_ENV=production yarn start`するだけです。GLHF!
 | 
			
		||||
 | 
			
		||||
### systemdを用いた起動
 | 
			
		||||
1. systemdサービスのファイルを作成
 | 
			
		||||
@@ -120,7 +121,7 @@ npm run init
 | 
			
		||||
 | 
			
		||||
3. systemdを再読み込みしmisskeyサービスを有効化
 | 
			
		||||
 | 
			
		||||
	`systemctl daemon-reload ; systemctl enable misskey`
 | 
			
		||||
	`systemctl daemon-reload; systemctl enable misskey`
 | 
			
		||||
 | 
			
		||||
4. misskeyサービスの起動
 | 
			
		||||
 | 
			
		||||
@@ -131,11 +132,11 @@ npm run init
 | 
			
		||||
### Misskeyを最新バージョンにアップデートする方法:
 | 
			
		||||
1. `git checkout master`
 | 
			
		||||
2. `git pull`
 | 
			
		||||
3. `npm install`
 | 
			
		||||
4. `NODE_ENV=production npm run build`
 | 
			
		||||
5. `npm run migrate`
 | 
			
		||||
3. `yarn install`
 | 
			
		||||
4. `NODE_ENV=production yarn build`
 | 
			
		||||
5. `yarn migrate`
 | 
			
		||||
 | 
			
		||||
なにか問題が発生した場合は、`npm run clean`または`npm run cleanall`すると直る場合があります。
 | 
			
		||||
なにか問題が発生した場合は、`yarn clean`または`yarn cleanall`すると直る場合があります。
 | 
			
		||||
 | 
			
		||||
----------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,11 @@ gulp.task('build:copy:views', () =>
 | 
			
		||||
	gulp.src('./src/server/web/views/**/*').pipe(gulp.dest('./built/server/web/views'))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task('build:copy', gulp.parallel('build:copy:views', () =>
 | 
			
		||||
gulp.task('build:copy:fonts', () =>
 | 
			
		||||
	gulp.src('./node_modules/three/examples/fonts/**/*').pipe(gulp.dest('./built/client/assets/fonts/'))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task('build:copy', gulp.parallel('build:copy:views', 'build:copy:fonts', () =>
 | 
			
		||||
	gulp.src([
 | 
			
		||||
		'./src/const.json',
 | 
			
		||||
		'./src/server/web/views/**/*',
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,19 @@ common:
 | 
			
		||||
  signup: "Registrovat"
 | 
			
		||||
  signout: "Odhlásit"
 | 
			
		||||
  reload-to-apply-the-setting: "Pro uplatnění tohoto nastavení musíte znovu načíst tuto stránku. Chcete ji načíst teď?"
 | 
			
		||||
  delete-confirm: "Opravdu chcete smazat tento příspěvek?"
 | 
			
		||||
  signin-required: "Přihlašte se, prosím"
 | 
			
		||||
  notification-type: "Typy oznámení"
 | 
			
		||||
  notification-types:
 | 
			
		||||
    all: "Všechny"
 | 
			
		||||
    pollVote: "Hlasy"
 | 
			
		||||
    follow: "Sledovaní"
 | 
			
		||||
    receiveFollowRequest: "Žádost o sledování"
 | 
			
		||||
    reply: "Odpovědi"
 | 
			
		||||
    quote: "Citace"
 | 
			
		||||
    renote: "Renotovat"
 | 
			
		||||
    mention: "Zmínky"
 | 
			
		||||
    reaction: "Reakce"
 | 
			
		||||
  got-it: "Rozumím!"
 | 
			
		||||
  customization-tips:
 | 
			
		||||
    title: "Tipy pro přizpůsobení"
 | 
			
		||||
@@ -89,6 +102,7 @@ common:
 | 
			
		||||
    "read:notifications": "Prohlížet oznámení"
 | 
			
		||||
    "write:notifications": "Pracovat s oznámeními"
 | 
			
		||||
    "read:reactions": "Prohlížet reakce"
 | 
			
		||||
    "write:reactions": "Narabět s reakcemi"
 | 
			
		||||
    "write:votes": "Hlasovat"
 | 
			
		||||
  empty-timeline-info:
 | 
			
		||||
    follow-users-to-make-your-timeline: "Poznámky sledujících se zobrazí ve vaší časové ose"
 | 
			
		||||
@@ -98,6 +112,7 @@ common:
 | 
			
		||||
    hide-contents: "Schovat obsah"
 | 
			
		||||
    reply-placeholder: "Odpovědět na tento příspěvek"
 | 
			
		||||
    quote-placeholder: "Citovat tento příspěvek"
 | 
			
		||||
    quote-attached: "Přiložit citaci"
 | 
			
		||||
    submit: "Odeslat"
 | 
			
		||||
    reply: "Odpovědět"
 | 
			
		||||
    renote: "Renotovat"
 | 
			
		||||
@@ -107,12 +122,15 @@ common:
 | 
			
		||||
    create-poll: "Vytvořit anketu"
 | 
			
		||||
    text-remain: "zbývá ještě {} znaků"
 | 
			
		||||
    recent-tags: "Nejnovější"
 | 
			
		||||
    click-to-tagging: "Klikni pro otágování"
 | 
			
		||||
    visibility: "Viditelnost"
 | 
			
		||||
    geolocation-alert: "Vaše zařízení nedalo k dispozici lokaci"
 | 
			
		||||
    error: "Chyba"
 | 
			
		||||
    enter-username: "Zadejte své uživatelské jméno"
 | 
			
		||||
    specified-recipient: "Pro"
 | 
			
		||||
    add-visible-user: "Přidat uživatele"
 | 
			
		||||
    username-prompt: "Zadejte své uživatelské jméno"
 | 
			
		||||
    enter-file-name: "Upravit název souboru"
 | 
			
		||||
  weekday-short:
 | 
			
		||||
    sunday: "Ne"
 | 
			
		||||
    monday: "Po"
 | 
			
		||||
@@ -179,6 +197,7 @@ common:
 | 
			
		||||
    remember-note-visibility: "Zapamatovat viditelnost příspěvků"
 | 
			
		||||
    web-search-engine: "Webové vyhledávače"
 | 
			
		||||
    web-search-engine-desc: "Například: https://www.google.com/?#q={{query}}"
 | 
			
		||||
    paste: "Vložit"
 | 
			
		||||
    keep-cw: "Zachovat varování o obsahu"
 | 
			
		||||
    keep-cw-desc: "Při odpovědi na příspěvek bude varování o obsahu nastaveno stejně jako původní příspěvek."
 | 
			
		||||
    i-like-sushi: "Mam radši sushi (než puding)"
 | 
			
		||||
@@ -216,7 +235,7 @@ common:
 | 
			
		||||
    deck-column-width-wide: "Široké"
 | 
			
		||||
    use-shadow: "Používat v rozhraní stíny"
 | 
			
		||||
    rounded-corners: "Zakulatit rohy v rozhraní"
 | 
			
		||||
    circle-icons: "Používat kulaté ikony"
 | 
			
		||||
    circle-icons: "Používat kulaté avatary"
 | 
			
		||||
    contrasted-acct: "Přidat uživatelskému účtu kontrast"
 | 
			
		||||
    wallpaper: "Obrázek na pozadí"
 | 
			
		||||
    choose-wallpaper: "Zvolit pozadí"
 | 
			
		||||
@@ -262,6 +281,17 @@ common:
 | 
			
		||||
    load-raw-images: "Zobrazovat obrázky v původní kvalitě"
 | 
			
		||||
    load-remote-media: "Zobrazovat média ze vzdáleného serveru"
 | 
			
		||||
    sync: "Synchronizace"
 | 
			
		||||
    save: "Uložit"
 | 
			
		||||
    saved: "Uloženo"
 | 
			
		||||
    room: "Místnost"
 | 
			
		||||
    _room:
 | 
			
		||||
      graphicsQuality: "Kvalita grafiky"
 | 
			
		||||
      _graphicsQuality:
 | 
			
		||||
        ultra: "Nejvyšší"
 | 
			
		||||
        high: "Vysoká"
 | 
			
		||||
        medium: "Střední"
 | 
			
		||||
        low: "Nízká"
 | 
			
		||||
        cheep: "Nejnižší"
 | 
			
		||||
  search: "Hledání"
 | 
			
		||||
  delete: "Odstranit"
 | 
			
		||||
  loading: "Načítám..."
 | 
			
		||||
@@ -354,6 +384,7 @@ common/views/components/games/reversi/reversi.vue:
 | 
			
		||||
    cancel: "Zrušit"
 | 
			
		||||
common/views/components/games/reversi/reversi.game.vue:
 | 
			
		||||
  surrender: "Vzdát se"
 | 
			
		||||
  surrendered: "Vzdaním se"
 | 
			
		||||
  looped-map: "Zacyklená mapa"
 | 
			
		||||
common/views/components/games/reversi/reversi.index.vue:
 | 
			
		||||
  title: "Misskey Reversi"
 | 
			
		||||
@@ -387,6 +418,7 @@ common/views/components/connect-failed.vue:
 | 
			
		||||
  title: "Nelze se připojit k serveru"
 | 
			
		||||
  description: "Nastal problém s Vaším připojením k internetu, nebo server není dostupný nebo zrovna probíhá údržba. Prosím {zkuste to znova} za pár minut."
 | 
			
		||||
  thanks: "Děkujeme že jste použili Misskey."
 | 
			
		||||
  troubleshoot: "Odstranění problémů"
 | 
			
		||||
common/views/components/connect-failed.troubleshooter.vue:
 | 
			
		||||
  title: "Poradce při potížích"
 | 
			
		||||
  network: "Síťové připojení"
 | 
			
		||||
@@ -395,6 +427,8 @@ common/views/components/connect-failed.troubleshooter.vue:
 | 
			
		||||
  checking-internet: "Ověřuji připojení k internetu."
 | 
			
		||||
  server: "Připojení k serveru"
 | 
			
		||||
  checking-server: "Spojuji se se serverem"
 | 
			
		||||
  finding: "Vyšetřování problému"
 | 
			
		||||
  no-network: "Žádné připojení k síti"
 | 
			
		||||
  no-network-desc: "Ujistěte se že jste připojeni k Internetu."
 | 
			
		||||
  no-internet: "Nejste připojeni k internetu"
 | 
			
		||||
  no-internet-desc: "Jste připojen k síti, ale zdá se že stále chybí připojení k Internetu. Prosím zkontrolujte Vaše připojení k Internetu."
 | 
			
		||||
@@ -452,9 +486,12 @@ common/views/components/messaging.vue:
 | 
			
		||||
  no-history: "Žádná historie"
 | 
			
		||||
  user: "Uživatel"
 | 
			
		||||
  group: "Skupina"
 | 
			
		||||
  start-with-user: "Zahájit konverzaci s uživatelem"
 | 
			
		||||
  start-with-group: "Zahájit skupinovou konverzaci"
 | 
			
		||||
common/views/components/messaging-room.vue:
 | 
			
		||||
  new-message: "Máte novou zprávu"
 | 
			
		||||
common/views/components/messaging-room.form.vue:
 | 
			
		||||
  input-message-here: "Sem zadejte zprávu"
 | 
			
		||||
  send: "Odeslat"
 | 
			
		||||
  attach-from-local: "Přiložit soubory z Vašeho zařízení"
 | 
			
		||||
common/views/components/messaging-room.message.vue:
 | 
			
		||||
@@ -483,6 +520,7 @@ common/views/components/note-menu.vue:
 | 
			
		||||
  unpin: "Odepnout"
 | 
			
		||||
  delete: "Odstranit"
 | 
			
		||||
  delete-confirm: "Opravdu chcete smazat tento příspěvek?"
 | 
			
		||||
  delete-and-edit: "Smazat a upravit"
 | 
			
		||||
  remote: "Ukázat originální poznámku"
 | 
			
		||||
common/views/components/user-menu.vue:
 | 
			
		||||
  mention: "Zmínění"
 | 
			
		||||
@@ -642,6 +680,7 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  saved: "Profil byl úspěšně aktualizován"
 | 
			
		||||
  uploading: "Nahrávám"
 | 
			
		||||
  upload-failed: "Nahrávání selhalo"
 | 
			
		||||
  unable-to-process: "Operace nemohla být dokončena."
 | 
			
		||||
  email: "Nastavení e-mailů"
 | 
			
		||||
  email-address: "Emailová adresa"
 | 
			
		||||
  email-verified: "Váš e-mail byl ověřen"
 | 
			
		||||
@@ -658,6 +697,9 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  danger-zone: "Nebezpečná zóna"
 | 
			
		||||
  delete-account: "Smazat účet"
 | 
			
		||||
  account-deleted: "Váš účet byl smazán. Může chvilku trvat než zmizí všechna data."
 | 
			
		||||
  profile-metadata: "Metadata profilu"
 | 
			
		||||
  metadata-label: "Popis"
 | 
			
		||||
  metadata-content: "Obsah"
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "Uživatel"
 | 
			
		||||
  rename: "Přejmenovat seznam"
 | 
			
		||||
@@ -731,6 +773,7 @@ desktop:
 | 
			
		||||
  avatar: "Avatar"
 | 
			
		||||
  uploading-avatar: "Nahrál nový avatar"
 | 
			
		||||
  avatar-updated: "Vaše avatar byl aktualizován"
 | 
			
		||||
  unable-to-process: "Operace nemohla být dokončena."
 | 
			
		||||
  invalid-filetype: "Tento formát souboru není podporován"
 | 
			
		||||
desktop/views/components/activity.chart.vue:
 | 
			
		||||
  total: "Černá ... Celkem"
 | 
			
		||||
@@ -910,6 +953,7 @@ desktop/views/components/ui.header.account.vue:
 | 
			
		||||
  lists: "Seznamy"
 | 
			
		||||
  groups: "Skupiny"
 | 
			
		||||
  admin: "Administrace"
 | 
			
		||||
  room: "Místnost"
 | 
			
		||||
desktop/views/components/ui.header.nav.vue:
 | 
			
		||||
  game: "Hry"
 | 
			
		||||
desktop/views/components/ui.header.notifications.vue:
 | 
			
		||||
@@ -1039,6 +1083,8 @@ admin/views/users.vue:
 | 
			
		||||
  reset-password-confirm: "Opravdu chcete resetovat Vaše heslo?"
 | 
			
		||||
  password-updated: "Heslo je nyní \"{password}\""
 | 
			
		||||
  update-remote-user: "Aktualizovat informace o vzdáleném účtu"
 | 
			
		||||
  username: "Přezdívka"
 | 
			
		||||
  host: "Hostitel"
 | 
			
		||||
  users:
 | 
			
		||||
    title: "Uživatel"
 | 
			
		||||
    state:
 | 
			
		||||
@@ -1055,6 +1101,11 @@ admin/views/users.vue:
 | 
			
		||||
admin/views/moderators.vue:
 | 
			
		||||
  add-moderator:
 | 
			
		||||
    title: "Vytvořit moderátora"
 | 
			
		||||
  logs:
 | 
			
		||||
    title: "Logy"
 | 
			
		||||
    moderator: "Moderátoři"
 | 
			
		||||
    type: "Operace"
 | 
			
		||||
    info: "Informace"
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "Přidat emoji"
 | 
			
		||||
@@ -1246,6 +1297,8 @@ mobile/views/pages/search.vue:
 | 
			
		||||
  not-found: "Pro '{q}' nebyly nalezeny žádné příspěvky."
 | 
			
		||||
mobile/views/pages/selectdrive.vue:
 | 
			
		||||
  select-file: "Vybrat soubory"
 | 
			
		||||
mobile/views/pages/notifications.vue:
 | 
			
		||||
  notifications: "Oznámení"
 | 
			
		||||
mobile/views/pages/user/home.vue:
 | 
			
		||||
  activity: "Aktivita"
 | 
			
		||||
  frequently-replied-users: "Častá zmínění"
 | 
			
		||||
@@ -1292,6 +1345,8 @@ pages:
 | 
			
		||||
      _action:
 | 
			
		||||
        _dialog:
 | 
			
		||||
          content: "Obsah"
 | 
			
		||||
    _radioButton:
 | 
			
		||||
      title: "Titulek"
 | 
			
		||||
  script:
 | 
			
		||||
    categories:
 | 
			
		||||
      random: "Náhodně"
 | 
			
		||||
@@ -1312,3 +1367,10 @@ pages:
 | 
			
		||||
        arg1: "Seznamy"
 | 
			
		||||
    types:
 | 
			
		||||
      array: "Seznamy"
 | 
			
		||||
room:
 | 
			
		||||
  translate: "Přesunout"
 | 
			
		||||
  save: "Uložit"
 | 
			
		||||
  saved: "Uloženo"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "Měsíc"
 | 
			
		||||
    bin: "Koš"
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,13 @@ common:
 | 
			
		||||
  signout: "Log ud"
 | 
			
		||||
  reload-to-apply-the-setting: "Denne indstilling slår først igennem, når du har genindlæst siden. Vil du genindlæse siden nu?"
 | 
			
		||||
  fetching-as-ap-object: "Tilladelse til sammenkobling"
 | 
			
		||||
  delete-confirm: "Er du helt sikker på, at du vil slette denne post?"
 | 
			
		||||
  notification-types:
 | 
			
		||||
    all: "Alle"
 | 
			
		||||
    follow: "Følger"
 | 
			
		||||
    reply: "Svar"
 | 
			
		||||
    renote: "Gen-postering"
 | 
			
		||||
    reaction: "Reaktion"
 | 
			
		||||
  got-it: "Det er OK"
 | 
			
		||||
  customization-tips:
 | 
			
		||||
    title: "Tips om tilpasning"
 | 
			
		||||
@@ -206,7 +213,7 @@ common:
 | 
			
		||||
    deck-column-width-wide: "Bred"
 | 
			
		||||
    use-shadow: "Vis skygger"
 | 
			
		||||
    rounded-corners: "Vis afrundede hjørner"
 | 
			
		||||
    circle-icons: "Anvend cykliske ikoner"
 | 
			
		||||
    circle-icons: "Anvend cykliske avatar"
 | 
			
		||||
    contrasted-acct: "Tilføj kontrast til brugerkontoen"
 | 
			
		||||
    wallpaper: "Baggrundsbillede"
 | 
			
		||||
    choose-wallpaper: "Vælg en baggrund"
 | 
			
		||||
@@ -251,6 +258,8 @@ common:
 | 
			
		||||
    disable-via-mobile: "Marker aldrig posten som \"fra mobil\""
 | 
			
		||||
    load-raw-images: "Vis vedhæftede bilag i original kvalitet"
 | 
			
		||||
    load-remote-media: "Vis medie-materiale fra en ekstern server"
 | 
			
		||||
    save: "Gem"
 | 
			
		||||
    saved: "Gemt"
 | 
			
		||||
  search: "Søg"
 | 
			
		||||
  delete: "Slet"
 | 
			
		||||
  loading: "Henter"
 | 
			
		||||
@@ -671,7 +680,7 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  you-can-include-hashtags: "Du må gerne bruge hashtags i din profil beskrivelse"
 | 
			
		||||
  language: "Sprog"
 | 
			
		||||
  birthday: "Fødselsdag"
 | 
			
		||||
  avatar: "Ikon"
 | 
			
		||||
  avatar: "Avatar"
 | 
			
		||||
  banner: "Banner"
 | 
			
		||||
  is-cat: "Denne konto er en Kat"
 | 
			
		||||
  is-bot: "Denne konto er en Bot"
 | 
			
		||||
@@ -684,6 +693,7 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  saved: "Profil er opdateret med succes"
 | 
			
		||||
  uploading: "Overfører"
 | 
			
		||||
  upload-failed: "Fejl ved overførsel"
 | 
			
		||||
  unable-to-process: "Handlingen kunne ikke gennemføres."
 | 
			
		||||
  email: "Email indstillinger"
 | 
			
		||||
  email-address: "Email adresse"
 | 
			
		||||
  email-verified: "Din email er blevet bekræftet"
 | 
			
		||||
@@ -703,6 +713,7 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  danger-zone: "Risici"
 | 
			
		||||
  delete-account: "Slet kontoen"
 | 
			
		||||
  account-deleted: "Kontoen er slettet. Det kan vare lidt, inden alle data forsvinder."
 | 
			
		||||
  metadata-content: "Indhold"
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "Bruger"
 | 
			
		||||
  rename: "Omdøb listen"
 | 
			
		||||
@@ -809,6 +820,7 @@ desktop:
 | 
			
		||||
  uploading-avatar: "Overfør en ny avatar"
 | 
			
		||||
  avatar-updated: "Avatar er overført med succes"
 | 
			
		||||
  choose-avatar: "Vælg et billede til din avatar"
 | 
			
		||||
  unable-to-process: "Handlingen kunne ikke gennemføres."
 | 
			
		||||
  invalid-filetype: "Denne filtype kan ikke benyttes her"
 | 
			
		||||
desktop/views/components/activity.chart.vue:
 | 
			
		||||
  total: "Sort ... Total"
 | 
			
		||||
@@ -1276,6 +1288,8 @@ admin/views/users.vue:
 | 
			
		||||
  remote-user-updated: "Informationen om den eksterne bruger er nu blevet opdateret."
 | 
			
		||||
  delete-all-files: "Slet alle filer"
 | 
			
		||||
  delete-all-files-confirm: "Er du sikker på, at alle filerne skal slettes?"
 | 
			
		||||
  username: "Brugernavn"
 | 
			
		||||
  host: "Vært"
 | 
			
		||||
  users:
 | 
			
		||||
    title: "Bruger"
 | 
			
		||||
    sort:
 | 
			
		||||
@@ -1306,6 +1320,11 @@ admin/views/moderators.vue:
 | 
			
		||||
    added: "Redaktør er oprettet"
 | 
			
		||||
    remove: "Fjern"
 | 
			
		||||
    removed: "Redaktøren er nu fjernet"
 | 
			
		||||
  logs:
 | 
			
		||||
    title: "Logs"
 | 
			
		||||
    moderator: "Redaktører"
 | 
			
		||||
    type: "Drift"
 | 
			
		||||
    info: "Information"
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "Tilføj emoji"
 | 
			
		||||
@@ -1594,6 +1613,8 @@ mobile/views/pages/search.vue:
 | 
			
		||||
  not-found: "Ingen poster fundet for \"{q}\""
 | 
			
		||||
mobile/views/pages/selectdrive.vue:
 | 
			
		||||
  select-file: "Vælg fil(er)"
 | 
			
		||||
mobile/views/pages/notifications.vue:
 | 
			
		||||
  notifications: "Notifikationer"
 | 
			
		||||
mobile/views/pages/settings.vue:
 | 
			
		||||
  signed-in-as: "Logget ind som {}"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
@@ -1715,6 +1736,10 @@ pages:
 | 
			
		||||
        _dialog:
 | 
			
		||||
          content: "Indhold"
 | 
			
		||||
        resetRandom: "Nulstil tilfældigt tal"
 | 
			
		||||
    _radioButton:
 | 
			
		||||
      name: "Variabel navn"
 | 
			
		||||
      title: "Titel"
 | 
			
		||||
      default: "Standard værdi"
 | 
			
		||||
  script:
 | 
			
		||||
    categories:
 | 
			
		||||
      flow: "Kontrol"
 | 
			
		||||
@@ -1885,3 +1910,10 @@ pages:
 | 
			
		||||
    enviromentVariables: "Miljø variabel"
 | 
			
		||||
    pageVariables: "Side element"
 | 
			
		||||
    argVariables: "Input slot"
 | 
			
		||||
room:
 | 
			
		||||
  translate: "Flyt"
 | 
			
		||||
  save: "Gem"
 | 
			
		||||
  saved: "Gemt"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "Måne"
 | 
			
		||||
    bin: "Skraldespand"
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,10 @@ common:
 | 
			
		||||
  signout: "Ausloggen"
 | 
			
		||||
  reload-to-apply-the-setting: "Die Seite muss zum Übernehmen dieser Einstellung aktualisiert werden. Soll die Seite jetzt neu geladen werden?"
 | 
			
		||||
  fetching-as-ap-object: "Hole Daten…"
 | 
			
		||||
  delete-confirm: "Diesen Beitrag löschen?"
 | 
			
		||||
  notification-types:
 | 
			
		||||
    reply: "Antworten"
 | 
			
		||||
    renote: "Anmerkung"
 | 
			
		||||
  got-it: "Verstanden!"
 | 
			
		||||
  customization-tips:
 | 
			
		||||
    title: "Anpassung-Tipps"
 | 
			
		||||
@@ -203,7 +207,7 @@ common:
 | 
			
		||||
    deck-column-width-wide: "Sehr breit"
 | 
			
		||||
    use-shadow: "Nutze Schatten"
 | 
			
		||||
    rounded-corners: "Abgerundete Ecken"
 | 
			
		||||
    circle-icons: "Kreisförmige Icons"
 | 
			
		||||
    circle-icons: "Kreisförmige Avatar"
 | 
			
		||||
    contrasted-acct: "Nutzernamen kontrastreicher darstellen"
 | 
			
		||||
    wallpaper: "Hintergrund"
 | 
			
		||||
    choose-wallpaper: "Hintergrund auswählen"
 | 
			
		||||
@@ -248,6 +252,8 @@ common:
 | 
			
		||||
    disable-via-mobile: "Beitrag nicht als „vom Handy“ markieren"
 | 
			
		||||
    load-raw-images: "Anhänge in voller Größe laden"
 | 
			
		||||
    load-remote-media: "Zeige Inhalte von fremden Servern"
 | 
			
		||||
    save: "Speichern"
 | 
			
		||||
    saved: "Gespeichert"
 | 
			
		||||
  search: "Suche"
 | 
			
		||||
  delete: "Löschen"
 | 
			
		||||
  loading: "Laden"
 | 
			
		||||
@@ -565,6 +571,7 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  avatar: "Avatar"
 | 
			
		||||
  banner: "Banner"
 | 
			
		||||
  save: "Speichern"
 | 
			
		||||
  unable-to-process: "Der Vorgang konnte nicht abgeschlossen werden"
 | 
			
		||||
  export: "Exportieren"
 | 
			
		||||
  import: "Importieren"
 | 
			
		||||
  export-targets:
 | 
			
		||||
@@ -598,6 +605,8 @@ common/views/widgets/memo.vue:
 | 
			
		||||
  save: "Speichern"
 | 
			
		||||
desktop:
 | 
			
		||||
  banner: "Banner"
 | 
			
		||||
  avatar: "Avatar"
 | 
			
		||||
  unable-to-process: "Der Vorgang konnte nicht abgeschlossen werden"
 | 
			
		||||
desktop/views/components/activity.chart.vue:
 | 
			
		||||
  total: "Schwarz ... komplett"
 | 
			
		||||
  notes: "Blau ... Hinweise"
 | 
			
		||||
@@ -782,6 +791,7 @@ admin/views/drive.vue:
 | 
			
		||||
    local: "Lokal"
 | 
			
		||||
  delete: "Löschen"
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  username: "Benutzername"
 | 
			
		||||
  users:
 | 
			
		||||
    origin:
 | 
			
		||||
      local: "Lokal"
 | 
			
		||||
@@ -871,6 +881,8 @@ mobile/views/pages/note.vue:
 | 
			
		||||
  next: "Nächster Kommentar"
 | 
			
		||||
mobile/views/pages/search.vue:
 | 
			
		||||
  search: "Suchen"
 | 
			
		||||
mobile/views/pages/notifications.vue:
 | 
			
		||||
  notifications: "Benachrichtigungen"
 | 
			
		||||
mobile/views/pages/user/home.vue:
 | 
			
		||||
  activity: "Aktivität"
 | 
			
		||||
  keywords: "Schlagwörter"
 | 
			
		||||
@@ -935,3 +947,9 @@ pages:
 | 
			
		||||
        arg1: "Listen"
 | 
			
		||||
    types:
 | 
			
		||||
      array: "Listen"
 | 
			
		||||
room:
 | 
			
		||||
  save: "Speichern"
 | 
			
		||||
  saved: "Gespeichert"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "Mond"
 | 
			
		||||
    bin: "Papierkorb"
 | 
			
		||||
 
 | 
			
		||||
@@ -30,12 +30,25 @@ common:
 | 
			
		||||
  customize-home: "Customize home layout"
 | 
			
		||||
  featured-notes: "Featured notes"
 | 
			
		||||
  dark-mode: "Dark Mode"
 | 
			
		||||
  signin: "Log In"
 | 
			
		||||
  signin: "Login"
 | 
			
		||||
  signup: "Sign up"
 | 
			
		||||
  signout: "Logout"
 | 
			
		||||
  reload-to-apply-the-setting: "You'll need to reload the page to reflect this setting. Do you want to reload it now?"
 | 
			
		||||
  fetching-as-ap-object: "Inquiring to union"
 | 
			
		||||
  fetching-as-ap-object: "Inquiring to fediverse"
 | 
			
		||||
  unfollow-confirm: "Do you want to unfollow {name}?"
 | 
			
		||||
  delete-confirm: "Are you sure you want to delete this post?"
 | 
			
		||||
  signin-required: "Please login"
 | 
			
		||||
  notification-type: "Notification Type"
 | 
			
		||||
  notification-types:
 | 
			
		||||
    all: "All"
 | 
			
		||||
    pollVote: "Votes"
 | 
			
		||||
    follow: "Following"
 | 
			
		||||
    receiveFollowRequest: "Follow requests"
 | 
			
		||||
    reply: "Reply"
 | 
			
		||||
    quote: "Quote"
 | 
			
		||||
    renote: "Renote"
 | 
			
		||||
    mention: "Mentions"
 | 
			
		||||
    reaction: "Reaction"
 | 
			
		||||
  got-it: "Got it!"
 | 
			
		||||
  customization-tips:
 | 
			
		||||
    title: "Customization tips"
 | 
			
		||||
@@ -120,6 +133,7 @@ common:
 | 
			
		||||
    geolocation-alert: "Your device does not provide location services"
 | 
			
		||||
    error: "Error"
 | 
			
		||||
    enter-username: "Please enter username"
 | 
			
		||||
    specified-recipient: "Recipient"
 | 
			
		||||
    add-visible-user: "Add a user"
 | 
			
		||||
    cw-placeholder: "Comments for the post (optional)"
 | 
			
		||||
    username-prompt: "Please enter username"
 | 
			
		||||
@@ -202,6 +216,7 @@ common:
 | 
			
		||||
    use-avatar-reversi-stones: "Use avatar as a stone in reversi"
 | 
			
		||||
    disable-animated-mfm: "Disable animated texts in a post"
 | 
			
		||||
    disable-showing-animated-images: "Do not play animated images"
 | 
			
		||||
    enable-quick-notification-view: "Enable Quick Notification View"
 | 
			
		||||
    suggest-recent-hashtags: "Show recent popular hashtags on the post form"
 | 
			
		||||
    always-show-nsfw: "Always show NSFW contents"
 | 
			
		||||
    always-mark-nsfw: "Always mark posts with media attachments as NSFW"
 | 
			
		||||
@@ -232,7 +247,7 @@ common:
 | 
			
		||||
    deck-column-width-wide: "Wide"
 | 
			
		||||
    use-shadow: "Use shadows in the UI"
 | 
			
		||||
    rounded-corners: "Round the corners of the UI"
 | 
			
		||||
    circle-icons: "Use circular icons"
 | 
			
		||||
    circle-icons: "Use circular avatar icon"
 | 
			
		||||
    contrasted-acct: "Add contrast to user account"
 | 
			
		||||
    wallpaper: "Background image"
 | 
			
		||||
    choose-wallpaper: "Choose a background"
 | 
			
		||||
@@ -278,8 +293,20 @@ common:
 | 
			
		||||
    load-raw-images: "Show attached images in original quality"
 | 
			
		||||
    load-remote-media: "Show media from a remote server"
 | 
			
		||||
    sync: "Sync"
 | 
			
		||||
    save: "Save"
 | 
			
		||||
    saved: "Saved"
 | 
			
		||||
    home-profile: "Home profile"
 | 
			
		||||
    deck-profile: "Deck profile"
 | 
			
		||||
    room: "Room"
 | 
			
		||||
    _room:
 | 
			
		||||
      graphicsQuality: "Graphics Quality"
 | 
			
		||||
      _graphicsQuality:
 | 
			
		||||
        ultra: "Ultra"
 | 
			
		||||
        high: "High"
 | 
			
		||||
        medium: "Medium"
 | 
			
		||||
        low: "Low"
 | 
			
		||||
        cheep: "Cheep"
 | 
			
		||||
      useOrthographicCamera: "Use Orthographic Camera"
 | 
			
		||||
  search: "Search"
 | 
			
		||||
  delete: "Delete"
 | 
			
		||||
  loading: "Loading"
 | 
			
		||||
@@ -364,6 +391,9 @@ common/views/pages/explore.vue:
 | 
			
		||||
  federated: "From the fediverse"
 | 
			
		||||
  explore: "Explore {host}"
 | 
			
		||||
  users-info: "Currently, {users} users are registered here"
 | 
			
		||||
common/views/components/reactions-viewer.details.vue:
 | 
			
		||||
  few-users: "{users} reacted with {reaction}"
 | 
			
		||||
  many-users: "{users}, and {omitted} more reacted with {reaction}"
 | 
			
		||||
common/views/components/url-preview.vue:
 | 
			
		||||
  enable-player: "Enable playback"
 | 
			
		||||
  disable-player: "Close the player"
 | 
			
		||||
@@ -529,6 +559,8 @@ common/views/components/note-menu.vue:
 | 
			
		||||
  unpin: "Unpin"
 | 
			
		||||
  delete: "Delete"
 | 
			
		||||
  delete-confirm: "Are you sure you want to delete this post?"
 | 
			
		||||
  delete-and-edit: "Delete and Edit"
 | 
			
		||||
  delete-and-edit-confirm: "Are you sure you want to delete this note and edit it? You will lose all reactions, renotes and replies to it."
 | 
			
		||||
  remote: "Show original note"
 | 
			
		||||
  pin-limit-exceeded: "You can't pin any more posts."
 | 
			
		||||
common/views/components/user-menu.vue:
 | 
			
		||||
@@ -616,7 +648,7 @@ common/views/components/signin.vue:
 | 
			
		||||
  signin-with-twitter: "Log in with Twitter"
 | 
			
		||||
  signin-with-github: "Sign in with GitHub"
 | 
			
		||||
  signin-with-discord: "Sign in with Discord"
 | 
			
		||||
  login-failed: "Logging in has failed. Make sure you have entered the correct username and password."
 | 
			
		||||
  login-failed: "Unable to log in. The username or password you entered is incorrect."
 | 
			
		||||
  tap-key: "Click on the Security Key to log in"
 | 
			
		||||
  enter-2fa-code: "Enter your verification code"
 | 
			
		||||
common/views/components/signup.vue:
 | 
			
		||||
@@ -710,7 +742,7 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  you-can-include-hashtags: "You can also include hashtags in your profile description."
 | 
			
		||||
  language: "Language"
 | 
			
		||||
  birthday: "Birthday"
 | 
			
		||||
  avatar: "Icon"
 | 
			
		||||
  avatar: "Avatar"
 | 
			
		||||
  banner: "Banner"
 | 
			
		||||
  is-cat: "This account is a Cat"
 | 
			
		||||
  is-bot: "This account is a Bot"
 | 
			
		||||
@@ -723,6 +755,9 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  saved: "Profile updated successfully"
 | 
			
		||||
  uploading: "Uploading"
 | 
			
		||||
  upload-failed: "Failed to upload"
 | 
			
		||||
  unable-to-process: "The operation could not be completed."
 | 
			
		||||
  avatar-not-an-image: "The file you specified as an avatar is not an image"
 | 
			
		||||
  banner-not-an-image: "The file you specified as a banner is not an image"
 | 
			
		||||
  email: "Email settings"
 | 
			
		||||
  email-address: "Email Address"
 | 
			
		||||
  email-verified: "Your email has been verified."
 | 
			
		||||
@@ -742,6 +777,9 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  danger-zone: "Cautious options"
 | 
			
		||||
  delete-account: "Remove the account"
 | 
			
		||||
  account-deleted: "The account has been deleted. It may take some time until all of the data disappears."
 | 
			
		||||
  profile-metadata: "Profile metadata"
 | 
			
		||||
  metadata-label: "Label"
 | 
			
		||||
  metadata-content: "Content"
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "User"
 | 
			
		||||
  rename: "Rename list"
 | 
			
		||||
@@ -851,6 +889,7 @@ desktop:
 | 
			
		||||
  uploading-avatar: "Uploading a new avatar"
 | 
			
		||||
  avatar-updated: "Successfully updated the avatar"
 | 
			
		||||
  choose-avatar: "Select an image for the avatar"
 | 
			
		||||
  unable-to-process: "The operation could not be completed."
 | 
			
		||||
  invalid-filetype: "This filetype is not acceptable here"
 | 
			
		||||
desktop/views/components/activity.chart.vue:
 | 
			
		||||
  total: "Black ... Total"
 | 
			
		||||
@@ -892,7 +931,7 @@ desktop/views/components/drive.file.vue:
 | 
			
		||||
    copy-url: "Copy URL"
 | 
			
		||||
    download: "Download"
 | 
			
		||||
    else-files: "Other"
 | 
			
		||||
    set-as-avatar: "Set as an avatar"
 | 
			
		||||
    set-as-avatar: "Set as avatar"
 | 
			
		||||
    set-as-banner: "Set as a banner"
 | 
			
		||||
    open-in-app: "Open in app"
 | 
			
		||||
    add-app: "Add app"
 | 
			
		||||
@@ -1110,6 +1149,7 @@ desktop/views/components/ui.header.account.vue:
 | 
			
		||||
  groups: "Groups"
 | 
			
		||||
  follow-requests: "Follow requests"
 | 
			
		||||
  admin: "Admin"
 | 
			
		||||
  room: "Room"
 | 
			
		||||
desktop/views/components/ui.header.nav.vue:
 | 
			
		||||
  game: "Games"
 | 
			
		||||
desktop/views/components/ui.header.notifications.vue:
 | 
			
		||||
@@ -1363,6 +1403,8 @@ admin/views/users.vue:
 | 
			
		||||
  remote-user-updated: "The information regarding the remote user has been updated."
 | 
			
		||||
  delete-all-files: "Delete all files"
 | 
			
		||||
  delete-all-files-confirm: "Are you sure that you want to delete all files?"
 | 
			
		||||
  username: "Username"
 | 
			
		||||
  host: "Host"
 | 
			
		||||
  users:
 | 
			
		||||
    title: "Users"
 | 
			
		||||
    sort:
 | 
			
		||||
@@ -1393,6 +1435,12 @@ admin/views/moderators.vue:
 | 
			
		||||
    added: "Registered a Moderator."
 | 
			
		||||
    remove: "Discharge"
 | 
			
		||||
    removed: "The moderator has been discharged"
 | 
			
		||||
  logs:
 | 
			
		||||
    title: "Logs"
 | 
			
		||||
    moderator: "Moderators"
 | 
			
		||||
    type: "Operations"
 | 
			
		||||
    at: "Timestamp"
 | 
			
		||||
    info: "Information"
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "Add emoji"
 | 
			
		||||
@@ -1681,6 +1729,8 @@ mobile/views/pages/search.vue:
 | 
			
		||||
  not-found: "No posts were found for '{q}'"
 | 
			
		||||
mobile/views/pages/selectdrive.vue:
 | 
			
		||||
  select-file: "Choose files"
 | 
			
		||||
mobile/views/pages/notifications.vue:
 | 
			
		||||
  notifications: "Notifications"
 | 
			
		||||
mobile/views/pages/settings.vue:
 | 
			
		||||
  signed-in-as: "Signed in as {}"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
@@ -1771,6 +1821,7 @@ pages:
 | 
			
		||||
  read-page: "Viewing the source"
 | 
			
		||||
  page-created: "Created the page!"
 | 
			
		||||
  page-updated: "Updated the page"
 | 
			
		||||
  name-already-exists: "The specified page name already exists"
 | 
			
		||||
  are-you-sure-delete: "Do you want to delete this page?"
 | 
			
		||||
  page-deleted: "The page has been deleted"
 | 
			
		||||
  edit-this-page: "Edit this page"
 | 
			
		||||
@@ -1861,6 +1912,12 @@ pages:
 | 
			
		||||
          message: "Message to display when pressed"
 | 
			
		||||
          variable: "Variable to send"
 | 
			
		||||
          no-variable: "None"
 | 
			
		||||
    radioButton: "Choices"
 | 
			
		||||
    _radioButton:
 | 
			
		||||
      name: "Variable name"
 | 
			
		||||
      title: "Title"
 | 
			
		||||
      values: "Item of choices that delimited by line breaks"
 | 
			
		||||
      default: "Default value"
 | 
			
		||||
  script:
 | 
			
		||||
    categories:
 | 
			
		||||
      flow: "Control"
 | 
			
		||||
@@ -2033,3 +2090,64 @@ pages:
 | 
			
		||||
    enviromentVariables: "Environment variable"
 | 
			
		||||
    pageVariables: "Page element"
 | 
			
		||||
    argVariables: "Input slot"
 | 
			
		||||
room:
 | 
			
		||||
  add-furniture: "Place furniture"
 | 
			
		||||
  translate: "Move"
 | 
			
		||||
  rotate: "Rotate"
 | 
			
		||||
  exit: "Deselect"
 | 
			
		||||
  remove: "Remove"
 | 
			
		||||
  save: "Save"
 | 
			
		||||
  saved: "Saved"
 | 
			
		||||
  clear: "Remove All"
 | 
			
		||||
  clear-confirm: "Are you sure to remove all furnitures in your room?"
 | 
			
		||||
  leave-confirm: "There are unsaved changes. Do you really want to leave?"
 | 
			
		||||
  chooseImage: "Select an image"
 | 
			
		||||
  room-type: "Room type"
 | 
			
		||||
  carpet-color: "Color of carpet"
 | 
			
		||||
  rooms:
 | 
			
		||||
    default: "Default"
 | 
			
		||||
    washitsu: "Japanese-style"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    milk: "Milk carton"
 | 
			
		||||
    bed: "Bed"
 | 
			
		||||
    low-table: "Low Table"
 | 
			
		||||
    desk: "Desk"
 | 
			
		||||
    chair: "Chair"
 | 
			
		||||
    chair2: "Chair 2"
 | 
			
		||||
    fan: "Fan"
 | 
			
		||||
    pc: "Computer"
 | 
			
		||||
    plant: "Houseplant"
 | 
			
		||||
    plant2: "Houseplant 2"
 | 
			
		||||
    eraser: "Eraser"
 | 
			
		||||
    pencil: "Pencil"
 | 
			
		||||
    pudding: "Pudding"
 | 
			
		||||
    cardboard-box: "Cardboard Box"
 | 
			
		||||
    cardboard-box2: "Cardboard Box 2"
 | 
			
		||||
    cardboard-box3: "Cardboard Box 3"
 | 
			
		||||
    book: "Book"
 | 
			
		||||
    book2: "Book 2"
 | 
			
		||||
    piano: "Piano"
 | 
			
		||||
    facial-tissue: "Facial tissue"
 | 
			
		||||
    server: "Servers"
 | 
			
		||||
    moon: "Moon"
 | 
			
		||||
    corkboard: "Cork board"
 | 
			
		||||
    mousepad: "Mousepad"
 | 
			
		||||
    monitor: "Monitor"
 | 
			
		||||
    keyboard: "Keyboard"
 | 
			
		||||
    carpet-stripe: "Carpet (stripe)"
 | 
			
		||||
    mat: "Mat"
 | 
			
		||||
    color-box: "Bookshelf"
 | 
			
		||||
    wall-clock: "Wall clock"
 | 
			
		||||
    photoframe: "Picture frame"
 | 
			
		||||
    cube: "Cube"
 | 
			
		||||
    tv: "TV"
 | 
			
		||||
    pinguin: "Penguin"
 | 
			
		||||
    rubik-cube: "Rubik's Cube"
 | 
			
		||||
    poster-h: "Poster (Horizontal)"
 | 
			
		||||
    poster-v: "Poster (Vertical)"
 | 
			
		||||
    sofa: "Sofa"
 | 
			
		||||
    spiral: "Spiral Staircase"
 | 
			
		||||
    bin: "Waste bin"
 | 
			
		||||
    cup-noodle: "Cup noodle"
 | 
			
		||||
    holo-display: "Holographic display"
 | 
			
		||||
    energy-drink: "Energy drink"
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,11 @@ common:
 | 
			
		||||
  signin: "Iniciar sesión"
 | 
			
		||||
  signup: "¡Regístrate!"
 | 
			
		||||
  signout: "Cerrar sesión"
 | 
			
		||||
  delete-confirm: "¿Seguro que quieres borrar la publicación?"
 | 
			
		||||
  notification-types:
 | 
			
		||||
    all: "Todo"
 | 
			
		||||
    reply: "Responder"
 | 
			
		||||
    renote: "Volver a publicar"
 | 
			
		||||
  got-it: "¡Listo!"
 | 
			
		||||
  customization-tips:
 | 
			
		||||
    title: "Consejos de personalización"
 | 
			
		||||
@@ -172,7 +177,7 @@ common:
 | 
			
		||||
    deck-column-width-wide: "Ancho"
 | 
			
		||||
    use-shadow: "Usar sombras en la Interfaz de Usuario"
 | 
			
		||||
    rounded-corners: "Esquinas redondeadas en la Interfaz de Usuario"
 | 
			
		||||
    circle-icons: "Usar iconos circulares"
 | 
			
		||||
    circle-icons: "Usar avatar circulares"
 | 
			
		||||
    contrasted-acct: "Añadir contraste al nombre de usuario"
 | 
			
		||||
    wallpaper: "Fondo de pantalla"
 | 
			
		||||
    choose-wallpaper: "Escoge un fondo de pantalla"
 | 
			
		||||
@@ -197,6 +202,8 @@ common:
 | 
			
		||||
    update-available-desc: "Las actualizaciones se aplicarán cuando la página se vuelva a cargar."
 | 
			
		||||
    advanced-settings: "Configuraciones avanzadas"
 | 
			
		||||
    navbar-position-left: "Izquierda"
 | 
			
		||||
    save: "Guardar"
 | 
			
		||||
    saved: "Guardado"
 | 
			
		||||
  search: "Buscar"
 | 
			
		||||
  delete: "eliminar"
 | 
			
		||||
  loading: "cargando"
 | 
			
		||||
@@ -567,6 +574,7 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  saved: "Perfil actualizado con exito"
 | 
			
		||||
  uploading: "Subiendo"
 | 
			
		||||
  upload-failed: "Error al subir"
 | 
			
		||||
  unable-to-process: "La operación no se puede llevar a cabo"
 | 
			
		||||
  email: "Preferencias de correo"
 | 
			
		||||
  email-address: "Correo electrónico"
 | 
			
		||||
  email-verified: "Tu cuenta de correo ha sido verificada."
 | 
			
		||||
@@ -669,6 +677,7 @@ desktop:
 | 
			
		||||
  uploading-avatar: "Cargando un nuevo avatar"
 | 
			
		||||
  avatar-updated: "Avatar actualizado"
 | 
			
		||||
  choose-avatar: "Escoge una imagen de avatar"
 | 
			
		||||
  unable-to-process: "La operación no se puede llevar a cabo"
 | 
			
		||||
  invalid-filetype: "Este tipo de archivo no es compatible aquí"
 | 
			
		||||
desktop/views/components/activity.chart.vue:
 | 
			
		||||
  total: "Negro ... Total"
 | 
			
		||||
@@ -962,12 +971,18 @@ admin/views/drive.vue:
 | 
			
		||||
  mark-as-sensitive: "Marcar como 'sensible'"
 | 
			
		||||
  unmark-as-sensitive: "Desmarcar como 'sensible'"
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  username: "Usuario"
 | 
			
		||||
  host: "Host"
 | 
			
		||||
  users:
 | 
			
		||||
    state:
 | 
			
		||||
      all: "Todo"
 | 
			
		||||
      moderator: "Moderadores"
 | 
			
		||||
    origin:
 | 
			
		||||
      local: "Local"
 | 
			
		||||
admin/views/moderators.vue:
 | 
			
		||||
  logs:
 | 
			
		||||
    title: "Registros"
 | 
			
		||||
    moderator: "Moderadores"
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    add: "Agregar"
 | 
			
		||||
@@ -1079,6 +1094,8 @@ mobile/views/pages/games/reversi.vue:
 | 
			
		||||
  reversi: "Reversi"
 | 
			
		||||
mobile/views/pages/search.vue:
 | 
			
		||||
  search: "Buscar"
 | 
			
		||||
mobile/views/pages/notifications.vue:
 | 
			
		||||
  notifications: "Notificaciones"
 | 
			
		||||
mobile/views/pages/user/home.vue:
 | 
			
		||||
  activity: "Actividad"
 | 
			
		||||
mobile/views/pages/user/home.photos.vue:
 | 
			
		||||
@@ -1118,3 +1135,9 @@ pages:
 | 
			
		||||
        arg1: "Listas"
 | 
			
		||||
    types:
 | 
			
		||||
      array: "Listas"
 | 
			
		||||
room:
 | 
			
		||||
  save: "Guardar"
 | 
			
		||||
  saved: "Guardado"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "Luna"
 | 
			
		||||
    bin: "Papelera"
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,19 @@ common:
 | 
			
		||||
  signout: "Se déconnecter"
 | 
			
		||||
  reload-to-apply-the-setting: "Le rechargement de la page est nécessaire pour appliquer ces paramètres. Désirez-vous la recharger maintenant ?"
 | 
			
		||||
  unfollow-confirm: "Désirez-vous vous désabonner de {name} ?"
 | 
			
		||||
  delete-confirm: "Supprimer cette publication ?"
 | 
			
		||||
  signin-required: "Veuillez vous connecter"
 | 
			
		||||
  notification-type: "Type de notification"
 | 
			
		||||
  notification-types:
 | 
			
		||||
    all: "Tout"
 | 
			
		||||
    pollVote: "Sondage"
 | 
			
		||||
    follow: "Abonnements"
 | 
			
		||||
    receiveFollowRequest: "Demandes d’abonnements"
 | 
			
		||||
    reply: "Répondre"
 | 
			
		||||
    quote: "Cité par"
 | 
			
		||||
    renote: "Republier"
 | 
			
		||||
    mention: "Mentions"
 | 
			
		||||
    reaction: "Réaction"
 | 
			
		||||
  got-it: "J’ai compris !"
 | 
			
		||||
  customization-tips:
 | 
			
		||||
    title: "Conseils de personnalisation"
 | 
			
		||||
@@ -118,6 +131,7 @@ common:
 | 
			
		||||
    add-visible-user: "Ajouter un utilisateur"
 | 
			
		||||
    cw-placeholder: "Commenter le contenu (optionnel)"
 | 
			
		||||
    username-prompt: "Saisir un nom d'utilisateur"
 | 
			
		||||
    enter-file-name: "Éditer le nom du fichier"
 | 
			
		||||
  weekday-short:
 | 
			
		||||
    sunday: "D"
 | 
			
		||||
    monday: "L"
 | 
			
		||||
@@ -184,6 +198,7 @@ common:
 | 
			
		||||
    remember-note-visibility: "Se souvenir du mode de visibilité de la publication"
 | 
			
		||||
    web-search-engine: "Moteur de recherche Web"
 | 
			
		||||
    web-search-engine-desc: "Exemple : https://www.google.com/?#q={{query}}"
 | 
			
		||||
    paste: "Coller"
 | 
			
		||||
    keep-cw: "Maintenir l'avertissement de contenu"
 | 
			
		||||
    keep-cw-desc: "Lorsque vous répondez à un message, le même avertissement de contenu est reprit par défaut dans la réponse, le même que celui qui a été défini dans le message original."
 | 
			
		||||
    i-like-sushi: "Je préfère les sushis plutôt que le pudding"
 | 
			
		||||
@@ -221,7 +236,7 @@ common:
 | 
			
		||||
    deck-column-width-wide: "Large"
 | 
			
		||||
    use-shadow: "Utiliser les ombres dans l'interface utilisateur"
 | 
			
		||||
    rounded-corners: "Coins arrondis de l'interface utilisateur"
 | 
			
		||||
    circle-icons: "Utiliser des icônes circulaires"
 | 
			
		||||
    circle-icons: "Utiliser des avatar circulaires"
 | 
			
		||||
    contrasted-acct: "Ajouter du contraste au nom de l’utilisateur"
 | 
			
		||||
    wallpaper: "Image du fond d'écran"
 | 
			
		||||
    choose-wallpaper: "Sélectionner un fond d'écran"
 | 
			
		||||
@@ -267,6 +282,20 @@ common:
 | 
			
		||||
    load-raw-images: "Afficher les photos jointes dans leur qualité originale"
 | 
			
		||||
    load-remote-media: "Afficher les médias depuis le serveur distant"
 | 
			
		||||
    sync: "Synchroniser"
 | 
			
		||||
    save: "Enregistrer"
 | 
			
		||||
    saved: "enregistré"
 | 
			
		||||
    home-profile: "Profil principal"
 | 
			
		||||
    deck-profile: "Profil deck"
 | 
			
		||||
    room: "Pièce"
 | 
			
		||||
    _room:
 | 
			
		||||
      graphicsQuality: "Qualité des graphismes"
 | 
			
		||||
      _graphicsQuality:
 | 
			
		||||
        ultra: "Très élevée"
 | 
			
		||||
        high: "Élevée"
 | 
			
		||||
        medium: "Moyenne"
 | 
			
		||||
        low: "Basse"
 | 
			
		||||
        cheep: "Minimale"
 | 
			
		||||
      useOrthographicCamera: "Utiliser une caméra orthographique"
 | 
			
		||||
  search: "Recherche"
 | 
			
		||||
  delete: "Supprimer"
 | 
			
		||||
  loading: "Chargement en cours …"
 | 
			
		||||
@@ -388,6 +417,7 @@ common/views/components/games/reversi/reversi.room.vue:
 | 
			
		||||
  black-or-white: "Noirs/Blancs"
 | 
			
		||||
  black-is: "{} Noirs"
 | 
			
		||||
  rules: "Règles"
 | 
			
		||||
  is-llotheo: "Celui ou celle qui a le moins de pièces gagne (Llotheo)"
 | 
			
		||||
  looped-map: "Carte en boucle"
 | 
			
		||||
  can-put-everywhere: "Peut poser partout"
 | 
			
		||||
  settings-of-the-bot: "Configuration du bot"
 | 
			
		||||
@@ -515,6 +545,7 @@ common/views/components/note-menu.vue:
 | 
			
		||||
  unpin: "Désépingler"
 | 
			
		||||
  delete: "Supprimer"
 | 
			
		||||
  delete-confirm: "Supprimer cette publication ?"
 | 
			
		||||
  delete-and-edit: "Effacer et éditer"
 | 
			
		||||
  remote: "Afficher la note originale"
 | 
			
		||||
common/views/components/user-menu.vue:
 | 
			
		||||
  mention: "Mention"
 | 
			
		||||
@@ -555,6 +586,7 @@ common/views/components/poll-editor.vue:
 | 
			
		||||
  remove: "Supprimer ce choix"
 | 
			
		||||
  add: "+ Ajouter un choix"
 | 
			
		||||
  destroy: "Annuler ce sondage"
 | 
			
		||||
  multiple: "Autoriser le multi-choix"
 | 
			
		||||
  expiration: "Valide jusqu'à"
 | 
			
		||||
  infinite: "Illimité"
 | 
			
		||||
  at: "Choisir une date et une durée"
 | 
			
		||||
@@ -581,6 +613,12 @@ common/views/components/emoji-picker.vue:
 | 
			
		||||
  symbols: "Symboles"
 | 
			
		||||
  flags: "Drapeaux"
 | 
			
		||||
common/views/components/settings/app-type.vue:
 | 
			
		||||
  title: "Mode"
 | 
			
		||||
  intro: "Vous pouvez choisir, si vous voulez utiliser la disposition de bureau ou mobile."
 | 
			
		||||
  choices:
 | 
			
		||||
    auto: "Choisir la disposition automatiquement"
 | 
			
		||||
    desktop: "Toujours utiliser la disposition de bureau"
 | 
			
		||||
    mobile: "Toujours utiliser la disposition mobile"
 | 
			
		||||
  info: "Le rechargement de la page est requis afin d'appliquer les modifications."
 | 
			
		||||
common/views/components/signin.vue:
 | 
			
		||||
  username: "Nom d'utilisateur·rice"
 | 
			
		||||
@@ -698,6 +736,9 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  saved: "Profil mis à jour avec succès"
 | 
			
		||||
  uploading: "En cours d’envoi …"
 | 
			
		||||
  upload-failed: "Échec de l'envoi"
 | 
			
		||||
  unable-to-process: "L'opération n'a pas pu être complétée"
 | 
			
		||||
  avatar-not-an-image: "Le fichier sélectionné pour votre avatar n'est pas une image"
 | 
			
		||||
  banner-not-an-image: "Le fichier sélectionné pour votre bannière n'est pas une image"
 | 
			
		||||
  email: "Paramètres de messagerie"
 | 
			
		||||
  email-address: "Adresse de courrier électronique"
 | 
			
		||||
  email-verified: "L’adresse du courrier électronique a été vérifiée."
 | 
			
		||||
@@ -717,6 +758,9 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  danger-zone: "Zone de danger"
 | 
			
		||||
  delete-account: "Supprimer le compte"
 | 
			
		||||
  account-deleted: "Le compte a été supprimé. Cela peut prendre un certain temps avant que toutes les données disparaissent."
 | 
			
		||||
  profile-metadata: "Métadonnées du profil"
 | 
			
		||||
  metadata-label: "Étiquette"
 | 
			
		||||
  metadata-content: "Contenu"
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "Utilisateur·rice"
 | 
			
		||||
  rename: "Renommer la liste"
 | 
			
		||||
@@ -825,6 +869,7 @@ desktop:
 | 
			
		||||
  uploading-avatar: "Téléversement du nouvel avatar"
 | 
			
		||||
  avatar-updated: "Mise à jour de l’avatar avec succès"
 | 
			
		||||
  choose-avatar: "Choisir un avatar"
 | 
			
		||||
  unable-to-process: "L'opération n'a pas pu être complétée"
 | 
			
		||||
  invalid-filetype: "Ce format de fichier n’est pas pris en charge"
 | 
			
		||||
desktop/views/components/activity.chart.vue:
 | 
			
		||||
  total: "Noirs ... Total"
 | 
			
		||||
@@ -875,9 +920,12 @@ desktop/views/components/drive.file.vue:
 | 
			
		||||
    copied: "Copié"
 | 
			
		||||
    copied-url-to-clipboard: "L'URL a été copiée dans le presse-papier"
 | 
			
		||||
desktop/views/components/drive.folder.vue:
 | 
			
		||||
  upload-folder: "Emplacement de téléversement par défaut"
 | 
			
		||||
  unable-to-process: "L'opération n'a pas pu être complétée"
 | 
			
		||||
  circular-reference-detected: "Le dossier de destination est un sous-dossier du dossier que vous souhaitez déplacer."
 | 
			
		||||
  unhandled-error: "Erreur inconnue"
 | 
			
		||||
  unable-to-delete: "Ne peut pas être supprimé"
 | 
			
		||||
  has-child-files-or-folders: "Ce dossier n'est pas vide, il ne peut pas être supprimé"
 | 
			
		||||
  contextmenu:
 | 
			
		||||
    move-to-this-folder: "Déplacer dans ce dossier"
 | 
			
		||||
    show-in-new-window: "Ouvrir dans une nouvelle fenêtre"
 | 
			
		||||
@@ -885,6 +933,7 @@ desktop/views/components/drive.folder.vue:
 | 
			
		||||
    rename-folder: "Renommer le dossier"
 | 
			
		||||
    input-new-folder-name: "Entrer un nouveau nom"
 | 
			
		||||
    else-folders: "Avancé"
 | 
			
		||||
    set-as-upload-folder: "Spécifier en tant que dossier de téléversement par défaut"
 | 
			
		||||
desktop/views/components/drive.vue:
 | 
			
		||||
  search: "Rechercher"
 | 
			
		||||
  empty-draghover: "Drop Welcome!"
 | 
			
		||||
@@ -990,10 +1039,12 @@ desktop/views/components/settings.2fa.vue:
 | 
			
		||||
  success: "Sauvegarde des paramètres avec succès !"
 | 
			
		||||
  failed: "L’opération a échoué. Veuillez vous assurer que le jeton a été saisi correctement."
 | 
			
		||||
  info: "À partir de maintenant, à chaque fois que vous vous connectez entrez votre mot de passe ainsi que le jeton généré sur votre appareil."
 | 
			
		||||
  totp-header: "Application d'authentification"
 | 
			
		||||
  security-key-header: "Clé de sécurité"
 | 
			
		||||
  last-used: "Dernière utilisation :"
 | 
			
		||||
  activate-key: "Cliquez pour activer la clé de sécurité"
 | 
			
		||||
  security-key-name: "Nom de la clé"
 | 
			
		||||
  something-went-wrong: "Oula ! Il y a eu un problème lors de l’enregistrement de la clé."
 | 
			
		||||
  key-unregistered: "La clé a été supprimée"
 | 
			
		||||
  use-password-less-login: "Utiliser une connexion sans mot de passe"
 | 
			
		||||
common/views/components/media-image.vue:
 | 
			
		||||
@@ -1020,7 +1071,9 @@ common/views/components/drive-settings.vue:
 | 
			
		||||
  max: "Maximale"
 | 
			
		||||
  in-use: "utilisé"
 | 
			
		||||
  stats: "Statistiques"
 | 
			
		||||
  default-upload-folder: "Emplacement par défaut du dossier de transfert"
 | 
			
		||||
  default-upload-folder-name: "Dossier·s"
 | 
			
		||||
  change-default-upload-folder: "Changer de dossier"
 | 
			
		||||
common/views/components/mute-and-block.vue:
 | 
			
		||||
  mute-and-block: "Silencés / Bloqués"
 | 
			
		||||
  mute: "Mettre en sourdine"
 | 
			
		||||
@@ -1074,6 +1127,7 @@ desktop/views/components/ui.header.account.vue:
 | 
			
		||||
  groups: "Groupes"
 | 
			
		||||
  follow-requests: "Demandes d’abonnement"
 | 
			
		||||
  admin: "Admin"
 | 
			
		||||
  room: "Pièce"
 | 
			
		||||
desktop/views/components/ui.header.nav.vue:
 | 
			
		||||
  game: "Jeux"
 | 
			
		||||
desktop/views/components/ui.header.notifications.vue:
 | 
			
		||||
@@ -1127,11 +1181,13 @@ admin/views/queue.vue:
 | 
			
		||||
    deliver: "Délivrées"
 | 
			
		||||
    inbox: "Reçues"
 | 
			
		||||
    db: "Base de données"
 | 
			
		||||
    objectStorage: "Stockage d'objets"
 | 
			
		||||
  state: "État"
 | 
			
		||||
  states:
 | 
			
		||||
    active: "en cours"
 | 
			
		||||
    delayed: "Programmé"
 | 
			
		||||
    waiting: "En file d'attente"
 | 
			
		||||
  result-is-truncated: "Le résultat est tronqué"
 | 
			
		||||
  other-queues: "Autres files d’attente"
 | 
			
		||||
admin/views/logs.vue:
 | 
			
		||||
  logs: "Journaux"
 | 
			
		||||
@@ -1164,12 +1220,14 @@ admin/views/instance.vue:
 | 
			
		||||
  languages-desc: "Vous pouvez en définir plus d’une, séparées par des espaces."
 | 
			
		||||
  tos-url: "URL des conditions d'utilisation"
 | 
			
		||||
  repository-url: "URL du dépôt"
 | 
			
		||||
  feedback-url: "URL pour les commentaires"
 | 
			
		||||
  maintainer-config: "Informations de l’administrateur"
 | 
			
		||||
  maintainer-name: "Nom de l’administrateur"
 | 
			
		||||
  maintainer-email: "Contact administratif"
 | 
			
		||||
  advanced-config: "Autres réglages"
 | 
			
		||||
  note-and-tl: "Notes et fils"
 | 
			
		||||
  drive-config: "Paramètres du lecteur"
 | 
			
		||||
  use-object-storage: "Utiliser le stockage d'objets"
 | 
			
		||||
  object-storage-base-url: "URL"
 | 
			
		||||
  object-storage-prefix: "Préfixe"
 | 
			
		||||
  object-storage-endpoint: "Point de terminaison"
 | 
			
		||||
@@ -1311,6 +1369,8 @@ admin/views/users.vue:
 | 
			
		||||
  remote-user-updated: "Les informations de l’utilisateur·rice distant·e ont étés mis à jour"
 | 
			
		||||
  delete-all-files: "Supprimer tous les fichiers"
 | 
			
		||||
  delete-all-files-confirm: "Êtes vous surs de vouloir supprimer tous les fichiers ?"
 | 
			
		||||
  username: "Nom d'utilisateur·rice"
 | 
			
		||||
  host: "Hôte"
 | 
			
		||||
  users:
 | 
			
		||||
    title: "Utilisateur·rice·s"
 | 
			
		||||
    sort:
 | 
			
		||||
@@ -1341,6 +1401,12 @@ admin/views/moderators.vue:
 | 
			
		||||
    added: "Ajouté en tant que modérateur"
 | 
			
		||||
    remove: "Révoquer"
 | 
			
		||||
    removed: "Le modérateur a été révoqué"
 | 
			
		||||
  logs:
 | 
			
		||||
    title: "Journaux"
 | 
			
		||||
    moderator: "Modérateurs"
 | 
			
		||||
    type: "Actions"
 | 
			
		||||
    at: "Date de modification"
 | 
			
		||||
    info: "Informations"
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "Ajouter un émoji"
 | 
			
		||||
@@ -1617,6 +1683,8 @@ mobile/views/pages/search.vue:
 | 
			
		||||
  not-found: "Aucune publication trouvée pour « {q} »."
 | 
			
		||||
mobile/views/pages/selectdrive.vue:
 | 
			
		||||
  select-file: "Choisissez un fichier"
 | 
			
		||||
mobile/views/pages/notifications.vue:
 | 
			
		||||
  notifications: "Notifications"
 | 
			
		||||
mobile/views/pages/settings.vue:
 | 
			
		||||
  signed-in-as: "Connecté·e en tant que {}"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
@@ -1675,6 +1743,7 @@ deck/deck.user-column.vue:
 | 
			
		||||
  activity: "Activité"
 | 
			
		||||
  timeline: "Fil d’actualité"
 | 
			
		||||
  pinned-notes: "Notes épinglées"
 | 
			
		||||
  pinned-page: "Page épinglée"
 | 
			
		||||
docs:
 | 
			
		||||
  edit-this-page-on-github: "Vous avez trouvé une erreur ou vous voulez contribuer à la documentation ?"
 | 
			
		||||
  edit-this-page-on-github-link: "Éditez cette page sur GitHub !"
 | 
			
		||||
@@ -1790,6 +1859,11 @@ pages:
 | 
			
		||||
          message: "Message à afficher lorsque appuyé"
 | 
			
		||||
          variable: "Variable à envoyer"
 | 
			
		||||
          no-variable: "Aucune"
 | 
			
		||||
    radioButton: "Choix"
 | 
			
		||||
    _radioButton:
 | 
			
		||||
      name: "Nom de la variable"
 | 
			
		||||
      title: "Titre"
 | 
			
		||||
      default: "Valeur par défaut"
 | 
			
		||||
  script:
 | 
			
		||||
    categories:
 | 
			
		||||
      flow: "Contrôle"
 | 
			
		||||
@@ -1938,3 +2012,60 @@ pages:
 | 
			
		||||
    emptySlot: "Slot vide"
 | 
			
		||||
    enviromentVariables: "Variables d'environnement"
 | 
			
		||||
    pageVariables: "Élément de page"
 | 
			
		||||
room:
 | 
			
		||||
  add-furniture: "Placer des meubles"
 | 
			
		||||
  translate: "Déplacer"
 | 
			
		||||
  rotate: "Tourner"
 | 
			
		||||
  remove: "Enlever"
 | 
			
		||||
  save: "Enregistrer"
 | 
			
		||||
  saved: "enregistré"
 | 
			
		||||
  clear: "Tout enlever"
 | 
			
		||||
  clear-confirm: "Désirez-vous enlever tout les meubles de votre chambre ?"
 | 
			
		||||
  leave-confirm: "Vous avez des modifications non-sauvegardées. Voulez-vous vraiment quitter ?"
 | 
			
		||||
  chooseImage: "Sélectionnez une image"
 | 
			
		||||
  room-type: "Type de chambre"
 | 
			
		||||
  carpet-color: "Couleur du tapis"
 | 
			
		||||
  rooms:
 | 
			
		||||
    default: "Par défaut"
 | 
			
		||||
    washitsu: "Style japonnais"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    milk: "Lait en carton"
 | 
			
		||||
    bed: "Lit"
 | 
			
		||||
    low-table: "Table basse"
 | 
			
		||||
    desk: "Bureau"
 | 
			
		||||
    chair: "Chaise"
 | 
			
		||||
    chair2: "Chaise 2"
 | 
			
		||||
    fan: "Ventilateur"
 | 
			
		||||
    pc: "Ordinateur"
 | 
			
		||||
    plant: "Plante d’intérieur"
 | 
			
		||||
    plant2: "Plante d’intérieur 2"
 | 
			
		||||
    eraser: "Gomme"
 | 
			
		||||
    pencil: "Crayon"
 | 
			
		||||
    cardboard-box: "Boîte en carton"
 | 
			
		||||
    cardboard-box2: "Boîte en carton 2"
 | 
			
		||||
    cardboard-box3: "Boîte en carton 3"
 | 
			
		||||
    book: "Livre"
 | 
			
		||||
    book2: "Livre 2"
 | 
			
		||||
    piano: "Piano"
 | 
			
		||||
    server: "Serveurs"
 | 
			
		||||
    moon: "Lune"
 | 
			
		||||
    corkboard: "Tableau en liège"
 | 
			
		||||
    mousepad: "Tapis de souris"
 | 
			
		||||
    monitor: "Écran"
 | 
			
		||||
    keyboard: "Clavier"
 | 
			
		||||
    carpet-stripe: "Tapis (zébré)"
 | 
			
		||||
    color-box: "Étagère"
 | 
			
		||||
    wall-clock: "Horloge murale"
 | 
			
		||||
    photoframe: "Cadre photo"
 | 
			
		||||
    cube: "Cube"
 | 
			
		||||
    tv: "Téléviseur"
 | 
			
		||||
    pinguin: "Pingouin"
 | 
			
		||||
    rubik-cube: "Cube de Rubik"
 | 
			
		||||
    poster-h: "Affiche (horizontale)"
 | 
			
		||||
    poster-v: "Affiche (verticale)"
 | 
			
		||||
    sofa: "Canapé"
 | 
			
		||||
    spiral: "Escaliers en spirale"
 | 
			
		||||
    bin: "Corbeille"
 | 
			
		||||
    cup-noodle: "Bol de nouilles"
 | 
			
		||||
    holo-display: "Affichage holographique"
 | 
			
		||||
    energy-drink: "Boisson énergétique"
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,19 @@ common:
 | 
			
		||||
  reload-to-apply-the-setting: "この設定を反映するにはページをリロードする必要があります。今すぐリロードしますか?"
 | 
			
		||||
  fetching-as-ap-object: "連合に照会中"
 | 
			
		||||
  unfollow-confirm: "{name}さんをフォロー解除しますか?"
 | 
			
		||||
  delete-confirm: "この投稿を削除しますか?"
 | 
			
		||||
  signin-required: "ログインしてください"
 | 
			
		||||
  notification-type: "通知の種類"
 | 
			
		||||
  notification-types:
 | 
			
		||||
    all: "すべて"
 | 
			
		||||
    pollVote: "投票"
 | 
			
		||||
    follow: "フォロー"
 | 
			
		||||
    receiveFollowRequest: "フォローリクエスト"
 | 
			
		||||
    reply: "返信"
 | 
			
		||||
    quote: "引用"
 | 
			
		||||
    renote: "Renote"
 | 
			
		||||
    mention: "言及"
 | 
			
		||||
    reaction: "リアクション"
 | 
			
		||||
 | 
			
		||||
  got-it: "わかった"
 | 
			
		||||
  customization-tips:
 | 
			
		||||
@@ -126,6 +139,7 @@ common:
 | 
			
		||||
    geolocation-alert: "お使いの端末は位置情報に対応していません"
 | 
			
		||||
    error: "エラー"
 | 
			
		||||
    enter-username: "ユーザー名を入力してください"
 | 
			
		||||
    specified-recipient: "宛先"
 | 
			
		||||
    add-visible-user: "ユーザーを追加"
 | 
			
		||||
    cw-placeholder: "内容への注釈 (オプション)"
 | 
			
		||||
    username-prompt: "ユーザー名を入力してください"
 | 
			
		||||
@@ -214,6 +228,7 @@ common:
 | 
			
		||||
    use-avatar-reversi-stones: "リバーシの石にアバターを使う"
 | 
			
		||||
    disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
 | 
			
		||||
    disable-showing-animated-images: "アニメーション画像を再生しない"
 | 
			
		||||
    enable-quick-notification-view: "通知のクイックビューを有効にする"
 | 
			
		||||
    suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
 | 
			
		||||
    always-show-nsfw: "常に閲覧注意のメディアを表示する"
 | 
			
		||||
    always-mark-nsfw: "常にメディアを閲覧注意として投稿"
 | 
			
		||||
@@ -244,7 +259,7 @@ common:
 | 
			
		||||
    deck-column-width-wide: "広"
 | 
			
		||||
    use-shadow: "UIに影を使用"
 | 
			
		||||
    rounded-corners: "UIの角を丸める"
 | 
			
		||||
    circle-icons: "円形のアイコンを使用"
 | 
			
		||||
    circle-icons: "円形のアバターを使用"
 | 
			
		||||
    contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
    wallpaper: "壁紙"
 | 
			
		||||
    choose-wallpaper: "壁紙を選択"
 | 
			
		||||
@@ -290,8 +305,20 @@ common:
 | 
			
		||||
    load-raw-images: "添付された画像を高画質で表示する"
 | 
			
		||||
    load-remote-media: "リモートサーバーのメディアを表示する"
 | 
			
		||||
    sync: "同期"
 | 
			
		||||
    save: "保存"
 | 
			
		||||
    saved: "保存しました"
 | 
			
		||||
    home-profile: "ホームのプロファイル"
 | 
			
		||||
    deck-profile: "デッキのプロファイル"
 | 
			
		||||
    room: "ルーム"
 | 
			
		||||
    _room:
 | 
			
		||||
      graphicsQuality: "グラフィックの品質"
 | 
			
		||||
      _graphicsQuality:
 | 
			
		||||
        ultra: "最高"
 | 
			
		||||
        high: "高"
 | 
			
		||||
        medium: "中"
 | 
			
		||||
        low: "低"
 | 
			
		||||
        cheep: "最低"
 | 
			
		||||
      useOrthographicCamera: "平行投影カメラを使用"
 | 
			
		||||
 | 
			
		||||
  search: "検索"
 | 
			
		||||
  delete: "削除"
 | 
			
		||||
@@ -386,6 +413,10 @@ common/views/pages/explore.vue:
 | 
			
		||||
  explore: "{host}を探索"
 | 
			
		||||
  users-info: "現在{users}ユーザーが登録されています"
 | 
			
		||||
 | 
			
		||||
common/views/components/reactions-viewer.details.vue:
 | 
			
		||||
  few-users: "{users}が{reaction}をリアクション"
 | 
			
		||||
  many-users: "{users}と他{omitted}人が{reaction}をリアクション"
 | 
			
		||||
 | 
			
		||||
common/views/components/url-preview.vue:
 | 
			
		||||
  enable-player: "プレイヤーを開く"
 | 
			
		||||
  disable-player: "プレイヤーを閉じる"
 | 
			
		||||
@@ -567,6 +598,8 @@ common/views/components/note-menu.vue:
 | 
			
		||||
  unpin: "ピン留め解除"
 | 
			
		||||
  delete: "削除"
 | 
			
		||||
  delete-confirm: "この投稿を削除しますか?"
 | 
			
		||||
  delete-and-edit: "削除して編集"
 | 
			
		||||
  delete-and-edit-confirm: "この投稿を削除してもう一度編集しますか?この投稿へのリアクション、Renote、返信も全て削除されます。"
 | 
			
		||||
  remote: "投稿元で見る"
 | 
			
		||||
  pin-limit-exceeded: "これ以上ピン留めできません。"
 | 
			
		||||
 | 
			
		||||
@@ -767,7 +800,7 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  you-can-include-hashtags: "ハッシュタグを含めることができます。"
 | 
			
		||||
  language: "言語"
 | 
			
		||||
  birthday: "誕生日"
 | 
			
		||||
  avatar: "アイコン"
 | 
			
		||||
  avatar: "アバター"
 | 
			
		||||
  banner: "バナー"
 | 
			
		||||
  is-cat: "このアカウントはCatです"
 | 
			
		||||
  is-bot: "このアカウントはBotです"
 | 
			
		||||
@@ -780,6 +813,9 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  saved: "プロフィールを保存しました"
 | 
			
		||||
  uploading: "アップロード中"
 | 
			
		||||
  upload-failed: "アップロードに失敗しました"
 | 
			
		||||
  unable-to-process: "操作を完了できません"
 | 
			
		||||
  avatar-not-an-image: "アバターとして指定したファイルは画像ではありません"
 | 
			
		||||
  banner-not-an-image: "バナーとして指定したファイルは画像ではありません"
 | 
			
		||||
  email: "メール設定"
 | 
			
		||||
  email-address: "メールアドレス"
 | 
			
		||||
  email-verified: "メールアドレスが確認されました"
 | 
			
		||||
@@ -799,6 +835,9 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  danger-zone: "危険な設定"
 | 
			
		||||
  delete-account: "アカウントを削除"
 | 
			
		||||
  account-deleted: "アカウントが削除されました。データが消えるまで時間がかかる場合があります。"
 | 
			
		||||
  profile-metadata: "プロフィール補足情報"
 | 
			
		||||
  metadata-label: "ラベル"
 | 
			
		||||
  metadata-content: "内容"
 | 
			
		||||
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
@@ -925,6 +964,7 @@ desktop:
 | 
			
		||||
  uploading-avatar: "新しいアバターをアップロードしています"
 | 
			
		||||
  avatar-updated: "アバターを更新しました"
 | 
			
		||||
  choose-avatar: "アバターにする画像を選択"
 | 
			
		||||
  unable-to-process: "操作を完了できません"
 | 
			
		||||
  invalid-filetype: "この形式のファイルはサポートされていません"
 | 
			
		||||
 | 
			
		||||
desktop/views/components/activity.chart.vue:
 | 
			
		||||
@@ -964,7 +1004,7 @@ desktop/views/components/drive-window.vue:
 | 
			
		||||
  used: "使用中"
 | 
			
		||||
 | 
			
		||||
desktop/views/components/drive.file.vue:
 | 
			
		||||
  avatar: "アイコン"
 | 
			
		||||
  avatar: "アバター"
 | 
			
		||||
  banner: "バナー"
 | 
			
		||||
  nsfw: "閲覧注意"
 | 
			
		||||
  contextmenu:
 | 
			
		||||
@@ -974,7 +1014,7 @@ desktop/views/components/drive.file.vue:
 | 
			
		||||
    copy-url: "URLをコピー"
 | 
			
		||||
    download: "ダウンロード"
 | 
			
		||||
    else-files: "その他"
 | 
			
		||||
    set-as-avatar: "アイコンに設定"
 | 
			
		||||
    set-as-avatar: "アバターに設定"
 | 
			
		||||
    set-as-banner: "バナーに設定"
 | 
			
		||||
    open-in-app: "アプリで開く"
 | 
			
		||||
    add-app: "アプリを追加"
 | 
			
		||||
@@ -1225,6 +1265,7 @@ desktop/views/components/ui.header.account.vue:
 | 
			
		||||
  groups: "グループ"
 | 
			
		||||
  follow-requests: "フォロー申請"
 | 
			
		||||
  admin: "管理"
 | 
			
		||||
  room: "ルーム"
 | 
			
		||||
 | 
			
		||||
desktop/views/components/ui.header.nav.vue:
 | 
			
		||||
  game: "ゲーム"
 | 
			
		||||
@@ -1496,6 +1537,8 @@ admin/views/users.vue:
 | 
			
		||||
  remote-user-updated: "リモートユーザー情報を更新しました"
 | 
			
		||||
  delete-all-files: "すべてのファイルを削除"
 | 
			
		||||
  delete-all-files-confirm: "すべてのファイルを削除しますか?"
 | 
			
		||||
  username: "ユーザー名"
 | 
			
		||||
  host: "ホスト"
 | 
			
		||||
  users:
 | 
			
		||||
    title: "ユーザー"
 | 
			
		||||
    sort:
 | 
			
		||||
@@ -1527,6 +1570,12 @@ admin/views/moderators.vue:
 | 
			
		||||
    added: "モデレーターを登録しました"
 | 
			
		||||
    remove: "解除"
 | 
			
		||||
    removed: "モデレーター登録を解除しました"
 | 
			
		||||
  logs:
 | 
			
		||||
    title: "ログ"
 | 
			
		||||
    moderator: "モデレーター"
 | 
			
		||||
    type: "操作"
 | 
			
		||||
    at: "日時"
 | 
			
		||||
    info: "情報"
 | 
			
		||||
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
@@ -1866,6 +1915,9 @@ mobile/views/pages/search.vue:
 | 
			
		||||
mobile/views/pages/selectdrive.vue:
 | 
			
		||||
  select-file: "ファイルを選択"
 | 
			
		||||
 | 
			
		||||
mobile/views/pages/notifications.vue:
 | 
			
		||||
  notifications: "通知"
 | 
			
		||||
 | 
			
		||||
mobile/views/pages/settings.vue:
 | 
			
		||||
  signed-in-as: "{}としてサインイン中"
 | 
			
		||||
 | 
			
		||||
@@ -1967,6 +2019,9 @@ pages:
 | 
			
		||||
  read-page: "ソースを表示中"
 | 
			
		||||
  page-created: "ページを作成しました"
 | 
			
		||||
  page-updated: "ページを更新しました"
 | 
			
		||||
  name-already-exists: "指定されたページURLは既に存在しています"
 | 
			
		||||
  title-invalid-name: "不正なページURLです"
 | 
			
		||||
  text-invalid-name: "空白でないか確認してください"
 | 
			
		||||
  are-you-sure-delete: "このページを削除しますか?"
 | 
			
		||||
  page-deleted: "ページを削除しました"
 | 
			
		||||
  edit-this-page: "このページを編集"
 | 
			
		||||
@@ -2066,6 +2121,13 @@ pages:
 | 
			
		||||
          variable: "送信する変数"
 | 
			
		||||
          no-variable: "なし"
 | 
			
		||||
 | 
			
		||||
    radioButton: "選択肢"
 | 
			
		||||
    _radioButton:
 | 
			
		||||
      name: "変数名"
 | 
			
		||||
      title: "タイトル"
 | 
			
		||||
      values: "改行で区切った選択肢"
 | 
			
		||||
      default: "デフォルト値"
 | 
			
		||||
 | 
			
		||||
  script:
 | 
			
		||||
    categories:
 | 
			
		||||
      flow: "制御"
 | 
			
		||||
@@ -2238,3 +2300,65 @@ pages:
 | 
			
		||||
    enviromentVariables: "環境変数"
 | 
			
		||||
    pageVariables: "ページ要素"
 | 
			
		||||
    argVariables: "入力スロット"
 | 
			
		||||
 | 
			
		||||
room:
 | 
			
		||||
  add-furniture: "家具を置く"
 | 
			
		||||
  translate: "移動"
 | 
			
		||||
  rotate: "回転"
 | 
			
		||||
  exit: "戻る"
 | 
			
		||||
  remove: "しまう"
 | 
			
		||||
  save: "保存"
 | 
			
		||||
  saved: "保存しました"
 | 
			
		||||
  clear: "片付け"
 | 
			
		||||
  clear-confirm: "全ての家具をしまいますか?"
 | 
			
		||||
  leave-confirm: "未保存の変更があります、移動しますか?"
 | 
			
		||||
  chooseImage: "画像を選択"
 | 
			
		||||
  room-type: "部屋のタイプ"
 | 
			
		||||
  carpet-color: "床の色"
 | 
			
		||||
  rooms:
 | 
			
		||||
    default: "デフォルト"
 | 
			
		||||
    washitsu: "和室"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    milk: "牛乳パック"
 | 
			
		||||
    bed: "ベッド"
 | 
			
		||||
    low-table: "ローテーブル"
 | 
			
		||||
    desk: "デスク"
 | 
			
		||||
    chair: "チェア"
 | 
			
		||||
    chair2: "チェア2"
 | 
			
		||||
    fan: "換気扇"
 | 
			
		||||
    pc: "パソコン"
 | 
			
		||||
    plant: "観葉植物"
 | 
			
		||||
    plant2: "観葉植物2"
 | 
			
		||||
    eraser: "消しゴム"
 | 
			
		||||
    pencil: "鉛筆"
 | 
			
		||||
    pudding: "プリン"
 | 
			
		||||
    cardboard-box: "段ボール箱"
 | 
			
		||||
    cardboard-box2: "段ボール箱2"
 | 
			
		||||
    cardboard-box3: "段ボール箱3"
 | 
			
		||||
    book: "本"
 | 
			
		||||
    book2: "本2"
 | 
			
		||||
    piano: "ピアノ"
 | 
			
		||||
    facial-tissue: "ティッシュボックス"
 | 
			
		||||
    server: "サーバー"
 | 
			
		||||
    moon: "月"
 | 
			
		||||
    corkboard: "コルクボード"
 | 
			
		||||
    mousepad: "マウスパッド"
 | 
			
		||||
    monitor: "モニター"
 | 
			
		||||
    keyboard: "キーボード"
 | 
			
		||||
    carpet-stripe: "カーペット(縞)"
 | 
			
		||||
    mat: "マット"
 | 
			
		||||
    color-box: "カラーボックス"
 | 
			
		||||
    wall-clock: "壁掛け時計"
 | 
			
		||||
    photoframe: "額縁"
 | 
			
		||||
    cube: "キューブ"
 | 
			
		||||
    tv: "テレビ"
 | 
			
		||||
    pinguin: "ピンギン"
 | 
			
		||||
    rubik-cube: "ルービックキューブ"
 | 
			
		||||
    poster-h: "ポスター(横長)"
 | 
			
		||||
    poster-v: "ポスター(縦長)"
 | 
			
		||||
    sofa: "ソファ"
 | 
			
		||||
    spiral: "螺旋階段"
 | 
			
		||||
    bin: "ゴミ箱"
 | 
			
		||||
    cup-noodle: "カップ麺"
 | 
			
		||||
    holo-display: "ホログラフィックディスプレイ"
 | 
			
		||||
    energy-drink: "エナジードリンク"
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,13 @@ common:
 | 
			
		||||
  load-more: "もっとあらへんのか!"
 | 
			
		||||
  enter-password: "パスワードを入れてや"
 | 
			
		||||
  2fa: "二段階認証"
 | 
			
		||||
  delete-confirm: "この投稿を削除してもええか?"
 | 
			
		||||
  notification-types:
 | 
			
		||||
    all: "すべて"
 | 
			
		||||
    follow: "フォロー"
 | 
			
		||||
    reply: "返す"
 | 
			
		||||
    renote: "Renote"
 | 
			
		||||
    reaction: "リアクション"
 | 
			
		||||
  got-it: "ほい"
 | 
			
		||||
  customization-tips:
 | 
			
		||||
    title: "カスタマイズのヒント"
 | 
			
		||||
@@ -123,6 +130,8 @@ common:
 | 
			
		||||
    password: "パスワード"
 | 
			
		||||
    other: "その他"
 | 
			
		||||
    timeline: "タイムライン"
 | 
			
		||||
    save: "保存"
 | 
			
		||||
    saved: "保存したで!"
 | 
			
		||||
  search: "検索"
 | 
			
		||||
  delete: "削除"
 | 
			
		||||
  loading: "読み込み中"
 | 
			
		||||
@@ -458,7 +467,7 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  description: "自己紹介"
 | 
			
		||||
  language: "言語"
 | 
			
		||||
  birthday: "誕生日"
 | 
			
		||||
  avatar: "アイコン"
 | 
			
		||||
  avatar: "アバター"
 | 
			
		||||
  banner: "バナー"
 | 
			
		||||
  is-cat: "このアカウントはCatやで"
 | 
			
		||||
  is-bot: "このアカウントはBotやで"
 | 
			
		||||
@@ -470,6 +479,7 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  saved: "プロフィールを保存したで"
 | 
			
		||||
  uploading: "アップロードしとります"
 | 
			
		||||
  upload-failed: "これアップロードでけへんわ"
 | 
			
		||||
  unable-to-process: "あかん、無理やわ"
 | 
			
		||||
  email: "メール設定"
 | 
			
		||||
  email-address: "メールアドレス"
 | 
			
		||||
  email-verified: "このメールアドレスOKや!"
 | 
			
		||||
@@ -563,6 +573,7 @@ desktop:
 | 
			
		||||
  uploading-avatar: "新しいアバターをアップロードしとるで"
 | 
			
		||||
  avatar-updated: "アバターを更新したで"
 | 
			
		||||
  choose-avatar: "アバターにする画像選んでや"
 | 
			
		||||
  unable-to-process: "あかん、無理やわ"
 | 
			
		||||
  invalid-filetype: "この形式のファイル無理やねん"
 | 
			
		||||
desktop/views/components/activity.chart.vue:
 | 
			
		||||
  total: "黒いの ... 全部"
 | 
			
		||||
@@ -594,7 +605,7 @@ desktop/views/components/crop-window.vue:
 | 
			
		||||
desktop/views/components/drive-window.vue:
 | 
			
		||||
  used: "使うとる"
 | 
			
		||||
desktop/views/components/drive.file.vue:
 | 
			
		||||
  avatar: "アイコン"
 | 
			
		||||
  avatar: "アバター"
 | 
			
		||||
  banner: "バナー"
 | 
			
		||||
  nsfw: "見たらあかんで"
 | 
			
		||||
  contextmenu:
 | 
			
		||||
@@ -604,7 +615,7 @@ desktop/views/components/drive.file.vue:
 | 
			
		||||
    copy-url: "URLをコピー"
 | 
			
		||||
    download: "ダウンロード"
 | 
			
		||||
    else-files: "その他"
 | 
			
		||||
    set-as-avatar: "アイコンにする"
 | 
			
		||||
    set-as-avatar: "アバターにする"
 | 
			
		||||
    set-as-banner: "バナーにする"
 | 
			
		||||
    open-in-app: "アプリで開く"
 | 
			
		||||
    add-app: "アプリ増やす"
 | 
			
		||||
@@ -942,6 +953,8 @@ admin/views/users.vue:
 | 
			
		||||
  reset-password: "パスワードをリセット"
 | 
			
		||||
  password-updated: "パスワードは現在「{password} 」やで"
 | 
			
		||||
  suspend: "凍結"
 | 
			
		||||
  username: "ユーザー名"
 | 
			
		||||
  host: "ホスト"
 | 
			
		||||
  users:
 | 
			
		||||
    title: "ユーザー"
 | 
			
		||||
    state:
 | 
			
		||||
@@ -949,6 +962,11 @@ admin/views/users.vue:
 | 
			
		||||
      moderator: "モデレーター"
 | 
			
		||||
    origin:
 | 
			
		||||
      local: "ローカル"
 | 
			
		||||
admin/views/moderators.vue:
 | 
			
		||||
  logs:
 | 
			
		||||
    moderator: "モデレーター"
 | 
			
		||||
    type: "操作"
 | 
			
		||||
    info: "情報"
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    add: "増やす"
 | 
			
		||||
@@ -1165,6 +1183,8 @@ mobile/views/pages/search.vue:
 | 
			
		||||
  not-found: "ワイは「{q}」なんて投稿知らんわ、無いんちゃう?知らんけど。"
 | 
			
		||||
mobile/views/pages/selectdrive.vue:
 | 
			
		||||
  select-file: "ファイル選んでや"
 | 
			
		||||
mobile/views/pages/notifications.vue:
 | 
			
		||||
  notifications: "通知"
 | 
			
		||||
mobile/views/pages/settings.vue:
 | 
			
		||||
  signed-in-as: "あんたは橋の下で拾った{}や!"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
@@ -1264,3 +1284,10 @@ pages:
 | 
			
		||||
        arg1: "リスト"
 | 
			
		||||
    types:
 | 
			
		||||
      array: "リスト"
 | 
			
		||||
room:
 | 
			
		||||
  translate: "移動"
 | 
			
		||||
  save: "保存"
 | 
			
		||||
  saved: "保存したで!"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "月"
 | 
			
		||||
    bin: "ゴミ箱"
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,19 @@ common:
 | 
			
		||||
  reload-to-apply-the-setting: "이 설정을 적용하려면 페이지를 새로고침해야 합니다. 바로 새로고침하시겠습니까?"
 | 
			
		||||
  fetching-as-ap-object: "연합에서 조회 중"
 | 
			
		||||
  unfollow-confirm: "{name} 님을 팔로우 해제하시겠습니까?"
 | 
			
		||||
  delete-confirm: "이 글을 삭제하시겠습니까?"
 | 
			
		||||
  signin-required: "로그인 해주세요"
 | 
			
		||||
  notification-type: "알림의 종류"
 | 
			
		||||
  notification-types:
 | 
			
		||||
    all: "모두"
 | 
			
		||||
    pollVote: "투표"
 | 
			
		||||
    follow: "팔로잉"
 | 
			
		||||
    receiveFollowRequest: "팔로우 요청"
 | 
			
		||||
    reply: "답글 달기"
 | 
			
		||||
    quote: "인용"
 | 
			
		||||
    renote: "리노트"
 | 
			
		||||
    mention: "멘션"
 | 
			
		||||
    reaction: "리액션"
 | 
			
		||||
  got-it: "알겠습니다"
 | 
			
		||||
  customization-tips:
 | 
			
		||||
    title: "커스터마이징 도움말"
 | 
			
		||||
@@ -88,7 +101,7 @@ common:
 | 
			
		||||
    "read:mutes": "뮤트 보기"
 | 
			
		||||
    "write:mutes": "뮤트 수정"
 | 
			
		||||
    "write:notes": "글 작성, 삭제"
 | 
			
		||||
    "read:notifications": "글 보기"
 | 
			
		||||
    "read:notifications": "알림 보기"
 | 
			
		||||
    "write:notifications": "알림 수정"
 | 
			
		||||
    "read:reactions": "리액션 보기"
 | 
			
		||||
    "write:reactions": "리액션 수정"
 | 
			
		||||
@@ -120,6 +133,7 @@ common:
 | 
			
		||||
    geolocation-alert: "사용 중이신 장치에서는 위치 정보를 사용할 수 없습니다"
 | 
			
		||||
    error: "오류"
 | 
			
		||||
    enter-username: "사용자명을 입력해주세요"
 | 
			
		||||
    specified-recipient: "수신인"
 | 
			
		||||
    add-visible-user: "사용자 추가"
 | 
			
		||||
    cw-placeholder: "내용에 대한 주석 (옵션)"
 | 
			
		||||
    username-prompt: "사용자명을 입력해주세요"
 | 
			
		||||
@@ -202,6 +216,7 @@ common:
 | 
			
		||||
    use-avatar-reversi-stones: "리버시의 돌로 아바타를 사용"
 | 
			
		||||
    disable-animated-mfm: "글의 문자 애니메이션을 비활성화"
 | 
			
		||||
    disable-showing-animated-images: "움직이는 이미지를 자동으로 재생하지 않음"
 | 
			
		||||
    enable-quick-notification-view: "알림의 빠른 보기를 사용합니다"
 | 
			
		||||
    suggest-recent-hashtags: "최근 해시태그를 글 작성란에 표시"
 | 
			
		||||
    always-show-nsfw: "항상 열람주의 미디어를 표시"
 | 
			
		||||
    always-mark-nsfw: "항상 미디어를 열람주의로 설정하여 게시"
 | 
			
		||||
@@ -232,7 +247,7 @@ common:
 | 
			
		||||
    deck-column-width-wide: "넓음"
 | 
			
		||||
    use-shadow: "UI에 그림자 효과 적용"
 | 
			
		||||
    rounded-corners: "UI의 모서리를 둥글게 설정"
 | 
			
		||||
    circle-icons: "원형 아이콘 사용"
 | 
			
		||||
    circle-icons: "원형 아바타를 사용"
 | 
			
		||||
    contrasted-acct: "사용자명에 대비 추가"
 | 
			
		||||
    wallpaper: "배경"
 | 
			
		||||
    choose-wallpaper: "배경 설정"
 | 
			
		||||
@@ -278,8 +293,20 @@ common:
 | 
			
		||||
    load-raw-images: "첨부 이미지를 고품질로 표시"
 | 
			
		||||
    load-remote-media: "원격 서버의 미디어를 표시"
 | 
			
		||||
    sync: "동기화"
 | 
			
		||||
    save: "저장"
 | 
			
		||||
    saved: "저장하였습니다"
 | 
			
		||||
    home-profile: "홈 프로필"
 | 
			
		||||
    deck-profile: "덱 프로필"
 | 
			
		||||
    room: "룸"
 | 
			
		||||
    _room:
 | 
			
		||||
      graphicsQuality: "그래픽 품질"
 | 
			
		||||
      _graphicsQuality:
 | 
			
		||||
        ultra: "최고"
 | 
			
		||||
        high: "높음"
 | 
			
		||||
        medium: "보통"
 | 
			
		||||
        low: "낮음"
 | 
			
		||||
        cheep: "최저"
 | 
			
		||||
      useOrthographicCamera: "평행 투시 카메라를 사용"
 | 
			
		||||
  search: "검색"
 | 
			
		||||
  delete: "삭제"
 | 
			
		||||
  loading: "로드 중"
 | 
			
		||||
@@ -364,6 +391,9 @@ common/views/pages/explore.vue:
 | 
			
		||||
  federated: "연합"
 | 
			
		||||
  explore: "{host}을(를) 탐색"
 | 
			
		||||
  users-info: "현재 {users} 사용자가 등록되어 있습니다"
 | 
			
		||||
common/views/components/reactions-viewer.details.vue:
 | 
			
		||||
  few-users: "{users}님이 {reaction} 리액션"
 | 
			
		||||
  many-users: "{users}님 외 {omitted}명이 {reaction} 리액션"
 | 
			
		||||
common/views/components/url-preview.vue:
 | 
			
		||||
  enable-player: "플레이어 열기"
 | 
			
		||||
  disable-player: "플레이어 닫기"
 | 
			
		||||
@@ -529,6 +559,8 @@ common/views/components/note-menu.vue:
 | 
			
		||||
  unpin: "프로필에서 고정 해제"
 | 
			
		||||
  delete: "삭제"
 | 
			
		||||
  delete-confirm: "이 글을 삭제하시겠습니까?"
 | 
			
		||||
  delete-and-edit: "삭제 후 편집"
 | 
			
		||||
  delete-and-edit-confirm: "이 글을 삭제한 뒤 다시 편집하시겠습니까? 이 글에 대한 리액션, 리노트, 답글 또한 모두 삭제됩니다."
 | 
			
		||||
  remote: "글 원본 보기"
 | 
			
		||||
  pin-limit-exceeded: "더 이상 고정할 수 없습니다."
 | 
			
		||||
common/views/components/user-menu.vue:
 | 
			
		||||
@@ -723,6 +755,9 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  saved: "프로필을 저장하였습니다"
 | 
			
		||||
  uploading: "업로드 중"
 | 
			
		||||
  upload-failed: "업로드에 실패하였습니다"
 | 
			
		||||
  unable-to-process: "작업을 완료할 수 없습니다"
 | 
			
		||||
  avatar-not-an-image: "아바타로 지정한 파일이 이미지 형식이 아닙니다"
 | 
			
		||||
  banner-not-an-image: "배너로 지정한 파일이 이미지 형식이 아닙니다"
 | 
			
		||||
  email: "메일 설정"
 | 
			
		||||
  email-address: "메일 주소"
 | 
			
		||||
  email-verified: "매일 주소가 확인되었습니다"
 | 
			
		||||
@@ -742,6 +777,9 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  danger-zone: "위험한 설정"
 | 
			
		||||
  delete-account: "계정 삭제"
 | 
			
		||||
  account-deleted: "계정이 삭제되었습니다. 데이터가 사라질 때까지 시간이 걸릴 수 있습니다."
 | 
			
		||||
  profile-metadata: "프로필 추가 정보"
 | 
			
		||||
  metadata-label: "라벨"
 | 
			
		||||
  metadata-content: "내용"
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "사용자"
 | 
			
		||||
  rename: "리스트 이름 바꾸기"
 | 
			
		||||
@@ -851,6 +889,7 @@ desktop:
 | 
			
		||||
  uploading-avatar: "새로운 아바타를 업로드하고 있습니다"
 | 
			
		||||
  avatar-updated: "아바타가 변경되었습니다"
 | 
			
		||||
  choose-avatar: "아바타 이미지를 선택"
 | 
			
		||||
  unable-to-process: "작업을 완료할 수 없습니다"
 | 
			
		||||
  invalid-filetype: "이 형식의 파일은 지원되지 않습니다"
 | 
			
		||||
desktop/views/components/activity.chart.vue:
 | 
			
		||||
  total: "검은색 ... 전체"
 | 
			
		||||
@@ -892,7 +931,7 @@ desktop/views/components/drive.file.vue:
 | 
			
		||||
    copy-url: "URL 복사"
 | 
			
		||||
    download: "다운로드"
 | 
			
		||||
    else-files: "기타"
 | 
			
		||||
    set-as-avatar: "아이콘으로 설정"
 | 
			
		||||
    set-as-avatar: "아바타로 설정"
 | 
			
		||||
    set-as-banner: "배너로 설정"
 | 
			
		||||
    open-in-app: "앱에서 열기"
 | 
			
		||||
    add-app: "앱 추가"
 | 
			
		||||
@@ -1110,6 +1149,7 @@ desktop/views/components/ui.header.account.vue:
 | 
			
		||||
  groups: "그룹"
 | 
			
		||||
  follow-requests: "팔로우 요청"
 | 
			
		||||
  admin: "관리"
 | 
			
		||||
  room: "룸"
 | 
			
		||||
desktop/views/components/ui.header.nav.vue:
 | 
			
		||||
  game: "게임"
 | 
			
		||||
desktop/views/components/ui.header.notifications.vue:
 | 
			
		||||
@@ -1363,6 +1403,8 @@ admin/views/users.vue:
 | 
			
		||||
  remote-user-updated: "원격 사용자 정보를 갱신하였습니다"
 | 
			
		||||
  delete-all-files: "모든 파일 삭제"
 | 
			
		||||
  delete-all-files-confirm: "모든 파일을 삭제하시겠습니까?"
 | 
			
		||||
  username: "사용자명"
 | 
			
		||||
  host: "관리자"
 | 
			
		||||
  users:
 | 
			
		||||
    title: "사용자"
 | 
			
		||||
    sort:
 | 
			
		||||
@@ -1393,6 +1435,12 @@ admin/views/moderators.vue:
 | 
			
		||||
    added: "모더레이터를 등록하였습니다"
 | 
			
		||||
    remove: "해제"
 | 
			
		||||
    removed: "모더레이터 등록을 해제했습니다"
 | 
			
		||||
  logs:
 | 
			
		||||
    title: "로그"
 | 
			
		||||
    moderator: "모더레이터"
 | 
			
		||||
    type: "작업"
 | 
			
		||||
    at: "일시"
 | 
			
		||||
    info: "정보"
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "이모지 등록"
 | 
			
		||||
@@ -1681,6 +1729,8 @@ mobile/views/pages/search.vue:
 | 
			
		||||
  not-found: "\"{q}\" 와 일치하는 글을 찾을 수 없습니다."
 | 
			
		||||
mobile/views/pages/selectdrive.vue:
 | 
			
		||||
  select-file: "파일 선택"
 | 
			
		||||
mobile/views/pages/notifications.vue:
 | 
			
		||||
  notifications: "알림"
 | 
			
		||||
mobile/views/pages/settings.vue:
 | 
			
		||||
  signed-in-as: "{}(으)로 로그인"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
@@ -1771,6 +1821,7 @@ pages:
 | 
			
		||||
  read-page: "소스 표시중"
 | 
			
		||||
  page-created: "페이지를 만들었습니다"
 | 
			
		||||
  page-updated: "페이지를 수정했습니다"
 | 
			
		||||
  name-already-exists: "지정한 페이지 URL은 이미 존재합니다"
 | 
			
		||||
  are-you-sure-delete: "이 페이지를 삭제하시겠습니까?"
 | 
			
		||||
  page-deleted: "페이지가 삭제되었습니다"
 | 
			
		||||
  edit-this-page: "이 페이지를 편집"
 | 
			
		||||
@@ -1861,6 +1912,12 @@ pages:
 | 
			
		||||
          message: "눌렀을 때 표시할 메시지"
 | 
			
		||||
          variable: "보낼 변수"
 | 
			
		||||
          no-variable: "없음"
 | 
			
		||||
    radioButton: "선택지"
 | 
			
		||||
    _radioButton:
 | 
			
		||||
      name: "변수명"
 | 
			
		||||
      title: "제목"
 | 
			
		||||
      values: "줄바꿈으로 구분된 선택지"
 | 
			
		||||
      default: "기본값"
 | 
			
		||||
  script:
 | 
			
		||||
    categories:
 | 
			
		||||
      flow: "흐름 제어"
 | 
			
		||||
@@ -2033,3 +2090,64 @@ pages:
 | 
			
		||||
    enviromentVariables: "환경 변수"
 | 
			
		||||
    pageVariables: "페이지 요소"
 | 
			
		||||
    argVariables: "입력 슬롯"
 | 
			
		||||
room:
 | 
			
		||||
  add-furniture: "가구를 배치"
 | 
			
		||||
  translate: "이동"
 | 
			
		||||
  rotate: "회전"
 | 
			
		||||
  exit: "선택 해제"
 | 
			
		||||
  remove: "치우기"
 | 
			
		||||
  save: "저장"
 | 
			
		||||
  saved: "저장하였습니다"
 | 
			
		||||
  clear: "모두 치우기"
 | 
			
		||||
  clear-confirm: "정말 방 안의 모든 가구를 치우시겠습니까?"
 | 
			
		||||
  leave-confirm: "저장되지 않은 변경 사항이 있습니다. 정말 떠나시겠습니까?"
 | 
			
		||||
  chooseImage: "이미지 선택"
 | 
			
		||||
  room-type: "룸 종류"
 | 
			
		||||
  carpet-color: "바닥 색상"
 | 
			
		||||
  rooms:
 | 
			
		||||
    default: "기본"
 | 
			
		||||
    washitsu: "일본식"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    milk: "우유 팩"
 | 
			
		||||
    bed: "침대"
 | 
			
		||||
    low-table: "낮은 테이블"
 | 
			
		||||
    desk: "책상"
 | 
			
		||||
    chair: "의자"
 | 
			
		||||
    chair2: "의자 2"
 | 
			
		||||
    fan: "환기구"
 | 
			
		||||
    pc: "컴퓨터"
 | 
			
		||||
    plant: "관엽식물"
 | 
			
		||||
    plant2: "관엽식물 2"
 | 
			
		||||
    eraser: "지우개"
 | 
			
		||||
    pencil: "연필"
 | 
			
		||||
    pudding: "푸딩"
 | 
			
		||||
    cardboard-box: "골판지 상자"
 | 
			
		||||
    cardboard-box2: "골판지 상자 2"
 | 
			
		||||
    cardboard-box3: "골판지 상자 3"
 | 
			
		||||
    book: "책"
 | 
			
		||||
    book2: "책 2"
 | 
			
		||||
    piano: "피아노"
 | 
			
		||||
    facial-tissue: "휴지 상자"
 | 
			
		||||
    server: "서버"
 | 
			
		||||
    moon: "달"
 | 
			
		||||
    corkboard: "게시판"
 | 
			
		||||
    mousepad: "마우스 패드"
 | 
			
		||||
    monitor: "모니터"
 | 
			
		||||
    keyboard: "키보드"
 | 
			
		||||
    carpet-stripe: "카페트 (줄무늬)"
 | 
			
		||||
    mat: "매트"
 | 
			
		||||
    color-box: "책장"
 | 
			
		||||
    wall-clock: "벽걸이 시계"
 | 
			
		||||
    photoframe: "액자"
 | 
			
		||||
    cube: "큐브"
 | 
			
		||||
    tv: "TV"
 | 
			
		||||
    pinguin: "펭귄"
 | 
			
		||||
    rubik-cube: "루빅스 큐브"
 | 
			
		||||
    poster-h: "포스터 (가로)"
 | 
			
		||||
    poster-v: "포스터 (세로)"
 | 
			
		||||
    sofa: "소파"
 | 
			
		||||
    spiral: "나선형 계단"
 | 
			
		||||
    bin: "휴지통"
 | 
			
		||||
    cup-noodle: "컵라면"
 | 
			
		||||
    holo-display: "홀로그램"
 | 
			
		||||
    energy-drink: "에너지 드링크"
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,11 @@ common:
 | 
			
		||||
    reaction: "Reactie"
 | 
			
		||||
  close: "Sluiten"
 | 
			
		||||
  enter-password: "Voer het wachtwoord in"
 | 
			
		||||
  notification-types:
 | 
			
		||||
    all: "Alle"
 | 
			
		||||
    follow: "Volgend"
 | 
			
		||||
    reply: "Beantwoorden"
 | 
			
		||||
    reaction: "Reactie"
 | 
			
		||||
  time:
 | 
			
		||||
    unknown: "onbekend"
 | 
			
		||||
    future: "toekomstig"
 | 
			
		||||
@@ -199,6 +204,7 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  name: "Naam"
 | 
			
		||||
  avatar: "Gebruikersafbeelding"
 | 
			
		||||
  banner: "Omslagfoto"
 | 
			
		||||
  unable-to-process: "De operatie kan niet worden voltooid."
 | 
			
		||||
  export-targets:
 | 
			
		||||
    following-list: "Volgend"
 | 
			
		||||
    user-lists: "Lijsten"
 | 
			
		||||
@@ -226,6 +232,8 @@ common/views/pages/follow.vue:
 | 
			
		||||
  follow: "Volgend"
 | 
			
		||||
desktop:
 | 
			
		||||
  banner: "Omslagfoto"
 | 
			
		||||
  avatar: "Gebruikersafbeelding"
 | 
			
		||||
  unable-to-process: "De operatie kan niet worden voltooid."
 | 
			
		||||
desktop/views/components/activity.chart.vue:
 | 
			
		||||
  total: "Zwart ... totaal"
 | 
			
		||||
  notes: "Blauw ... notities"
 | 
			
		||||
@@ -425,6 +433,7 @@ admin/views/drive.vue:
 | 
			
		||||
    local: "Lokaal"
 | 
			
		||||
  delete: "Verwijderen"
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  username: "Gebruikersnaam"
 | 
			
		||||
  users:
 | 
			
		||||
    title: "Gebruiker"
 | 
			
		||||
    state:
 | 
			
		||||
@@ -566,6 +575,8 @@ mobile/views/pages/search.vue:
 | 
			
		||||
  search: "Zoeken"
 | 
			
		||||
mobile/views/pages/selectdrive.vue:
 | 
			
		||||
  select-file: "Kies een bestand"
 | 
			
		||||
mobile/views/pages/notifications.vue:
 | 
			
		||||
  notifications: "Meldingen"
 | 
			
		||||
mobile/views/pages/settings.vue:
 | 
			
		||||
  signed-in-as: "Ingelogd als {}"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
@@ -628,3 +639,7 @@ pages:
 | 
			
		||||
        arg1: "Lijsten"
 | 
			
		||||
    types:
 | 
			
		||||
      array: "Lijsten"
 | 
			
		||||
room:
 | 
			
		||||
  translate: "Verplaatsen"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "Maan"
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,10 @@ common:
 | 
			
		||||
    rich-contents: "Innlegg"
 | 
			
		||||
    drive: "Disk"
 | 
			
		||||
  close: "Lukk"
 | 
			
		||||
  notification-types:
 | 
			
		||||
    all: "Alle"
 | 
			
		||||
    follow: "Følger"
 | 
			
		||||
    reply: "Svar"
 | 
			
		||||
  got-it: "Skjønner!"
 | 
			
		||||
  notification:
 | 
			
		||||
    file-uploaded: "Filen ble lastet opp!"
 | 
			
		||||
@@ -78,6 +82,7 @@ common:
 | 
			
		||||
  _settings:
 | 
			
		||||
    notification: "Notifikasjon"
 | 
			
		||||
    password: "Passord"
 | 
			
		||||
    save: "Lagre"
 | 
			
		||||
  search: "Søk"
 | 
			
		||||
  delete: "Slett"
 | 
			
		||||
  loading: "Laster inn..."
 | 
			
		||||
@@ -345,12 +350,16 @@ admin/views/drive.vue:
 | 
			
		||||
    local: "Lokalt"
 | 
			
		||||
  delete: "Slett"
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  username: "Brukernavn"
 | 
			
		||||
  users:
 | 
			
		||||
    title: "Bruker"
 | 
			
		||||
    state:
 | 
			
		||||
      all: "Alle"
 | 
			
		||||
    origin:
 | 
			
		||||
      local: "Lokalt"
 | 
			
		||||
admin/views/moderators.vue:
 | 
			
		||||
  logs:
 | 
			
		||||
    info: "Informasjon"
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    add: "Legg til"
 | 
			
		||||
@@ -462,6 +471,8 @@ mobile/views/pages/games/reversi.vue:
 | 
			
		||||
  reversi: "Reversi"
 | 
			
		||||
mobile/views/pages/search.vue:
 | 
			
		||||
  search: "Søk"
 | 
			
		||||
mobile/views/pages/notifications.vue:
 | 
			
		||||
  notifications: "Notifikasjon"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  following: "Følger"
 | 
			
		||||
  followers: "Følgere"
 | 
			
		||||
@@ -509,3 +520,9 @@ pages:
 | 
			
		||||
        arg1: "Lister"
 | 
			
		||||
    types:
 | 
			
		||||
      array: "Lister"
 | 
			
		||||
room:
 | 
			
		||||
  translate: "Flytt"
 | 
			
		||||
  save: "Lagre"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "Måne"
 | 
			
		||||
    bin: "Papirkurv"
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,18 @@ common:
 | 
			
		||||
  signin: "Zaloguj się"
 | 
			
		||||
  signup: "Rejestracja"
 | 
			
		||||
  signout: "Wyloguj się"
 | 
			
		||||
  delete-confirm: "Czy na pewno chcesz usunąć ten wpis?"
 | 
			
		||||
  notification-type: "Typy powiadomień"
 | 
			
		||||
  notification-types:
 | 
			
		||||
    all: "Wszyscy"
 | 
			
		||||
    pollVote: "Głosy"
 | 
			
		||||
    follow: "Śledzeni"
 | 
			
		||||
    receiveFollowRequest: "Prośby o śledzenie"
 | 
			
		||||
    reply: "Odpowiedzi"
 | 
			
		||||
    quote: "Cytat"
 | 
			
		||||
    renote: "Udostępnij"
 | 
			
		||||
    mention: "Wzmianki"
 | 
			
		||||
    reaction: "Reakcje"
 | 
			
		||||
  got-it: "Rozumiem!"
 | 
			
		||||
  customization-tips:
 | 
			
		||||
    title: "Wskazówki o dostosowywaniu"
 | 
			
		||||
@@ -86,8 +98,10 @@ common:
 | 
			
		||||
    visibility: "Widoczność"
 | 
			
		||||
    error: "Błąd"
 | 
			
		||||
    enter-username: "Wprowadź nazwę użytkownika"
 | 
			
		||||
    specified-recipient: "Adresat"
 | 
			
		||||
    add-visible-user: "Dodaj użytkownika"
 | 
			
		||||
    username-prompt: "Wprowadź nazwę użytkownika"
 | 
			
		||||
    enter-file-name: "Wprowadź nazwę pliku"
 | 
			
		||||
  weekday-short:
 | 
			
		||||
    sunday: "N"
 | 
			
		||||
    monday: "Pn"
 | 
			
		||||
@@ -150,6 +164,7 @@ common:
 | 
			
		||||
    note-visibility: "Widoczność wpisów"
 | 
			
		||||
    remember-note-visibility: "Zapamiętaj widoczność wpisów"
 | 
			
		||||
    web-search-engine: "Wyszukiwarka internetowa"
 | 
			
		||||
    paste: "Wklej"
 | 
			
		||||
    line-width: "Szerokości linii"
 | 
			
		||||
    line-width-thin: "Cienka"
 | 
			
		||||
    line-width-normal: "Normalna"
 | 
			
		||||
@@ -178,6 +193,8 @@ common:
 | 
			
		||||
    version: "Wersja:"
 | 
			
		||||
    do-update: "Sprawdź dostępność nowych aktualizacji"
 | 
			
		||||
    navbar-position-left: "Z lewej"
 | 
			
		||||
    save: "Zapisz"
 | 
			
		||||
    saved: "Zapisano"
 | 
			
		||||
  search: "Szukaj"
 | 
			
		||||
  delete: "Usuń"
 | 
			
		||||
  loading: "Ładowanie"
 | 
			
		||||
@@ -516,6 +533,7 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  saved: "Pomyślnie zaktualizowano profil"
 | 
			
		||||
  uploading: "Wysyłanie"
 | 
			
		||||
  upload-failed: "Wysyłanie nie powiodło się"
 | 
			
		||||
  unable-to-process: "Nie udało się ukończyć działania."
 | 
			
		||||
  email: "Ustawienia e-mail"
 | 
			
		||||
  email-address: "Adres e-mail"
 | 
			
		||||
  email-verified: "Twój adres e-mail został zweryfikowany."
 | 
			
		||||
@@ -610,6 +628,7 @@ desktop:
 | 
			
		||||
  uploading-avatar: "Wysyłanie awatara"
 | 
			
		||||
  avatar-updated: "Wysłano awatar"
 | 
			
		||||
  choose-avatar: "Wybierz awatar"
 | 
			
		||||
  unable-to-process: "Nie udało się ukończyć działania."
 | 
			
		||||
desktop/views/components/activity.chart.vue:
 | 
			
		||||
  total: "Czarny … Łącznie"
 | 
			
		||||
  notes: "Niebieski … Wpisy"
 | 
			
		||||
@@ -908,6 +927,7 @@ admin/views/drive.vue:
 | 
			
		||||
  unmark-as-sensitive: "Cofnij oznaczenie jako zawartość wrażliwą"
 | 
			
		||||
admin/views/users.vue:
 | 
			
		||||
  user-not-found: "Nie znaleziono użytkownika"
 | 
			
		||||
  username: "Nazwa użytkownika"
 | 
			
		||||
  users:
 | 
			
		||||
    title: "Użytkownicy"
 | 
			
		||||
    sort:
 | 
			
		||||
@@ -923,6 +943,9 @@ admin/views/users.vue:
 | 
			
		||||
admin/views/moderators.vue:
 | 
			
		||||
  add-moderator:
 | 
			
		||||
    add: "Zarejestruj się"
 | 
			
		||||
  logs:
 | 
			
		||||
    moderator: "Moderatorzy"
 | 
			
		||||
    info: "Informacje"
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    name: "Nazwa Emoji"
 | 
			
		||||
@@ -1129,6 +1152,8 @@ mobile/views/pages/search.vue:
 | 
			
		||||
  search: "Szukaj"
 | 
			
		||||
mobile/views/pages/selectdrive.vue:
 | 
			
		||||
  select-file: "Wybierz plik"
 | 
			
		||||
mobile/views/pages/notifications.vue:
 | 
			
		||||
  notifications: "Powiadomienia"
 | 
			
		||||
mobile/views/pages/settings.vue:
 | 
			
		||||
  signed-in-as: "Zalogowany jako {}"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
@@ -1212,6 +1237,8 @@ pages:
 | 
			
		||||
      text: "Tytuł"
 | 
			
		||||
    _button:
 | 
			
		||||
      text: "Tytuł"
 | 
			
		||||
    _radioButton:
 | 
			
		||||
      title: "Tytuł"
 | 
			
		||||
  script:
 | 
			
		||||
    categories:
 | 
			
		||||
      random: "Losowy"
 | 
			
		||||
@@ -1232,3 +1259,10 @@ pages:
 | 
			
		||||
        arg1: "Listy"
 | 
			
		||||
    types:
 | 
			
		||||
      array: "Listy"
 | 
			
		||||
room:
 | 
			
		||||
  translate: "Przenieś"
 | 
			
		||||
  save: "Zapisz"
 | 
			
		||||
  saved: "Zapisano"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "Księżyc"
 | 
			
		||||
    bin: "Kosz"
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,8 @@ common:
 | 
			
		||||
  application-authorization: "Aplicativos autorizados"
 | 
			
		||||
  close: "Fechar"
 | 
			
		||||
  do-not-copy-paste: "Por favor, não digite ou copie o código aqui. A conta pode ser comprometida."
 | 
			
		||||
  notification-types:
 | 
			
		||||
    follow: "Seguindo"
 | 
			
		||||
  got-it: "Entendi!"
 | 
			
		||||
  customization-tips:
 | 
			
		||||
    title: "Dicas de personalização"
 | 
			
		||||
@@ -282,3 +284,7 @@ pages:
 | 
			
		||||
  blocks:
 | 
			
		||||
    image: "Imagens"
 | 
			
		||||
    post: "Formulário de publicação"
 | 
			
		||||
room:
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "Lua"
 | 
			
		||||
    bin: "Lixo"
 | 
			
		||||
 
 | 
			
		||||
@@ -168,3 +168,7 @@ pages:
 | 
			
		||||
      random: "Случайно"
 | 
			
		||||
    blocks:
 | 
			
		||||
      random: "Случайно"
 | 
			
		||||
room:
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "Луна"
 | 
			
		||||
    bin: "Мусорное ведро"
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,20 @@ common:
 | 
			
		||||
  signout: "退出"
 | 
			
		||||
  reload-to-apply-the-setting: "必须重新加载页面以应用此设置。 确实要立即重新加载吗?"
 | 
			
		||||
  fetching-as-ap-object: "联合查询"
 | 
			
		||||
  unfollow-confirm: "取消对{name}的关注?"
 | 
			
		||||
  delete-confirm: "确定删除这个投稿吗?"
 | 
			
		||||
  signin-required: "请先登录"
 | 
			
		||||
  notification-type: "通知类型"
 | 
			
		||||
  notification-types:
 | 
			
		||||
    all: "所有"
 | 
			
		||||
    pollVote: "投票"
 | 
			
		||||
    follow: "关注中"
 | 
			
		||||
    receiveFollowRequest: "关注请求"
 | 
			
		||||
    reply: "回复"
 | 
			
		||||
    quote: "引用"
 | 
			
		||||
    renote: "转推"
 | 
			
		||||
    mention: "提及"
 | 
			
		||||
    reaction: "回应"
 | 
			
		||||
  got-it: "知道了"
 | 
			
		||||
  customization-tips:
 | 
			
		||||
    title: "自定义提示"
 | 
			
		||||
@@ -119,9 +133,11 @@ common:
 | 
			
		||||
    geolocation-alert: "您的设备不支持定位服务"
 | 
			
		||||
    error: "错误"
 | 
			
		||||
    enter-username: "输入用户名"
 | 
			
		||||
    specified-recipient: "收件人"
 | 
			
		||||
    add-visible-user: "添加用户"
 | 
			
		||||
    cw-placeholder: "评论帖子(可选)"
 | 
			
		||||
    username-prompt: "输入用户名"
 | 
			
		||||
    enter-file-name: "编辑文件名"
 | 
			
		||||
  weekday-short:
 | 
			
		||||
    sunday: "日"
 | 
			
		||||
    monday: "一"
 | 
			
		||||
@@ -188,6 +204,11 @@ common:
 | 
			
		||||
    remember-note-visibility: "记住帖子可见性"
 | 
			
		||||
    web-search-engine: "搜索引擎"
 | 
			
		||||
    web-search-engine-desc: "例如: https://www.google.com/?#q={{query}}"
 | 
			
		||||
    paste: "粘贴"
 | 
			
		||||
    pasted-file-name: "粘贴的文件名模板"
 | 
			
		||||
    pasted-file-name-desc: "例: \"yyyy-MM-dd HH-mm-ss [{{number}}]\" → \"2018-03-20 21-30-24 1\""
 | 
			
		||||
    paste-dialog: "粘贴时编辑文件名"
 | 
			
		||||
    paste-dialog-desc: "粘贴时显示编辑文件名的对话框"
 | 
			
		||||
    keep-cw: "保留内容警告"
 | 
			
		||||
    keep-cw-desc: "在回复帖子时,如果原帖设置了内容警告,默认情况下回帖也会设置相同的内容警告。"
 | 
			
		||||
    i-like-sushi: "相比于布丁来说, 我更喜欢寿司。"
 | 
			
		||||
@@ -195,6 +216,7 @@ common:
 | 
			
		||||
    use-avatar-reversi-stones: "用头像作为黑白棋的棋子"
 | 
			
		||||
    disable-animated-mfm: "在帖子中禁用动画文本"
 | 
			
		||||
    disable-showing-animated-images: "不播放动画"
 | 
			
		||||
    enable-quick-notification-view: "启用通知快速查看"
 | 
			
		||||
    suggest-recent-hashtags: "在帖子表单上显示最近流行的哈希标签"
 | 
			
		||||
    always-show-nsfw: "总是显示 NSFW 的内容"
 | 
			
		||||
    always-mark-nsfw: "总是用 NSFW 来标记附件"
 | 
			
		||||
@@ -225,7 +247,7 @@ common:
 | 
			
		||||
    deck-column-width-wide: "宽"
 | 
			
		||||
    use-shadow: "在UI中使用阴影效果"
 | 
			
		||||
    rounded-corners: "UI界面圆角效果"
 | 
			
		||||
    circle-icons: "使用圆形图标"
 | 
			
		||||
    circle-icons: "使用圆形头像"
 | 
			
		||||
    contrasted-acct: "增加用户名的对比度"
 | 
			
		||||
    wallpaper: "壁纸"
 | 
			
		||||
    choose-wallpaper: "选择壁纸"
 | 
			
		||||
@@ -271,8 +293,20 @@ common:
 | 
			
		||||
    load-raw-images: "以原始质量显示附加图像"
 | 
			
		||||
    load-remote-media: "显示来自远程服务器的媒体"
 | 
			
		||||
    sync: "同步"
 | 
			
		||||
    save: "保存"
 | 
			
		||||
    saved: "已保存"
 | 
			
		||||
    home-profile: "定制首页数据"
 | 
			
		||||
    deck-profile: "定制Deck数据"
 | 
			
		||||
    room: "房间"
 | 
			
		||||
    _room:
 | 
			
		||||
      graphicsQuality: "图形质量"
 | 
			
		||||
      _graphicsQuality:
 | 
			
		||||
        ultra: "最高"
 | 
			
		||||
        high: "高"
 | 
			
		||||
        medium: "中"
 | 
			
		||||
        low: "低"
 | 
			
		||||
        cheep: "最低"
 | 
			
		||||
      useOrthographicCamera: "使用正交相机"
 | 
			
		||||
  search: "搜索"
 | 
			
		||||
  delete: "删除"
 | 
			
		||||
  loading: "正在加载中"
 | 
			
		||||
@@ -357,6 +391,9 @@ common/views/pages/explore.vue:
 | 
			
		||||
  federated: "联合"
 | 
			
		||||
  explore: "查找{host}"
 | 
			
		||||
  users-info: "当前有{users}个注册用户"
 | 
			
		||||
common/views/components/reactions-viewer.details.vue:
 | 
			
		||||
  few-users: "{users}作出了{reaction}的回应"
 | 
			
		||||
  many-users: "{users}和其他{omitted}人做出了{reaction}的回应"
 | 
			
		||||
common/views/components/url-preview.vue:
 | 
			
		||||
  enable-player: "打开播放器"
 | 
			
		||||
  disable-player: "关闭播放器"
 | 
			
		||||
@@ -522,7 +559,10 @@ common/views/components/note-menu.vue:
 | 
			
		||||
  unpin: "取消置顶"
 | 
			
		||||
  delete: "删除"
 | 
			
		||||
  delete-confirm: "确定删除这个投稿吗?"
 | 
			
		||||
  delete-and-edit: "删除和编辑"
 | 
			
		||||
  delete-and-edit-confirm: "要删除此帖并再次编辑吗?对此帖的所有回应,转推和回复也将被删除。"
 | 
			
		||||
  remote: "显示原始投稿"
 | 
			
		||||
  pin-limit-exceeded: "无法置顶更多了。"
 | 
			
		||||
common/views/components/user-menu.vue:
 | 
			
		||||
  mention: "提到"
 | 
			
		||||
  mute: "屏蔽"
 | 
			
		||||
@@ -592,6 +632,12 @@ common/views/components/emoji-picker.vue:
 | 
			
		||||
  symbols: "符号"
 | 
			
		||||
  flags: "旗帜"
 | 
			
		||||
common/views/components/settings/app-type.vue:
 | 
			
		||||
  title: "模式"
 | 
			
		||||
  intro: "您可以指定使用桌面版或移动版。"
 | 
			
		||||
  choices:
 | 
			
		||||
    auto: "自动选择"
 | 
			
		||||
    desktop: "固定为桌面版"
 | 
			
		||||
    mobile: "固定为移动版"
 | 
			
		||||
  info: "更改将在刷新页面后生效。"
 | 
			
		||||
common/views/components/signin.vue:
 | 
			
		||||
  username: "用户名"
 | 
			
		||||
@@ -603,6 +649,8 @@ common/views/components/signin.vue:
 | 
			
		||||
  signin-with-github: "用 GitHub 登录"
 | 
			
		||||
  signin-with-discord: "用 Discord 登录"
 | 
			
		||||
  login-failed: "登录失败。请检查用户名和密码。"
 | 
			
		||||
  tap-key: "点击安全密钥登录"
 | 
			
		||||
  enter-2fa-code: "输入验证码"
 | 
			
		||||
common/views/components/signup.vue:
 | 
			
		||||
  invitation-code: "邀请码"
 | 
			
		||||
  invitation-info: "如果您没有邀请码,请联系<a href=\"{}\">管理员</a>。"
 | 
			
		||||
@@ -707,6 +755,9 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  saved: "您的个人资料已保存"
 | 
			
		||||
  uploading: "正在上传"
 | 
			
		||||
  upload-failed: "上传失败"
 | 
			
		||||
  unable-to-process: "无法完成操作"
 | 
			
		||||
  avatar-not-an-image: "选择的头像文件不是图片格式"
 | 
			
		||||
  banner-not-an-image: "选择的横幅背景不是图片格式"
 | 
			
		||||
  email: "邮件设置"
 | 
			
		||||
  email-address: "电子邮件地址"
 | 
			
		||||
  email-verified: "电子邮件地址已验证"
 | 
			
		||||
@@ -726,6 +777,9 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  danger-zone: "危险选项"
 | 
			
		||||
  delete-account: "删除帐户"
 | 
			
		||||
  account-deleted: "帐户已被删除。 数据会在一段时间之后清除。"
 | 
			
		||||
  profile-metadata: "个人资料补充信息"
 | 
			
		||||
  metadata-label: "标签"
 | 
			
		||||
  metadata-content: "内容"
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "用户"
 | 
			
		||||
  rename: "重命名列表"
 | 
			
		||||
@@ -835,6 +889,7 @@ desktop:
 | 
			
		||||
  uploading-avatar: "上传一个新的头像"
 | 
			
		||||
  avatar-updated: "成功上传头像"
 | 
			
		||||
  choose-avatar: "选择作为头像的图片"
 | 
			
		||||
  unable-to-process: "无法完成操作"
 | 
			
		||||
  invalid-filetype: "不接受此文件类型"
 | 
			
		||||
desktop/views/components/activity.chart.vue:
 | 
			
		||||
  total: "黑 ... 总计"
 | 
			
		||||
@@ -876,7 +931,7 @@ desktop/views/components/drive.file.vue:
 | 
			
		||||
    copy-url: "复制链接"
 | 
			
		||||
    download: "下载"
 | 
			
		||||
    else-files: "其他"
 | 
			
		||||
    set-as-avatar: "设置为头像"
 | 
			
		||||
    set-as-avatar: "设为头像"
 | 
			
		||||
    set-as-banner: "设置为背景"
 | 
			
		||||
    open-in-app: "在应用程序中打开"
 | 
			
		||||
    add-app: "添加应用"
 | 
			
		||||
@@ -885,9 +940,12 @@ desktop/views/components/drive.file.vue:
 | 
			
		||||
    copied: "已复制"
 | 
			
		||||
    copied-url-to-clipboard: "已复制链接到剪贴板"
 | 
			
		||||
desktop/views/components/drive.folder.vue:
 | 
			
		||||
  upload-folder: "默认上传文件夹"
 | 
			
		||||
  unable-to-process: "无法完成操作"
 | 
			
		||||
  circular-reference-detected: "目标文件夹是您要移动的文件夹的子文件夹。"
 | 
			
		||||
  unhandled-error: "未知错误"
 | 
			
		||||
  unable-to-delete: "无法删除"
 | 
			
		||||
  has-child-files-or-folders: "此文件夹不为空,无法删除。"
 | 
			
		||||
  contextmenu:
 | 
			
		||||
    move-to-this-folder: "移动到此文件夹"
 | 
			
		||||
    show-in-new-window: "在新窗口打开"
 | 
			
		||||
@@ -895,6 +953,7 @@ desktop/views/components/drive.folder.vue:
 | 
			
		||||
    rename-folder: "重命名文件夹"
 | 
			
		||||
    input-new-folder-name: "请输入新文件名"
 | 
			
		||||
    else-folders: "其他"
 | 
			
		||||
    set-as-upload-folder: "设置为默认上传文件夹"
 | 
			
		||||
desktop/views/components/drive.vue:
 | 
			
		||||
  search: "搜索"
 | 
			
		||||
  empty-draghover: "放在这里!因为你知道我很可爱,对吗?"
 | 
			
		||||
@@ -1000,6 +1059,16 @@ desktop/views/components/settings.2fa.vue:
 | 
			
		||||
  success: "设置完成"
 | 
			
		||||
  failed: "设置失败, 请确保您的密钥是正确的。"
 | 
			
		||||
  info: "从下次登录Misskey时,您的设备上显示的令牌以及密码也是必需的。"
 | 
			
		||||
  totp-header: "身份验证 App"
 | 
			
		||||
  security-key-header: "安全密钥"
 | 
			
		||||
  security-key: "为了增强安全性,您可以使用支持FIDO2的硬件安全密钥登录您的帐户。 登录时,您将需要注册安全密钥或身份验证应用。"
 | 
			
		||||
  last-used: "最后使用:"
 | 
			
		||||
  activate-key: "单击以激活您的安全密钥"
 | 
			
		||||
  security-key-name: "密钥名称"
 | 
			
		||||
  register-security-key: "安全密钥注册完成"
 | 
			
		||||
  something-went-wrong: "糟糕!安全密钥注册出现问题:"
 | 
			
		||||
  key-unregistered: "安全密钥已被删除。"
 | 
			
		||||
  use-password-less-login: "使用免密码登录"
 | 
			
		||||
common/views/components/media-image.vue:
 | 
			
		||||
  sensitive: "阅读注意"
 | 
			
		||||
  click-to-show: "点击查看"
 | 
			
		||||
@@ -1024,7 +1093,9 @@ common/views/components/drive-settings.vue:
 | 
			
		||||
  max: "容量"
 | 
			
		||||
  in-use: "已使用"
 | 
			
		||||
  stats: "统计"
 | 
			
		||||
  default-upload-folder: "默认上传文件夹"
 | 
			
		||||
  default-upload-folder-name: "文件夹"
 | 
			
		||||
  change-default-upload-folder: "更改文件夹"
 | 
			
		||||
common/views/components/mute-and-block.vue:
 | 
			
		||||
  mute-and-block: "屏蔽/拉黑"
 | 
			
		||||
  mute: "屏蔽"
 | 
			
		||||
@@ -1078,6 +1149,7 @@ desktop/views/components/ui.header.account.vue:
 | 
			
		||||
  groups: "群组"
 | 
			
		||||
  follow-requests: "关注申请"
 | 
			
		||||
  admin: "管理"
 | 
			
		||||
  room: "房间"
 | 
			
		||||
desktop/views/components/ui.header.nav.vue:
 | 
			
		||||
  game: "游戏"
 | 
			
		||||
desktop/views/components/ui.header.notifications.vue:
 | 
			
		||||
@@ -1114,6 +1186,9 @@ admin/views/index.vue:
 | 
			
		||||
  back-to-misskey: "返回 Misskey"
 | 
			
		||||
admin/views/db.vue:
 | 
			
		||||
  tables: "表格"
 | 
			
		||||
  vacuum: "VACUUM"
 | 
			
		||||
  vacuum-info: "清理数据库。 保持数据完整并减少磁盘使用量。 此操作通常会自动定期执行。"
 | 
			
		||||
  vacuum-exclamation: "运行VACUUM之后,数据库上的负载可能会持续一段时间,并且可能不响应用户操作。"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "Dashboard"
 | 
			
		||||
  accounts: "账户"
 | 
			
		||||
@@ -1328,6 +1403,8 @@ admin/views/users.vue:
 | 
			
		||||
  remote-user-updated: "远程用户信息已更新"
 | 
			
		||||
  delete-all-files: "删除所有文件"
 | 
			
		||||
  delete-all-files-confirm: "删除所有文件吗?"
 | 
			
		||||
  username: "用户名"
 | 
			
		||||
  host: "主机名"
 | 
			
		||||
  users:
 | 
			
		||||
    title: "用户"
 | 
			
		||||
    sort:
 | 
			
		||||
@@ -1358,6 +1435,12 @@ admin/views/moderators.vue:
 | 
			
		||||
    added: "已注册版主。"
 | 
			
		||||
    remove: "取消"
 | 
			
		||||
    removed: "取消注册版主"
 | 
			
		||||
  logs:
 | 
			
		||||
    title: "登录"
 | 
			
		||||
    moderator: "版主"
 | 
			
		||||
    type: "操作"
 | 
			
		||||
    at: "日期和时间"
 | 
			
		||||
    info: "信息"
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
    title: "添加emoji"
 | 
			
		||||
@@ -1646,6 +1729,8 @@ mobile/views/pages/search.vue:
 | 
			
		||||
  not-found: "没有找到有关于“{q}”的帖子"
 | 
			
		||||
mobile/views/pages/selectdrive.vue:
 | 
			
		||||
  select-file: "选择文件"
 | 
			
		||||
mobile/views/pages/notifications.vue:
 | 
			
		||||
  notifications: "通知"
 | 
			
		||||
mobile/views/pages/settings.vue:
 | 
			
		||||
  signed-in-as: "以{}登录"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
@@ -1704,6 +1789,7 @@ deck/deck.user-column.vue:
 | 
			
		||||
  activity: "活动"
 | 
			
		||||
  timeline: "时间线"
 | 
			
		||||
  pinned-notes: "置顶帖"
 | 
			
		||||
  pinned-page: "已置顶的页面"
 | 
			
		||||
docs:
 | 
			
		||||
  edit-this-page-on-github: "发现错误或想要为文档做出贡献?"
 | 
			
		||||
  edit-this-page-on-github-link: "在GitHub上编辑这个页面。"
 | 
			
		||||
@@ -1735,6 +1821,7 @@ pages:
 | 
			
		||||
  read-page: "查看源"
 | 
			
		||||
  page-created: "页面已创建"
 | 
			
		||||
  page-updated: "页面已更新"
 | 
			
		||||
  name-already-exists: "该页面URL已存在"
 | 
			
		||||
  are-you-sure-delete: "是否删除此页面?"
 | 
			
		||||
  page-deleted: "该页面已被删除。"
 | 
			
		||||
  edit-this-page: "编辑此页面"
 | 
			
		||||
@@ -1758,6 +1845,7 @@ pages:
 | 
			
		||||
  url: "页面URL"
 | 
			
		||||
  summary: "页面摘要"
 | 
			
		||||
  align-center: "居中"
 | 
			
		||||
  hide-title-when-pinned: "置顶时隐藏标题"
 | 
			
		||||
  font: "字体"
 | 
			
		||||
  fontSerif: "衬线字体"
 | 
			
		||||
  fontSansSerif: "无衬线字体"
 | 
			
		||||
@@ -1811,12 +1899,25 @@ pages:
 | 
			
		||||
      inc: "增加值"
 | 
			
		||||
    _button:
 | 
			
		||||
      text: "标题"
 | 
			
		||||
      colored: "彩色"
 | 
			
		||||
      action: "按下按钮时的行为"
 | 
			
		||||
      _action:
 | 
			
		||||
        dialog: "显示对话框"
 | 
			
		||||
        _dialog:
 | 
			
		||||
          content: "内容"
 | 
			
		||||
        resetRandom: "随机值重置"
 | 
			
		||||
        pushEvent: "发送事件"
 | 
			
		||||
        _pushEvent:
 | 
			
		||||
          event: "事件名称"
 | 
			
		||||
          message: "按下时显示的消息"
 | 
			
		||||
          variable: "发送的变量"
 | 
			
		||||
          no-variable: "空"
 | 
			
		||||
    radioButton: "选择项"
 | 
			
		||||
    _radioButton:
 | 
			
		||||
      name: "变量名"
 | 
			
		||||
      title: "标题"
 | 
			
		||||
      values: "使用换行区分的选择项"
 | 
			
		||||
      default: "默认值"
 | 
			
		||||
  script:
 | 
			
		||||
    categories:
 | 
			
		||||
      flow: "控制"
 | 
			
		||||
@@ -1989,3 +2090,64 @@ pages:
 | 
			
		||||
    enviromentVariables: "环境变量"
 | 
			
		||||
    pageVariables: "页面元素"
 | 
			
		||||
    argVariables: "输入槽函数"
 | 
			
		||||
room:
 | 
			
		||||
  add-furniture: "放置家具"
 | 
			
		||||
  translate: "移动"
 | 
			
		||||
  rotate: "旋转"
 | 
			
		||||
  exit: "返回"
 | 
			
		||||
  remove: "移除"
 | 
			
		||||
  save: "保存"
 | 
			
		||||
  saved: "已保存"
 | 
			
		||||
  clear: "清理"
 | 
			
		||||
  clear-confirm: "是否清除所有家具?"
 | 
			
		||||
  leave-confirm: "有尚未保存的修改。是否离开?"
 | 
			
		||||
  chooseImage: "选择图片"
 | 
			
		||||
  room-type: "房间类型"
 | 
			
		||||
  carpet-color: "地板颜色"
 | 
			
		||||
  rooms:
 | 
			
		||||
    default: "默认"
 | 
			
		||||
    washitsu: "和式房间"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    milk: "牛奶纸箱"
 | 
			
		||||
    bed: "床"
 | 
			
		||||
    low-table: "矮桌"
 | 
			
		||||
    desk: "书桌"
 | 
			
		||||
    chair: "椅子"
 | 
			
		||||
    chair2: "椅子2"
 | 
			
		||||
    fan: "换气扇"
 | 
			
		||||
    pc: "电脑"
 | 
			
		||||
    plant: "观叶植物"
 | 
			
		||||
    plant2: "观叶植物2"
 | 
			
		||||
    eraser: "橡皮擦"
 | 
			
		||||
    pencil: "铅笔"
 | 
			
		||||
    pudding: "布丁"
 | 
			
		||||
    cardboard-box: "纸板箱"
 | 
			
		||||
    cardboard-box2: "纸板箱2"
 | 
			
		||||
    cardboard-box3: "纸板箱3"
 | 
			
		||||
    book: "书"
 | 
			
		||||
    book2: "书2"
 | 
			
		||||
    piano: "钢琴"
 | 
			
		||||
    facial-tissue: "纸巾盒"
 | 
			
		||||
    server: "服务器"
 | 
			
		||||
    moon: "月球"
 | 
			
		||||
    corkboard: "软木板"
 | 
			
		||||
    mousepad: "鼠标垫"
 | 
			
		||||
    monitor: "显示器"
 | 
			
		||||
    keyboard: "键盘"
 | 
			
		||||
    carpet-stripe: "地毯(条纹)"
 | 
			
		||||
    mat: "垫子"
 | 
			
		||||
    color-box: "收纳柜"
 | 
			
		||||
    wall-clock: "挂钟"
 | 
			
		||||
    photoframe: "相框"
 | 
			
		||||
    cube: "立方体"
 | 
			
		||||
    tv: "电视"
 | 
			
		||||
    pinguin: "企鹅君"
 | 
			
		||||
    rubik-cube: "魔方"
 | 
			
		||||
    poster-h: "海报(横向)"
 | 
			
		||||
    poster-v: "海报(纵向)"
 | 
			
		||||
    sofa: "沙发"
 | 
			
		||||
    spiral: "螺旋楼梯"
 | 
			
		||||
    bin: "垃圾箱"
 | 
			
		||||
    cup-noodle: "杯面"
 | 
			
		||||
    holo-display: "全息显示器"
 | 
			
		||||
    energy-drink: "能量饮料"
 | 
			
		||||
 
 | 
			
		||||
@@ -88,3 +88,6 @@ admin/views/charts.vue:
 | 
			
		||||
  drive: "雲端硬碟"
 | 
			
		||||
pages:
 | 
			
		||||
  like: "贊"
 | 
			
		||||
room:
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "月"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								migration/1562869971568-ModerationLog.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								migration/1562869971568-ModerationLog.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
import {MigrationInterface, QueryRunner} from "typeorm";
 | 
			
		||||
 | 
			
		||||
export class ModerationLog1562869971568 implements MigrationInterface {
 | 
			
		||||
 | 
			
		||||
    public async up(queryRunner: QueryRunner): Promise<any> {
 | 
			
		||||
        await queryRunner.query(`CREATE TABLE "moderation_log" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "type" character varying(128) NOT NULL, "info" jsonb NOT NULL, CONSTRAINT "PK_d0adca6ecfd068db83e4526cc26" PRIMARY KEY ("id"))`);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_a08ad074601d204e0f69da9a95" ON "moderation_log" ("userId") `);
 | 
			
		||||
        await queryRunner.query(`ALTER TABLE "moderation_log" ADD CONSTRAINT "FK_a08ad074601d204e0f69da9a954" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async down(queryRunner: QueryRunner): Promise<any> {
 | 
			
		||||
        await queryRunner.query(`ALTER TABLE "moderation_log" DROP CONSTRAINT "FK_a08ad074601d204e0f69da9a954"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_a08ad074601d204e0f69da9a95"`);
 | 
			
		||||
        await queryRunner.query(`DROP TABLE "moderation_log"`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								migration/1563757595828-UsedUsername.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								migration/1563757595828-UsedUsername.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import {MigrationInterface, QueryRunner} from "typeorm";
 | 
			
		||||
 | 
			
		||||
export class UsedUsername1563757595828 implements MigrationInterface {
 | 
			
		||||
 | 
			
		||||
    public async up(queryRunner: QueryRunner): Promise<any> {
 | 
			
		||||
        await queryRunner.query(`CREATE TABLE "used_username" ("username" character varying(128) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, CONSTRAINT "PK_78fd79d2d24c6ac2f4cc9a31a5d" PRIMARY KEY ("username"))`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async down(queryRunner: QueryRunner): Promise<any> {
 | 
			
		||||
        await queryRunner.query(`DROP TABLE "used_username"`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								migration/1565634203341-room.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								migration/1565634203341-room.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import {MigrationInterface, QueryRunner} from "typeorm";
 | 
			
		||||
 | 
			
		||||
export class room1565634203341 implements MigrationInterface {
 | 
			
		||||
 | 
			
		||||
    public async up(queryRunner: QueryRunner): Promise<any> {
 | 
			
		||||
        await queryRunner.query(`ALTER TABLE "user_profile" ADD "room" jsonb NOT NULL DEFAULT '{}'`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async down(queryRunner: QueryRunner): Promise<any> {
 | 
			
		||||
        await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "room"`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										103
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										103
									
								
								package.json
									
									
									
									
									
								
							@@ -1,7 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "misskey",
 | 
			
		||||
	"author": "syuilo <i@syuilo.com>",
 | 
			
		||||
	"version": "11.25.1",
 | 
			
		||||
	"version": "11.31.4",
 | 
			
		||||
	"codename": "daybreak",
 | 
			
		||||
	"repository": {
 | 
			
		||||
		"type": "git",
 | 
			
		||||
@@ -27,17 +27,16 @@
 | 
			
		||||
	},
 | 
			
		||||
	"resolutions": {
 | 
			
		||||
		"gulp-cssnano/cssnano/postcss-svgo/svgo/js-yaml": "^3.13.1",
 | 
			
		||||
		"video-thumbnail-generator/lodash": "^4.17.11"
 | 
			
		||||
		"lodash": "^4.17.13"
 | 
			
		||||
	},
 | 
			
		||||
	"dependencies": {
 | 
			
		||||
		"@elastic/elasticsearch": "7.1.0",
 | 
			
		||||
		"@elastic/elasticsearch": "7.3.0",
 | 
			
		||||
		"@fortawesome/fontawesome-svg-core": "1.2.19",
 | 
			
		||||
		"@fortawesome/free-brands-svg-icons": "5.9.0",
 | 
			
		||||
		"@fortawesome/free-regular-svg-icons": "5.9.0",
 | 
			
		||||
		"@fortawesome/free-solid-svg-icons": "5.9.0",
 | 
			
		||||
		"@fortawesome/vue-fontawesome": "0.1.6",
 | 
			
		||||
		"@koa/cors": "3.0.0",
 | 
			
		||||
		"@typescript-eslint/parser": "1.11.0",
 | 
			
		||||
		"@types/bcryptjs": "2.4.2",
 | 
			
		||||
		"@types/bull": "3.5.15",
 | 
			
		||||
		"@types/cbor": "2.0.0",
 | 
			
		||||
@@ -50,7 +49,6 @@
 | 
			
		||||
		"@types/gulp-replace": "0.0.31",
 | 
			
		||||
		"@types/gulp-uglify": "3.0.6",
 | 
			
		||||
		"@types/gulp-util": "3.0.34",
 | 
			
		||||
		"@types/is-root": "2.1.2",
 | 
			
		||||
		"@types/is-url": "1.2.28",
 | 
			
		||||
		"@types/js-yaml": "3.12.1",
 | 
			
		||||
		"@types/jsdom": "12.2.4",
 | 
			
		||||
@@ -68,7 +66,6 @@
 | 
			
		||||
		"@types/koa-views": "2.0.3",
 | 
			
		||||
		"@types/koa__cors": "2.2.3",
 | 
			
		||||
		"@types/lolex": "3.1.1",
 | 
			
		||||
		"@types/minio": "7.0.2",
 | 
			
		||||
		"@types/mocha": "5.2.7",
 | 
			
		||||
		"@types/node": "12.0.10",
 | 
			
		||||
		"@types/nodemailer": "6.2.0",
 | 
			
		||||
@@ -83,7 +80,7 @@
 | 
			
		||||
		"@types/ratelimiter": "2.1.28",
 | 
			
		||||
		"@types/redis": "2.8.13",
 | 
			
		||||
		"@types/rename": "1.0.1",
 | 
			
		||||
		"@types/request": "2.48.1",
 | 
			
		||||
		"@types/request": "2.48.2",
 | 
			
		||||
		"@types/request-promise-native": "1.0.16",
 | 
			
		||||
		"@types/request-stats": "3.0.0",
 | 
			
		||||
		"@types/rimraf": "2.0.2",
 | 
			
		||||
@@ -100,39 +97,43 @@
 | 
			
		||||
		"@types/webpack-stream": "3.2.10",
 | 
			
		||||
		"@types/websocket": "0.0.40",
 | 
			
		||||
		"@types/ws": "6.0.1",
 | 
			
		||||
		"animejs": "3.0.1",
 | 
			
		||||
		"apexcharts": "3.8.1",
 | 
			
		||||
		"@typescript-eslint/parser": "1.11.0",
 | 
			
		||||
		"agentkeepalive": "4.0.2",
 | 
			
		||||
		"animejs": "3.1.0",
 | 
			
		||||
		"apexcharts": "3.8.5",
 | 
			
		||||
		"autobind-decorator": "2.4.0",
 | 
			
		||||
		"autosize": "4.0.2",
 | 
			
		||||
		"autwh": "0.1.0",
 | 
			
		||||
		"aws-sdk": "2.520.0",
 | 
			
		||||
		"bcryptjs": "2.4.3",
 | 
			
		||||
		"bootstrap": "4.3.1",
 | 
			
		||||
		"bootstrap-vue": "2.0.0-rc.13",
 | 
			
		||||
		"bull": "3.10.0",
 | 
			
		||||
		"cafy": "15.1.1",
 | 
			
		||||
		"cbor": "4.1.5",
 | 
			
		||||
		"cbor": "4.3.0",
 | 
			
		||||
		"chai": "4.2.0",
 | 
			
		||||
		"chalk": "2.4.2",
 | 
			
		||||
		"cli-highlight": "2.1.1",
 | 
			
		||||
		"commander": "2.20.0",
 | 
			
		||||
		"content-disposition": "0.5.3",
 | 
			
		||||
		"crc-32": "1.2.0",
 | 
			
		||||
		"css-loader": "3.0.0",
 | 
			
		||||
		"css-loader": "3.2.0",
 | 
			
		||||
		"cssnano": "4.1.10",
 | 
			
		||||
		"dateformat": "3.0.3",
 | 
			
		||||
		"deep-equal": "1.0.1",
 | 
			
		||||
		"diskusage": "1.1.2",
 | 
			
		||||
		"diskusage": "1.1.3",
 | 
			
		||||
		"double-ended-queue": "2.1.0-0",
 | 
			
		||||
		"emojilib": "2.4.0",
 | 
			
		||||
		"eslint": "6.0.1",
 | 
			
		||||
		"eslint": "6.1.0",
 | 
			
		||||
		"eslint-plugin-vue": "5.2.3",
 | 
			
		||||
		"eventemitter3": "4.0.0",
 | 
			
		||||
		"feed": "3.0.0",
 | 
			
		||||
		"file-type": "12.0.0",
 | 
			
		||||
		"file-type": "12.2.0",
 | 
			
		||||
		"fluent-ffmpeg": "2.1.2",
 | 
			
		||||
		"fuckadblock": "3.2.1",
 | 
			
		||||
		"gulp": "4.0.2",
 | 
			
		||||
		"gulp-cssnano": "2.1.3",
 | 
			
		||||
		"gulp-imagemin": "6.0.0",
 | 
			
		||||
		"gulp-imagemin": "6.1.0",
 | 
			
		||||
		"gulp-mocha": "6.0.0",
 | 
			
		||||
		"gulp-rename": "1.4.0",
 | 
			
		||||
		"gulp-replace": "1.0.0",
 | 
			
		||||
@@ -145,7 +146,8 @@
 | 
			
		||||
		"hard-source-webpack-plugin": "0.13.1",
 | 
			
		||||
		"html-minifier": "4.0.0",
 | 
			
		||||
		"http-signature": "1.2.0",
 | 
			
		||||
		"insert-text-at-cursor": "0.2.0",
 | 
			
		||||
		"https-proxy-agent": "2.2.2",
 | 
			
		||||
		"insert-text-at-cursor": "0.3.0",
 | 
			
		||||
		"is-root": "2.1.0",
 | 
			
		||||
		"is-svg": "4.2.0",
 | 
			
		||||
		"js-yaml": "3.13.1",
 | 
			
		||||
@@ -153,13 +155,13 @@
 | 
			
		||||
		"json5": "2.1.0",
 | 
			
		||||
		"json5-loader": "3.0.0",
 | 
			
		||||
		"jsrsasign": "8.0.12",
 | 
			
		||||
		"katex": "0.10.2",
 | 
			
		||||
		"koa": "2.7.0",
 | 
			
		||||
		"katex": "0.11.0",
 | 
			
		||||
		"koa": "2.8.1",
 | 
			
		||||
		"koa-bodyparser": "4.2.1",
 | 
			
		||||
		"koa-compress": "3.0.0",
 | 
			
		||||
		"koa-favicon": "2.0.1",
 | 
			
		||||
		"koa-json-body": "5.3.0",
 | 
			
		||||
		"koa-logger": "3.2.0",
 | 
			
		||||
		"koa-logger": "3.2.1",
 | 
			
		||||
		"koa-mount": "4.0.0",
 | 
			
		||||
		"koa-multer": "1.0.2",
 | 
			
		||||
		"koa-router": "7.4.0",
 | 
			
		||||
@@ -168,38 +170,35 @@
 | 
			
		||||
		"koa-views": "6.2.0",
 | 
			
		||||
		"langmap": "0.0.16",
 | 
			
		||||
		"loader-utils": "1.2.3",
 | 
			
		||||
		"lolex": "4.1.0",
 | 
			
		||||
		"lolex": "4.2.0",
 | 
			
		||||
		"lookup-dns-cache": "2.1.0",
 | 
			
		||||
		"minio": "7.0.10",
 | 
			
		||||
		"mocha": "6.1.4",
 | 
			
		||||
		"mocha": "6.2.0",
 | 
			
		||||
		"moji": "0.5.1",
 | 
			
		||||
		"moment": "2.24.0",
 | 
			
		||||
		"ms": "2.1.2",
 | 
			
		||||
		"nested-property": "1.0.1",
 | 
			
		||||
		"node-fetch": "2.6.0",
 | 
			
		||||
		"nodemailer": "6.2.1",
 | 
			
		||||
		"nodemailer": "6.3.0",
 | 
			
		||||
		"nprogress": "0.2.0",
 | 
			
		||||
		"object-assign-deep": "0.4.0",
 | 
			
		||||
		"os-utils": "0.0.14",
 | 
			
		||||
		"parse5": "5.1.0",
 | 
			
		||||
		"parsimmon": "1.12.0",
 | 
			
		||||
		"pg": "7.11.0",
 | 
			
		||||
		"parsimmon": "1.13.0",
 | 
			
		||||
		"pg": "7.12.1",
 | 
			
		||||
		"portscanner": "2.2.0",
 | 
			
		||||
		"postcss-loader": "3.0.0",
 | 
			
		||||
		"prismjs": "1.16.0",
 | 
			
		||||
		"prismjs": "1.17.1",
 | 
			
		||||
		"progress-bar-webpack-plugin": "1.12.1",
 | 
			
		||||
		"promise-any": "0.2.0",
 | 
			
		||||
		"promise-limit": "2.7.0",
 | 
			
		||||
		"promise-sequential": "1.1.1",
 | 
			
		||||
		"pug": "2.0.4",
 | 
			
		||||
		"punycode": "2.1.1",
 | 
			
		||||
		"pureimage": "0.1.6",
 | 
			
		||||
		"qrcode": "1.3.3",
 | 
			
		||||
		"qrcode": "1.4.1",
 | 
			
		||||
		"random-seed": "0.3.0",
 | 
			
		||||
		"randomcolor": "0.5.4",
 | 
			
		||||
		"ratelimiter": "3.3.0",
 | 
			
		||||
		"ratelimiter": "3.3.1",
 | 
			
		||||
		"recaptcha-promise": "0.1.3",
 | 
			
		||||
		"reconnecting-websocket": "4.1.10",
 | 
			
		||||
		"reconnecting-websocket": "4.2.0",
 | 
			
		||||
		"redis": "2.8.0",
 | 
			
		||||
		"reflect-metadata": "0.1.13",
 | 
			
		||||
		"rename": "1.0.4",
 | 
			
		||||
@@ -210,20 +209,22 @@
 | 
			
		||||
		"rimraf": "2.6.3",
 | 
			
		||||
		"rndstr": "1.0.0",
 | 
			
		||||
		"s-age": "1.1.2",
 | 
			
		||||
		"seedrandom": "3.0.1",
 | 
			
		||||
		"sharp": "0.22.1",
 | 
			
		||||
		"seedrandom": "3.0.3",
 | 
			
		||||
		"sharp": "0.23.0",
 | 
			
		||||
		"showdown": "1.9.0",
 | 
			
		||||
		"showdown-highlightjs-extension": "0.1.2",
 | 
			
		||||
		"speakeasy": "2.0.0",
 | 
			
		||||
		"stringz": "2.0.0",
 | 
			
		||||
		"style-loader": "0.23.1",
 | 
			
		||||
		"stylus": "0.54.5",
 | 
			
		||||
		"stylus": "0.54.7",
 | 
			
		||||
		"stylus-loader": "3.0.2",
 | 
			
		||||
		"summaly": "2.3.0",
 | 
			
		||||
		"systeminformation": "4.13.1",
 | 
			
		||||
		"summaly": "2.3.1",
 | 
			
		||||
		"syslog-pro": "1.0.0",
 | 
			
		||||
		"systeminformation": "4.14.8",
 | 
			
		||||
		"syuilo-password-strength": "0.0.1",
 | 
			
		||||
		"terser-webpack-plugin": "1.3.0",
 | 
			
		||||
		"terser-webpack-plugin": "1.4.1",
 | 
			
		||||
		"textarea-caret": "3.1.0",
 | 
			
		||||
		"three": "0.108.0",
 | 
			
		||||
		"tinycolor2": "1.4.1",
 | 
			
		||||
		"tmp": "0.1.0",
 | 
			
		||||
		"ts-loader": "5.3.3",
 | 
			
		||||
@@ -231,38 +232,40 @@
 | 
			
		||||
		"tslint": "5.18.0",
 | 
			
		||||
		"tslint-sonarts": "1.9.0",
 | 
			
		||||
		"typeorm": "0.2.18",
 | 
			
		||||
		"typescript": "3.5.2",
 | 
			
		||||
		"typescript": "3.5.3",
 | 
			
		||||
		"uglify-es": "3.3.9",
 | 
			
		||||
		"ulid": "2.3.0",
 | 
			
		||||
		"url-loader": "2.0.1",
 | 
			
		||||
		"uuid": "3.3.2",
 | 
			
		||||
		"url-loader": "2.1.0",
 | 
			
		||||
		"uuid": "3.3.3",
 | 
			
		||||
		"v-animate-css": "0.0.3",
 | 
			
		||||
		"v-debounce": "0.1.2",
 | 
			
		||||
		"video-thumbnail-generator": "1.1.3",
 | 
			
		||||
		"vue": "2.6.10",
 | 
			
		||||
		"vue-color": "2.7.0",
 | 
			
		||||
		"vue-content-loading": "1.6.0",
 | 
			
		||||
		"vue-cropperjs": "4.0.0",
 | 
			
		||||
		"vue-i18n": "8.11.2",
 | 
			
		||||
		"vue-i18n": "8.14.0",
 | 
			
		||||
		"vue-js-modal": "1.3.31",
 | 
			
		||||
		"vue-json-pretty": "1.6.0",
 | 
			
		||||
		"vue-loader": "15.7.0",
 | 
			
		||||
		"vue-json-pretty": "1.6.1",
 | 
			
		||||
		"vue-loader": "15.7.1",
 | 
			
		||||
		"vue-marquee-text-component": "1.1.1",
 | 
			
		||||
		"vue-prism-component": "1.1.1",
 | 
			
		||||
		"vue-router": "3.0.6",
 | 
			
		||||
		"vue-router": "3.1.2",
 | 
			
		||||
		"vue-sequential-entrance": "1.1.3",
 | 
			
		||||
		"vue-style-loader": "4.1.2",
 | 
			
		||||
		"vue-svg-inline-loader": "1.2.16",
 | 
			
		||||
		"vue-svg-inline-loader": "1.2.17",
 | 
			
		||||
		"vue-template-compiler": "2.6.10",
 | 
			
		||||
		"vuedraggable": "2.23.0",
 | 
			
		||||
		"vuewordcloud": "18.7.11",
 | 
			
		||||
		"vuex": "3.1.1",
 | 
			
		||||
		"vuex-persistedstate": "2.5.4",
 | 
			
		||||
		"web-push": "3.3.5",
 | 
			
		||||
		"webpack": "4.35.2",
 | 
			
		||||
		"webpack-cli": "3.3.5",
 | 
			
		||||
		"websocket": "1.0.28",
 | 
			
		||||
		"ws": "7.0.1",
 | 
			
		||||
		"webpack": "4.39.3",
 | 
			
		||||
		"webpack-cli": "3.3.7",
 | 
			
		||||
		"websocket": "1.0.29",
 | 
			
		||||
		"ws": "7.1.2",
 | 
			
		||||
		"xev": "2.0.1"
 | 
			
		||||
	},
 | 
			
		||||
	"devDependencies": {
 | 
			
		||||
		"@types/fluent-ffmpeg": "2.1.10"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								src/@types/lookup-dns-cache.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								src/@types/lookup-dns-cache.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -1,17 +1,9 @@
 | 
			
		||||
declare module 'lookup-dns-cache' {
 | 
			
		||||
	type IPv4 = 4;
 | 
			
		||||
	import { LookupOneOptions, LookupAllOptions, LookupOptions, LookupAddress } from 'dns';
 | 
			
		||||
 | 
			
		||||
	type IPv6 = 6;
 | 
			
		||||
 | 
			
		||||
	type Family = IPv4 | IPv6 | undefined;
 | 
			
		||||
 | 
			
		||||
	interface IRunOptions {
 | 
			
		||||
		family?: Family;
 | 
			
		||||
		all?: boolean;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type RunCallback = (error: Error | null, address?: string | string[], family?: Family) => void;
 | 
			
		||||
 | 
			
		||||
	export function lookup(hostname: string, options: IRunOptions | Family, callback: RunCallback): {} | undefined;
 | 
			
		||||
	export function lookup(hostname: string, callback: RunCallback): {} | undefined;
 | 
			
		||||
	function lookup(hostname: string, family: number, callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void): void;
 | 
			
		||||
	function lookup(hostname: string, options: LookupOneOptions, callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void): void;
 | 
			
		||||
	function lookup(hostname: string, options: LookupAllOptions, callback: (err: NodeJS.ErrnoException | null, addresses: LookupAddress[]) => void): void;
 | 
			
		||||
	function lookup(hostname: string, options: LookupOptions, callback: (err: NodeJS.ErrnoException | null, address: string | LookupAddress[], family: number) => void): void;
 | 
			
		||||
	function lookup(hostname: string, callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void): void;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								src/@types/promise-any.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								src/@types/promise-any.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -1,7 +0,0 @@
 | 
			
		||||
declare module 'promise-any' {
 | 
			
		||||
	function promiseAny<T>(iterable: Iterable<T | PromiseLike<T>>): Promise<T>;
 | 
			
		||||
 | 
			
		||||
	namespace promiseAny {} // Hack
 | 
			
		||||
 | 
			
		||||
	export = promiseAny;
 | 
			
		||||
}
 | 
			
		||||
@@ -159,7 +159,7 @@ async function init(): Promise<Config> {
 | 
			
		||||
	return config;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function spawnWorkers(limit: number = Infinity) {
 | 
			
		||||
async function spawnWorkers(limit: number = 1) {
 | 
			
		||||
	const workers = Math.min(limit, os.cpus().length);
 | 
			
		||||
	bootLogger.info(`Starting ${workers} worker${workers === 1 ? '' : 's'}...`);
 | 
			
		||||
	await Promise.all([...Array(workers)].map(spawnWorker));
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,8 @@ init(launch => {
 | 
			
		||||
		mode: 'history',
 | 
			
		||||
		base: '/admin/',
 | 
			
		||||
		routes: [
 | 
			
		||||
			{ path: '/', component: Index },
 | 
			
		||||
			{ path: '/:page', component: Index },
 | 
			
		||||
			{ path: '/', redirect: '/dashboard' },
 | 
			
		||||
			{ path: '*', component: NotFound }
 | 
			
		||||
		]
 | 
			
		||||
	});
 | 
			
		||||
 
 | 
			
		||||
@@ -18,18 +18,18 @@
 | 
			
		||||
			<p class="name"><mk-user-name :user="$store.state.i"/></p>
 | 
			
		||||
		</div>
 | 
			
		||||
		<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('logs')" :class="{ active: page == 'logs' }"><fa :icon="faStream" fixed-width/>{{ $t('logs') }}</li>
 | 
			
		||||
			<li @click="nav('db')" :class="{ active: page == 'db' }"><fa :icon="faDatabase" fixed-width/>{{ $t('db') }}</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="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('abuse')" :class="{ active: page == 'abuse' }"><fa :icon="faExclamationCircle" fixed-width/>{{ $t('abuse') }}</li>
 | 
			
		||||
			<li><router-link to="/dashboard" active-class="active"><fa icon="home" fixed-width/>{{ $t('dashboard') }}</router-link></li>
 | 
			
		||||
			<li><router-link to="/instance" active-class="active"><fa icon="cog" fixed-width/>{{ $t('instance') }}</router-link></li>
 | 
			
		||||
			<li><router-link to="/queue" active-class="active"><fa :icon="faTasks" fixed-width/>{{ $t('queue') }}</router-link></li>
 | 
			
		||||
			<li><router-link to="/logs" active-class="active"><fa :icon="faStream" fixed-width/>{{ $t('logs') }}</router-link></li>
 | 
			
		||||
			<li><router-link to="/db" active-class="active"><fa :icon="faDatabase" fixed-width/>{{ $t('db') }}</router-link></li>
 | 
			
		||||
			<li><router-link to="/moderators" active-class="active"><fa :icon="faHeadset" fixed-width/>{{ $t('moderators') }}</router-link></li>
 | 
			
		||||
			<li><router-link to="/users" active-class="active"><fa icon="users" fixed-width/>{{ $t('users') }}</router-link></li>
 | 
			
		||||
			<li><router-link to="/drive" active-class="active"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</router-link></li>
 | 
			
		||||
			<li><router-link to="/federation" active-class="active"><fa :icon="faGlobe" fixed-width/>{{ $t('federation') }}</router-link></li>
 | 
			
		||||
			<li><router-link to="/emoji" active-class="active"><fa :icon="faGrin" fixed-width/>{{ $t('emoji') }}</router-link></li>
 | 
			
		||||
			<li><router-link to="/announcements" active-class="active"><fa icon="broadcast-tower" fixed-width/>{{ $t('announcements') }}</router-link></li>
 | 
			
		||||
			<li><router-link to="/abuse" active-class="active"><fa :icon="faExclamationCircle" fixed-width/>{{ $t('abuse') }}</router-link></li>
 | 
			
		||||
		</ul>
 | 
			
		||||
		<div class="back-to-misskey">
 | 
			
		||||
			<a href="/"><fa :icon="faArrowLeft"/> {{ $t('back-to-misskey') }}</a>
 | 
			
		||||
@@ -102,7 +102,6 @@ export default Vue.extend({
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			page: 'dashboard',
 | 
			
		||||
			version,
 | 
			
		||||
			isMobile,
 | 
			
		||||
			navOpend: !isMobile,
 | 
			
		||||
@@ -116,9 +115,9 @@ export default Vue.extend({
 | 
			
		||||
			faDatabase,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		nav(page: string) {
 | 
			
		||||
			this.page = page;
 | 
			
		||||
	computed: {
 | 
			
		||||
		page() {
 | 
			
		||||
			return this.$route.params.page;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
@@ -240,11 +239,10 @@ export default Vue.extend({
 | 
			
		||||
			list-style none
 | 
			
		||||
			font-size 15px
 | 
			
		||||
 | 
			
		||||
			> li
 | 
			
		||||
			> li > a
 | 
			
		||||
				display block
 | 
			
		||||
				padding 10px 16px
 | 
			
		||||
				margin 0
 | 
			
		||||
				cursor pointer
 | 
			
		||||
				user-select none
 | 
			
		||||
				color #eee
 | 
			
		||||
				transition margin-left 0.2s ease
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,31 @@
 | 
			
		||||
			</ui-horizon-group>
 | 
			
		||||
		</section>
 | 
			
		||||
	</ui-card>
 | 
			
		||||
 | 
			
		||||
	<ui-card>
 | 
			
		||||
		<template #title>{{ $t('logs.title') }}</template>
 | 
			
		||||
		<section class="fit-top">
 | 
			
		||||
			<sequential-entrance animation="entranceFromTop" delay="25">
 | 
			
		||||
				<div v-for="log in logs" :key="log.id" class="">
 | 
			
		||||
					<ui-horizon-group inputs>
 | 
			
		||||
						<ui-input :value="log.user | acct" type="text" readonly>
 | 
			
		||||
							<span>{{ $t('logs.moderator') }}</span>
 | 
			
		||||
						</ui-input>
 | 
			
		||||
						<ui-input :value="log.type" type="text" readonly>
 | 
			
		||||
							<span>{{ $t('logs.type') }}</span>
 | 
			
		||||
						</ui-input>
 | 
			
		||||
						<ui-input :value="log.createdAt | date" type="text" readonly>
 | 
			
		||||
							<span>{{ $t('logs.at') }}</span>
 | 
			
		||||
						</ui-input>
 | 
			
		||||
					</ui-horizon-group>
 | 
			
		||||
					<ui-textarea :value="JSON.stringify(log.info, null, 4)" readonly>
 | 
			
		||||
						<span>{{ $t('logs.info') }}</span>
 | 
			
		||||
					</ui-textarea>
 | 
			
		||||
				</div>
 | 
			
		||||
			</sequential-entrance>
 | 
			
		||||
			<ui-button v-if="existMoreLogs" @click="fetchLogs">{{ $t('@.load-more') }}</ui-button>
 | 
			
		||||
		</section>
 | 
			
		||||
	</ui-card>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -26,10 +51,17 @@ export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			username: '',
 | 
			
		||||
			changing: false
 | 
			
		||||
			changing: false,
 | 
			
		||||
			logs: [],
 | 
			
		||||
			untilLogId: null,
 | 
			
		||||
			existMoreLogs: false
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	created() {
 | 
			
		||||
		this.fetchLogs();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		async add() {
 | 
			
		||||
			this.changing = true;
 | 
			
		||||
@@ -74,6 +106,22 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
			this.changing = false;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		fetchLogs() {
 | 
			
		||||
			this.$root.api('admin/show-moderation-logs', {
 | 
			
		||||
				untilId: this.untilId,
 | 
			
		||||
				limit: 10 + 1
 | 
			
		||||
			}).then(logs => {
 | 
			
		||||
				if (logs.length == 10 + 1) {
 | 
			
		||||
					logs.pop();
 | 
			
		||||
					this.existMoreLogs = true;
 | 
			
		||||
				} else {
 | 
			
		||||
					this.existMoreLogs = false;
 | 
			
		||||
				}
 | 
			
		||||
				this.logs = this.logs.concat(logs);
 | 
			
		||||
				this.untilLogId = this.logs[this.logs.length - 1].id;
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
			<mk-avatar class="avatar" :user="user" :disable-link="true"/>
 | 
			
		||||
		</a>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div>
 | 
			
		||||
	<div @click="click(user.id)">
 | 
			
		||||
		<header>
 | 
			
		||||
			<b><mk-user-name :user="user"/></b>
 | 
			
		||||
			<span class="username">@{{ user | acct }}</span>
 | 
			
		||||
@@ -32,7 +32,7 @@ import { faSnowflake } from '@fortawesome/free-regular-svg-icons';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n('admin/views/users.vue'),
 | 
			
		||||
	props: ['user'],
 | 
			
		||||
	props: ['user', 'click'],
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			faSnowflake, faMicrophoneSlash
 | 
			
		||||
@@ -44,7 +44,7 @@ export default Vue.extend({
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.kofvwchc
 | 
			
		||||
	display flex
 | 
			
		||||
	padding 16px 0
 | 
			
		||||
	padding 16px
 | 
			
		||||
	border-top solid 1px var(--faceDivider)
 | 
			
		||||
 | 
			
		||||
	> div:first-child
 | 
			
		||||
@@ -55,6 +55,7 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
	> div:last-child
 | 
			
		||||
		flex 1
 | 
			
		||||
		cursor pointer
 | 
			
		||||
		padding-left 16px
 | 
			
		||||
 | 
			
		||||
		@media (max-width 500px)
 | 
			
		||||
@@ -80,4 +81,15 @@ export default Vue.extend({
 | 
			
		||||
			> .is-suspended
 | 
			
		||||
				margin 0 0 0 .5em
 | 
			
		||||
				color #4dabf7
 | 
			
		||||
 | 
			
		||||
	&:hover
 | 
			
		||||
		color var(--primaryForeground)
 | 
			
		||||
		background var(--primary)
 | 
			
		||||
		text-decoration none
 | 
			
		||||
		border-radius 3px
 | 
			
		||||
 | 
			
		||||
	&:active
 | 
			
		||||
		color var(--primaryForeground)
 | 
			
		||||
		background var(--primaryDarken10)
 | 
			
		||||
		border-radius 3px
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
			</ui-input>
 | 
			
		||||
			<ui-button @click="showUser"><fa :icon="faSearch"/> {{ $t('lookup') }}</ui-button>
 | 
			
		||||
 | 
			
		||||
			<div class="user" v-if="user">
 | 
			
		||||
			<div ref="user" class="user" v-if="user" :key="user.id">
 | 
			
		||||
				<x-user :user="user"/>
 | 
			
		||||
				<div class="actions">
 | 
			
		||||
					<ui-button v-if="user.host != null" @click="updateRemoteUser"><fa :icon="faSync"/> {{ $t('update-remote-user') }}</ui-button>
 | 
			
		||||
@@ -54,8 +54,16 @@
 | 
			
		||||
					<option value="remote">{{ $t('users.origin.remote') }}</option>
 | 
			
		||||
				</ui-select>
 | 
			
		||||
			</ui-horizon-group>
 | 
			
		||||
			<ui-horizon-group searchboxes>
 | 
			
		||||
				<ui-input v-model="searchUsername" type="text" spellcheck="false" @input="fetchUsers(true)">
 | 
			
		||||
					<span>{{ $t('username') }}</span>
 | 
			
		||||
				</ui-input>
 | 
			
		||||
				<ui-input v-model="searchHost" type="text" spellcheck="false" @input="fetchUsers(true)" :disabled="origin === 'local'">
 | 
			
		||||
					<span>{{ $t('host') }}</span>
 | 
			
		||||
				</ui-input>
 | 
			
		||||
			</ui-horizon-group>
 | 
			
		||||
			<sequential-entrance animation="entranceFromTop" delay="25">
 | 
			
		||||
				<x-user v-for="user in users" :user='user' :key="user.id"/>
 | 
			
		||||
				<x-user v-for="user in users" :key="user.id" :user='user' :click="showUserOnClick"/>
 | 
			
		||||
			</sequential-entrance>
 | 
			
		||||
			<ui-button v-if="existMore" @click="fetchUsers">{{ $t('@.load-more') }}</ui-button>
 | 
			
		||||
		</section>
 | 
			
		||||
@@ -85,6 +93,8 @@ export default Vue.extend({
 | 
			
		||||
			sort: '+createdAt',
 | 
			
		||||
			state: 'all',
 | 
			
		||||
			origin: 'local',
 | 
			
		||||
			searchUsername: '',
 | 
			
		||||
			searchHost: '',
 | 
			
		||||
			limit: 10,
 | 
			
		||||
			offset: 0,
 | 
			
		||||
			users: [],
 | 
			
		||||
@@ -107,6 +117,7 @@ export default Vue.extend({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		origin() {
 | 
			
		||||
			if (this.origin === 'local') this.searchHost = '';
 | 
			
		||||
			this.users = [];
 | 
			
		||||
			this.offset = 0;
 | 
			
		||||
			this.fetchUsers();
 | 
			
		||||
@@ -157,6 +168,15 @@ export default Vue.extend({
 | 
			
		||||
			this.target = '';
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		async showUserOnClick(userId: string) {
 | 
			
		||||
			this.$root.api('admin/show-user', { userId: userId }).then(info => {
 | 
			
		||||
				this.user = info;
 | 
			
		||||
				this.$nextTick(() => {
 | 
			
		||||
					this.$refs.user.scrollIntoView();
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		/** 処理対象ユーザーの情報を更新する */
 | 
			
		||||
		async refreshUser() {
 | 
			
		||||
			this.$root.api('admin/show-user', { userId: this.user.id }).then(info => {
 | 
			
		||||
@@ -308,13 +328,16 @@ export default Vue.extend({
 | 
			
		||||
			return !confirm.canceled;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		fetchUsers() {
 | 
			
		||||
		fetchUsers(truncate?: boolean) {
 | 
			
		||||
			if (truncate) this.offset = 0;
 | 
			
		||||
			this.$root.api('admin/show-users', {
 | 
			
		||||
				state: this.state,
 | 
			
		||||
				origin: this.origin,
 | 
			
		||||
				sort: this.sort,
 | 
			
		||||
				offset: this.offset,
 | 
			
		||||
				limit: this.limit + 1
 | 
			
		||||
				limit: this.limit + 1,
 | 
			
		||||
				username: this.searchUsername,
 | 
			
		||||
				hostname: this.searchHost
 | 
			
		||||
			}).then(users => {
 | 
			
		||||
				if (users.length == this.limit + 1) {
 | 
			
		||||
					users.pop();
 | 
			
		||||
@@ -322,7 +345,7 @@ export default Vue.extend({
 | 
			
		||||
				} else {
 | 
			
		||||
					this.existMore = false;
 | 
			
		||||
				}
 | 
			
		||||
				this.users = this.users.concat(users);
 | 
			
		||||
				this.users = truncate ? users : this.users.concat(users);
 | 
			
		||||
				this.offset += this.limit;
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,12 @@ export function collectPageVars(content) {
 | 
			
		||||
					type: 'number',
 | 
			
		||||
					value: 0
 | 
			
		||||
				});
 | 
			
		||||
			} else if (x.type === 'radioButton') {
 | 
			
		||||
				pageVars.push({
 | 
			
		||||
					name: x.name,
 | 
			
		||||
					type: 'string',
 | 
			
		||||
					value: x.default || ''
 | 
			
		||||
				});
 | 
			
		||||
			} else if (x.children) {
 | 
			
		||||
				collect(x.children);
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,8 @@ const faces = [
 | 
			
		||||
	'🐡( \'-\' 🐡 )フグパンチ!!!!',
 | 
			
		||||
	'✌️(´・_・`)✌️',
 | 
			
		||||
	'(。>﹏<。)',
 | 
			
		||||
	'(Δ・x・Δ)'
 | 
			
		||||
	'(Δ・x・Δ)',
 | 
			
		||||
	'(コ`・ヘ・´ケ)'
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export default () => faces[Math.floor(Math.random() * faces.length)];
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@ import { sum, unique } from '../../../../prelude/array';
 | 
			
		||||
import shouldMuteNote from './should-mute-note';
 | 
			
		||||
import MkNoteMenu from '../views/components/note-menu.vue';
 | 
			
		||||
import MkReactionPicker from '../views/components/reaction-picker.vue';
 | 
			
		||||
import pleaseLogin from './please-login';
 | 
			
		||||
import i18n from '../../i18n';
 | 
			
		||||
 | 
			
		||||
function focus(el, fn) {
 | 
			
		||||
	const target = fn(el);
 | 
			
		||||
@@ -20,10 +22,13 @@ type Opts = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default (opts: Opts = {}) => ({
 | 
			
		||||
	i18n: i18n(),
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			showContent: false,
 | 
			
		||||
			hideThisNote: false
 | 
			
		||||
			hideThisNote: false,
 | 
			
		||||
			openingMenu: false
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -108,6 +113,7 @@ export default (opts: Opts = {}) => ({
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		reply(viaKeyboard = false) {
 | 
			
		||||
			pleaseLogin(this.$root);
 | 
			
		||||
			this.$root.$post({
 | 
			
		||||
				reply: this.appearNote,
 | 
			
		||||
				animation: !viaKeyboard,
 | 
			
		||||
@@ -118,6 +124,7 @@ export default (opts: Opts = {}) => ({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		renote(viaKeyboard = false) {
 | 
			
		||||
			pleaseLogin(this.$root);
 | 
			
		||||
			this.$root.$post({
 | 
			
		||||
				renote: this.appearNote,
 | 
			
		||||
				animation: !viaKeyboard,
 | 
			
		||||
@@ -134,6 +141,7 @@ export default (opts: Opts = {}) => ({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		react(viaKeyboard = false) {
 | 
			
		||||
			pleaseLogin(this.$root);
 | 
			
		||||
			this.blur();
 | 
			
		||||
			this.$root.new(MkReactionPicker, {
 | 
			
		||||
				source: this.$refs.reactButton,
 | 
			
		||||
@@ -159,6 +167,7 @@ export default (opts: Opts = {}) => ({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		favorite() {
 | 
			
		||||
			pleaseLogin(this.$root);
 | 
			
		||||
			this.$root.api('notes/favorites/create', {
 | 
			
		||||
				noteId: this.appearNote.id
 | 
			
		||||
			}).then(() => {
 | 
			
		||||
@@ -170,17 +179,30 @@ export default (opts: Opts = {}) => ({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		del() {
 | 
			
		||||
			this.$root.api('notes/delete', {
 | 
			
		||||
				noteId: this.appearNote.id
 | 
			
		||||
			this.$root.dialog({
 | 
			
		||||
				type: 'warning',
 | 
			
		||||
				text: this.$t('@.delete-confirm'),
 | 
			
		||||
				showCancelButton: true
 | 
			
		||||
			}).then(({ canceled }) => {
 | 
			
		||||
				if (canceled) return;
 | 
			
		||||
 | 
			
		||||
				this.$root.api('notes/delete', {
 | 
			
		||||
					noteId: this.appearNote.id
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		menu(viaKeyboard = false) {
 | 
			
		||||
			if (this.openingMenu) return;
 | 
			
		||||
			this.openingMenu = true;
 | 
			
		||||
			this.$root.new(MkNoteMenu, {
 | 
			
		||||
				source: this.$refs.menuButton,
 | 
			
		||||
				note: this.appearNote,
 | 
			
		||||
				animation: !viaKeyboard
 | 
			
		||||
			}).$once('closed', this.focus);
 | 
			
		||||
			}).$once('closed', () => {
 | 
			
		||||
				this.openingMenu = false;
 | 
			
		||||
				this.focus();
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		toggleShowContent() {
 | 
			
		||||
 
 | 
			
		||||
@@ -44,13 +44,21 @@ export default (opts) => ({
 | 
			
		||||
				return window.scrollY <= 8;
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			window.addEventListener('scroll', this.onWindowScroll, { passive: true });
 | 
			
		||||
			window.addEventListener('scroll', this.onScroll, { passive: true });
 | 
			
		||||
		} else if (opts.isContainer) {
 | 
			
		||||
			this.isScrollTop = () => {
 | 
			
		||||
				return this.$el.scrollTop <= 8;
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			this.$el.addEventListener('scroll', this.onScroll, { passive: true });
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		if (opts.captureWindowScroll) {
 | 
			
		||||
			window.removeEventListener('scroll', this.onWindowScroll);
 | 
			
		||||
			window.removeEventListener('scroll', this.onScroll);
 | 
			
		||||
		} else if (opts.isContainer) {
 | 
			
		||||
			this.$el.removeEventListener('scroll', this.onScroll);
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -67,6 +75,7 @@ export default (opts) => ({
 | 
			
		||||
 | 
			
		||||
		async init() {
 | 
			
		||||
			this.fetching = true;
 | 
			
		||||
			if (opts.beforeInit) opts.beforeInit(this);
 | 
			
		||||
			let params = typeof this.pagination.params === 'function' ? this.pagination.params(true) : this.pagination.params;
 | 
			
		||||
			if (params && params.then) params = await params;
 | 
			
		||||
			await this.$root.api(this.pagination.endpoint, {
 | 
			
		||||
@@ -151,7 +160,7 @@ export default (opts) => ({
 | 
			
		||||
			this.queue = [];
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onWindowScroll() {
 | 
			
		||||
		onScroll() {
 | 
			
		||||
			if (this.isScrollTop()) {
 | 
			
		||||
				this.onTop();
 | 
			
		||||
			}
 | 
			
		||||
@@ -162,8 +171,10 @@ export default (opts) => ({
 | 
			
		||||
				// http://d.hatena.ne.jp/favril/20091105/1257403319
 | 
			
		||||
				if (this.$el.offsetHeight == 0) return;
 | 
			
		||||
 | 
			
		||||
				const current = window.scrollY + window.innerHeight;
 | 
			
		||||
				if (current > document.body.offsetHeight - 8) this.onBottom();
 | 
			
		||||
				const bottomPosition = opts.isContainer ? this.$el.scrollHeight : document.body.offsetHeight;
 | 
			
		||||
 | 
			
		||||
				const currentBottomPosition = opts.isContainer ? this.$el.scrollTop + this.$el.clientHeight : window.scrollY + window.innerHeight;
 | 
			
		||||
				if (currentBottomPosition > (bottomPosition - 8)) this.onBottom();
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								src/client/app/common/scripts/please-login.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/client/app/common/scripts/please-login.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
export default ($root: any) => {
 | 
			
		||||
	if ($root.$store.getters.isSignedIn) return;
 | 
			
		||||
 | 
			
		||||
	$root.dialog({
 | 
			
		||||
		title: $root.$t('@.signin-required'),
 | 
			
		||||
		text: null
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	throw new Error('signin required');
 | 
			
		||||
};
 | 
			
		||||
@@ -35,6 +35,10 @@ export default (opts) => ({
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		initialNote: {
 | 
			
		||||
			type: Object,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		instant: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
@@ -149,6 +153,10 @@ export default (opts) => ({
 | 
			
		||||
		// デフォルト公開範囲
 | 
			
		||||
		this.applyVisibility(this.$store.state.settings.rememberNoteVisibility ? (this.$store.state.device.visibility || this.$store.state.settings.defaultNoteVisibility) : this.$store.state.settings.defaultNoteVisibility);
 | 
			
		||||
 | 
			
		||||
		if (this.reply && this.reply.localOnly) {
 | 
			
		||||
			this.localOnly = true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 公開以外へのリプライ時は元の公開範囲を引き継ぐ
 | 
			
		||||
		if (this.reply && ['home', 'followers', 'specified'].includes(this.reply.visibility)) {
 | 
			
		||||
			this.visibility = this.reply.visibility;
 | 
			
		||||
@@ -158,13 +166,13 @@ export default (opts) => ({
 | 
			
		||||
				}).then(users => {
 | 
			
		||||
					this.visibleUsers.push(...users);
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (this.reply && this.reply.userId !== this.$store.state.i.id) {
 | 
			
		||||
			this.$root.api('users/show', { userId: this.reply.userId }).then(user => {
 | 
			
		||||
				this.visibleUsers.push(user);
 | 
			
		||||
			});
 | 
			
		||||
				if (this.reply.userId !== this.$store.state.i.id) {
 | 
			
		||||
					this.$root.api('users/show', { userId: this.reply.userId }).then(user => {
 | 
			
		||||
						this.visibleUsers.push(user);
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// keep cw when reply
 | 
			
		||||
@@ -196,6 +204,29 @@ export default (opts) => ({
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 削除して編集
 | 
			
		||||
			if (this.initialNote) {
 | 
			
		||||
				const init = this.initialNote;
 | 
			
		||||
				this.text = init.text ? init.text : '';
 | 
			
		||||
				this.files = init.files;
 | 
			
		||||
				this.cw = init.cw;
 | 
			
		||||
				this.useCw = init.cw != null;
 | 
			
		||||
				if (init.poll) {
 | 
			
		||||
					this.poll = true;
 | 
			
		||||
					this.$nextTick(() => {
 | 
			
		||||
						(this.$refs.poll as any).set({
 | 
			
		||||
							choices: init.poll.choices.map(c => c.text),
 | 
			
		||||
							multiple: init.poll.multiple
 | 
			
		||||
						});
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
				// hack 位置情報投稿が動くようになったら適用する
 | 
			
		||||
				this.geo = null;
 | 
			
		||||
				this.visibility = init.visibility;
 | 
			
		||||
				this.localOnly = init.localOnly;
 | 
			
		||||
				this.quoteId = init.renote ? init.renote.id : null;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			this.$nextTick(() => this.watch());
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
@@ -292,7 +323,7 @@ export default (opts) => ({
 | 
			
		||||
		setVisibility() {
 | 
			
		||||
			const w = this.$root.new(MkVisibilityChooser, {
 | 
			
		||||
				source: this.$refs.visibilityButton,
 | 
			
		||||
				currentVisibility: this.visibility
 | 
			
		||||
				currentVisibility: this.localOnly ? `local-${this.visibility}` : this.visibility
 | 
			
		||||
			});
 | 
			
		||||
			w.$once('chosen', v => {
 | 
			
		||||
				this.applyVisibility(v);
 | 
			
		||||
@@ -328,6 +359,7 @@ export default (opts) => ({
 | 
			
		||||
			this.text = '';
 | 
			
		||||
			this.files = [];
 | 
			
		||||
			this.poll = false;
 | 
			
		||||
			this.quoteId = null;
 | 
			
		||||
			this.$emit('change-attached-files', this.files);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
@@ -357,7 +389,7 @@ export default (opts) => ({
 | 
			
		||||
 | 
			
		||||
			const paste = e.clipboardData.getData('text');
 | 
			
		||||
 | 
			
		||||
			if (paste.startsWith(url + '/notes/')) {
 | 
			
		||||
			if (!this.renote && !this.quoteId && paste.startsWith(url + '/notes/')) {
 | 
			
		||||
				e.preventDefault();
 | 
			
		||||
 | 
			
		||||
				this.$root.dialog({
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								src/client/app/common/scripts/room/furniture.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/client/app/common/scripts/room/furniture.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
export type RoomInfo = {
 | 
			
		||||
	roomType: string;
 | 
			
		||||
	carpetColor: string;
 | 
			
		||||
	furnitures: Furniture[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Furniture = {
 | 
			
		||||
	id: string; // 同じ家具が複数ある場合にそれぞれを識別するためのIDであり、家具IDではない
 | 
			
		||||
	type: string; // こっちが家具ID(chairとか)
 | 
			
		||||
	position: {
 | 
			
		||||
		x: number;
 | 
			
		||||
		y: number;
 | 
			
		||||
		z: number;
 | 
			
		||||
	};
 | 
			
		||||
	rotation: {
 | 
			
		||||
		x: number;
 | 
			
		||||
		y: number;
 | 
			
		||||
		z: number;
 | 
			
		||||
	};
 | 
			
		||||
	props?: Record<string, any>;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										397
									
								
								src/client/app/common/scripts/room/furnitures.json5
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										397
									
								
								src/client/app/common/scripts/room/furnitures.json5
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,397 @@
 | 
			
		||||
// 家具メタデータ
 | 
			
		||||
 | 
			
		||||
// 家具にはユーザーが設定できるプロパティを設定可能です:
 | 
			
		||||
//
 | 
			
		||||
// props: {
 | 
			
		||||
//   <propname>: <proptype>
 | 
			
		||||
// }
 | 
			
		||||
//
 | 
			
		||||
// proptype一覧:
 | 
			
		||||
// * image ... 画像選択ダイアログを出し、その画像のURLが格納されます
 | 
			
		||||
// * color ... 色選択コントロールを出し、選択された色が格納されます
 | 
			
		||||
 | 
			
		||||
// 家具にカスタムテクスチャを適用できるようにするには、textureプロパティに以下の追加の情報を含めます:
 | 
			
		||||
// 便宜上そのUVのどの部分にカスタムテクスチャを貼り合わせるかのエリアをテクスチャエリアと呼びます。
 | 
			
		||||
// UVは1024*1024だと仮定します。
 | 
			
		||||
//
 | 
			
		||||
// <key>: {
 | 
			
		||||
//   prop: <プロパティ名>,
 | 
			
		||||
//   uv: {
 | 
			
		||||
//     x: <テクスチャエリアX座標>,
 | 
			
		||||
//     y: <テクスチャエリアY座標>,
 | 
			
		||||
//     width: <テクスチャエリアの幅>,
 | 
			
		||||
//     height: <テクスチャエリアの高さ>,
 | 
			
		||||
//   },
 | 
			
		||||
// }
 | 
			
		||||
//
 | 
			
		||||
// <key>には、カスタムテクスチャを適用したいメッシュ名を指定します
 | 
			
		||||
// <プロパティ名>には、カスタムテクスチャとして使用する画像を格納するプロパティ(前述)名を指定します
 | 
			
		||||
 | 
			
		||||
// 家具にカスタムカラーを適用できるようにするには、colorプロパティに以下の追加の情報を含めます:
 | 
			
		||||
//
 | 
			
		||||
// <key>: <プロパティ名>
 | 
			
		||||
//
 | 
			
		||||
// <key>には、カスタムカラーを適用したいマテリアル名を指定します
 | 
			
		||||
// <プロパティ名>には、カスタムカラーとして使用する色を格納するプロパティ(前述)名を指定します
 | 
			
		||||
 | 
			
		||||
[
 | 
			
		||||
	{
 | 
			
		||||
		id: "milk",
 | 
			
		||||
		place: "floor"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "bed",
 | 
			
		||||
		place: "floor"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "low-table",
 | 
			
		||||
		place: "floor",
 | 
			
		||||
		props: {
 | 
			
		||||
			color: 'color'
 | 
			
		||||
		},
 | 
			
		||||
		color: {
 | 
			
		||||
			Table: 'color'
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "desk",
 | 
			
		||||
		place: "floor",
 | 
			
		||||
		props: {
 | 
			
		||||
			color: 'color'
 | 
			
		||||
		},
 | 
			
		||||
		color: {
 | 
			
		||||
			Board: 'color'
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "chair",
 | 
			
		||||
		place: "floor",
 | 
			
		||||
		props: {
 | 
			
		||||
			color: 'color'
 | 
			
		||||
		},
 | 
			
		||||
		color: {
 | 
			
		||||
			Chair: 'color'
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "chair2",
 | 
			
		||||
		place: "floor",
 | 
			
		||||
		props: {
 | 
			
		||||
			color1: 'color',
 | 
			
		||||
			color2: 'color'
 | 
			
		||||
		},
 | 
			
		||||
		color: {
 | 
			
		||||
			Cushion: 'color1',
 | 
			
		||||
			Leg: 'color2'
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "fan",
 | 
			
		||||
		place: "wall"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "pc",
 | 
			
		||||
		place: "floor"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "plant",
 | 
			
		||||
		place: "floor"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "plant2",
 | 
			
		||||
		place: "floor"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "eraser",
 | 
			
		||||
		place: "floor"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "pencil",
 | 
			
		||||
		place: "floor"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "pudding",
 | 
			
		||||
		place: "floor"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "cardboard-box",
 | 
			
		||||
		place: "floor"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "cardboard-box2",
 | 
			
		||||
		place: "floor"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "cardboard-box3",
 | 
			
		||||
		place: "floor"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "book",
 | 
			
		||||
		place: "floor",
 | 
			
		||||
		props: {
 | 
			
		||||
			color: 'color'
 | 
			
		||||
		},
 | 
			
		||||
		color: {
 | 
			
		||||
			Cover: 'color'
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "book2",
 | 
			
		||||
		place: "floor"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "piano",
 | 
			
		||||
		place: "floor"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "facial-tissue",
 | 
			
		||||
		place: "floor"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "server",
 | 
			
		||||
		place: "floor"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "moon",
 | 
			
		||||
		place: "floor"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "corkboard",
 | 
			
		||||
		place: "wall"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "mousepad",
 | 
			
		||||
		place: "floor",
 | 
			
		||||
		props: {
 | 
			
		||||
			color: 'color'
 | 
			
		||||
		},
 | 
			
		||||
		color: {
 | 
			
		||||
			Pad: 'color'
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "monitor",
 | 
			
		||||
		place: "floor",
 | 
			
		||||
		props: {
 | 
			
		||||
			screen: 'image'
 | 
			
		||||
		},
 | 
			
		||||
		texture: {
 | 
			
		||||
			Screen: {
 | 
			
		||||
				prop: 'screen',
 | 
			
		||||
				uv: {
 | 
			
		||||
					x: 0,
 | 
			
		||||
					y: 434,
 | 
			
		||||
					width: 1024,
 | 
			
		||||
					height: 588,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "tv",
 | 
			
		||||
		place: "floor",
 | 
			
		||||
		props: {
 | 
			
		||||
			screen: 'image'
 | 
			
		||||
		},
 | 
			
		||||
		texture: {
 | 
			
		||||
			Screen: {
 | 
			
		||||
				prop: 'screen',
 | 
			
		||||
				uv: {
 | 
			
		||||
					x: 0,
 | 
			
		||||
					y: 434,
 | 
			
		||||
					width: 1024,
 | 
			
		||||
					height: 588,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "keyboard",
 | 
			
		||||
		place: "floor"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "carpet-stripe",
 | 
			
		||||
		place: "floor",
 | 
			
		||||
		props: {
 | 
			
		||||
			color1: 'color',
 | 
			
		||||
			color2: 'color'
 | 
			
		||||
		},
 | 
			
		||||
		color: {
 | 
			
		||||
			CarpetAreaA: 'color1',
 | 
			
		||||
			CarpetAreaB: 'color2'
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "mat",
 | 
			
		||||
		place: "floor",
 | 
			
		||||
		props: {
 | 
			
		||||
			color: 'color'
 | 
			
		||||
		},
 | 
			
		||||
		color: {
 | 
			
		||||
			Mat: 'color'
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "color-box",
 | 
			
		||||
		place: "floor",
 | 
			
		||||
		props: {
 | 
			
		||||
			color: 'color'
 | 
			
		||||
		},
 | 
			
		||||
		color: {
 | 
			
		||||
			main: 'color'
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "wall-clock",
 | 
			
		||||
		place: "wall"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "cube",
 | 
			
		||||
		place: "floor",
 | 
			
		||||
		props: {
 | 
			
		||||
			color: 'color'
 | 
			
		||||
		},
 | 
			
		||||
		color: {
 | 
			
		||||
			Cube: 'color'
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "photoframe",
 | 
			
		||||
		place: "wall",
 | 
			
		||||
		props: {
 | 
			
		||||
			photo: 'image',
 | 
			
		||||
			color: 'color'
 | 
			
		||||
		},
 | 
			
		||||
		texture: {
 | 
			
		||||
			Photo: {
 | 
			
		||||
				prop: 'photo',
 | 
			
		||||
				uv: {
 | 
			
		||||
					x: 0,
 | 
			
		||||
					y: 342,
 | 
			
		||||
					width: 1024,
 | 
			
		||||
					height: 683,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		color: {
 | 
			
		||||
			Frame: 'color'
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "pinguin",
 | 
			
		||||
		place: "floor",
 | 
			
		||||
		props: {
 | 
			
		||||
			body: 'color',
 | 
			
		||||
			belly: 'color'
 | 
			
		||||
		},
 | 
			
		||||
		color: {
 | 
			
		||||
			Body: 'body',
 | 
			
		||||
			Belly: 'belly',
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "rubik-cube",
 | 
			
		||||
		place: "floor",
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "poster-h",
 | 
			
		||||
		place: "wall",
 | 
			
		||||
		props: {
 | 
			
		||||
			picture: 'image'
 | 
			
		||||
		},
 | 
			
		||||
		texture: {
 | 
			
		||||
			Poster: {
 | 
			
		||||
				prop: 'picture',
 | 
			
		||||
				uv: {
 | 
			
		||||
					x: 0,
 | 
			
		||||
					y: 277,
 | 
			
		||||
					width: 1024,
 | 
			
		||||
					height: 745,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "poster-v",
 | 
			
		||||
		place: "wall",
 | 
			
		||||
		props: {
 | 
			
		||||
			picture: 'image'
 | 
			
		||||
		},
 | 
			
		||||
		texture: {
 | 
			
		||||
			Poster: {
 | 
			
		||||
				prop: 'picture',
 | 
			
		||||
				uv: {
 | 
			
		||||
					x: 0,
 | 
			
		||||
					y: 0,
 | 
			
		||||
					width: 745,
 | 
			
		||||
					height: 1024,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "sofa",
 | 
			
		||||
		place: "floor",
 | 
			
		||||
		props: {
 | 
			
		||||
			color: 'color'
 | 
			
		||||
		},
 | 
			
		||||
		color: {
 | 
			
		||||
			Sofa: 'color'
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "spiral",
 | 
			
		||||
		place: "floor",
 | 
			
		||||
		props: {
 | 
			
		||||
			color: 'color'
 | 
			
		||||
		},
 | 
			
		||||
		color: {
 | 
			
		||||
			Step: 'color'
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "bin",
 | 
			
		||||
		place: "floor",
 | 
			
		||||
		props: {
 | 
			
		||||
			color: 'color'
 | 
			
		||||
		},
 | 
			
		||||
		color: {
 | 
			
		||||
			Bin: 'color'
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "cup-noodle",
 | 
			
		||||
		place: "floor"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: "holo-display",
 | 
			
		||||
		place: "floor",
 | 
			
		||||
		props: {
 | 
			
		||||
			image: 'image'
 | 
			
		||||
		},
 | 
			
		||||
		texture: {
 | 
			
		||||
			Image_Front: {
 | 
			
		||||
				prop: 'image',
 | 
			
		||||
				uv: {
 | 
			
		||||
					x: 0,
 | 
			
		||||
					y: 0,
 | 
			
		||||
					width: 1024,
 | 
			
		||||
					height: 1024,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			Image_Back: {
 | 
			
		||||
				prop: 'image',
 | 
			
		||||
				uv: {
 | 
			
		||||
					x: 0,
 | 
			
		||||
					y: 0,
 | 
			
		||||
					width: 1024,
 | 
			
		||||
					height: 1024,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: 'energy-drink',
 | 
			
		||||
		place: "floor",
 | 
			
		||||
	}
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										776
									
								
								src/client/app/common/scripts/room/room.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										776
									
								
								src/client/app/common/scripts/room/room.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,776 @@
 | 
			
		||||
import autobind from 'autobind-decorator';
 | 
			
		||||
import { v4 as uuid } from 'uuid';
 | 
			
		||||
import * as THREE from 'three';
 | 
			
		||||
import { GLTFLoader, GLTF } from 'three/examples/jsm/loaders/GLTFLoader';
 | 
			
		||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
 | 
			
		||||
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
 | 
			
		||||
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
 | 
			
		||||
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
 | 
			
		||||
import { BloomPass } from 'three/examples/jsm/postprocessing/BloomPass.js';
 | 
			
		||||
import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js';
 | 
			
		||||
import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js';
 | 
			
		||||
import { Furniture, RoomInfo } from './furniture';
 | 
			
		||||
import { query as urlQuery } from '../../../../../prelude/url';
 | 
			
		||||
const furnitureDefs = require('./furnitures.json5');
 | 
			
		||||
 | 
			
		||||
THREE.ImageUtils.crossOrigin = '';
 | 
			
		||||
 | 
			
		||||
type Options = {
 | 
			
		||||
	graphicsQuality: Room['graphicsQuality'];
 | 
			
		||||
	onChangeSelect: Room['onChangeSelect'];
 | 
			
		||||
	useOrthographicCamera: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * MisskeyRoom Core Engine
 | 
			
		||||
 */
 | 
			
		||||
export class Room {
 | 
			
		||||
	private clock: THREE.Clock;
 | 
			
		||||
	private scene: THREE.Scene;
 | 
			
		||||
	private renderer: THREE.WebGLRenderer;
 | 
			
		||||
	private camera: THREE.PerspectiveCamera | THREE.OrthographicCamera;
 | 
			
		||||
	private controls: OrbitControls;
 | 
			
		||||
	private composer: EffectComposer;
 | 
			
		||||
	private mixers: THREE.AnimationMixer[] = [];
 | 
			
		||||
	private furnitureControl: TransformControls;
 | 
			
		||||
	private roomInfo: RoomInfo;
 | 
			
		||||
	private graphicsQuality: 'cheep' | 'low' | 'medium' | 'high' | 'ultra';
 | 
			
		||||
	private roomObj: THREE.Object3D;
 | 
			
		||||
	private objects: THREE.Object3D[] = [];
 | 
			
		||||
	private selectedObject: THREE.Object3D = null;
 | 
			
		||||
	private onChangeSelect: Function;
 | 
			
		||||
	private isTransformMode = false;
 | 
			
		||||
	private renderFrameRequestId: number;
 | 
			
		||||
 | 
			
		||||
	private get canvas(): HTMLCanvasElement {
 | 
			
		||||
		return this.renderer.domElement;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private get furnitures(): Furniture[] {
 | 
			
		||||
		return this.roomInfo.furnitures;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private set furnitures(furnitures: Furniture[]) {
 | 
			
		||||
		this.roomInfo.furnitures = furnitures;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private get enableShadow() {
 | 
			
		||||
		return this.graphicsQuality != 'cheep';
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private get usePostFXs() {
 | 
			
		||||
		return this.graphicsQuality !== 'cheep' && this.graphicsQuality !== 'low';
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private get shadowQuality() {
 | 
			
		||||
		return (
 | 
			
		||||
			this.graphicsQuality === 'ultra' ? 16384 :
 | 
			
		||||
			this.graphicsQuality === 'high' ? 8192 :
 | 
			
		||||
			this.graphicsQuality === 'medium' ? 4096 :
 | 
			
		||||
			this.graphicsQuality === 'low' ? 1024 :
 | 
			
		||||
			0); // cheep
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	constructor(user, isMyRoom, roomInfo: RoomInfo, container, options: Options) {
 | 
			
		||||
		this.roomInfo = roomInfo;
 | 
			
		||||
		this.graphicsQuality = options.graphicsQuality;
 | 
			
		||||
		this.onChangeSelect = options.onChangeSelect;
 | 
			
		||||
 | 
			
		||||
		this.clock = new THREE.Clock(true);
 | 
			
		||||
 | 
			
		||||
		//#region Init a scene
 | 
			
		||||
		this.scene = new THREE.Scene();
 | 
			
		||||
 | 
			
		||||
		const width = window.innerWidth;
 | 
			
		||||
		const height = window.innerHeight;
 | 
			
		||||
 | 
			
		||||
		//#region Init a renderer
 | 
			
		||||
		this.renderer = new THREE.WebGLRenderer({
 | 
			
		||||
			antialias: false,
 | 
			
		||||
			stencil: false,
 | 
			
		||||
			alpha: false,
 | 
			
		||||
			powerPreference:
 | 
			
		||||
				this.graphicsQuality === 'ultra' ? 'high-performance' :
 | 
			
		||||
				this.graphicsQuality === 'high' ? 'high-performance' :
 | 
			
		||||
				this.graphicsQuality === 'medium' ? 'default' :
 | 
			
		||||
				this.graphicsQuality === 'low' ? 'low-power' :
 | 
			
		||||
				'low-power' // cheep
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.renderer.setPixelRatio(window.devicePixelRatio);
 | 
			
		||||
		this.renderer.setSize(width, height);
 | 
			
		||||
		this.renderer.autoClear = false;
 | 
			
		||||
		this.renderer.setClearColor(new THREE.Color(0x051f2d));
 | 
			
		||||
		this.renderer.shadowMap.enabled = this.enableShadow;
 | 
			
		||||
		this.renderer.shadowMap.type =
 | 
			
		||||
			this.graphicsQuality === 'ultra' ? THREE.PCFSoftShadowMap :
 | 
			
		||||
			this.graphicsQuality === 'high' ? THREE.PCFSoftShadowMap :
 | 
			
		||||
			this.graphicsQuality === 'medium' ? THREE.PCFShadowMap :
 | 
			
		||||
			this.graphicsQuality === 'low' ? THREE.BasicShadowMap :
 | 
			
		||||
			THREE.BasicShadowMap; // cheep
 | 
			
		||||
 | 
			
		||||
		container.appendChild(this.canvas);
 | 
			
		||||
		//#endregion
 | 
			
		||||
 | 
			
		||||
		//#region Init a camera
 | 
			
		||||
		this.camera = options.useOrthographicCamera
 | 
			
		||||
			? new THREE.OrthographicCamera(
 | 
			
		||||
				width / - 2, width / 2, height / 2, height / - 2, -10, 10)
 | 
			
		||||
			: new THREE.PerspectiveCamera(45, width / height);
 | 
			
		||||
 | 
			
		||||
		if (options.useOrthographicCamera) {
 | 
			
		||||
			this.camera.position.x = 2;
 | 
			
		||||
			this.camera.position.y = 2;
 | 
			
		||||
			this.camera.position.z = 2;
 | 
			
		||||
			this.camera.zoom = 100;
 | 
			
		||||
			this.camera.updateProjectionMatrix();
 | 
			
		||||
		} else {
 | 
			
		||||
			this.camera.position.x = 5;
 | 
			
		||||
			this.camera.position.y = 2;
 | 
			
		||||
			this.camera.position.z = 5;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this.scene.add(this.camera);
 | 
			
		||||
		//#endregion
 | 
			
		||||
 | 
			
		||||
		//#region AmbientLight
 | 
			
		||||
		const ambientLight = new THREE.AmbientLight(0xffffff, 1);
 | 
			
		||||
		this.scene.add(ambientLight);
 | 
			
		||||
		//#endregion
 | 
			
		||||
 | 
			
		||||
		if (this.graphicsQuality !== 'cheep') {
 | 
			
		||||
			//#region Room light
 | 
			
		||||
			const roomLight = new THREE.SpotLight(0xffffff, 0.1);
 | 
			
		||||
 | 
			
		||||
			roomLight.position.set(0, 8, 0);
 | 
			
		||||
			roomLight.castShadow = this.enableShadow;
 | 
			
		||||
			roomLight.shadow.bias = -0.0001;
 | 
			
		||||
			roomLight.shadow.mapSize.width = this.shadowQuality;
 | 
			
		||||
			roomLight.shadow.mapSize.height = this.shadowQuality;
 | 
			
		||||
			roomLight.shadow.camera.near = 0.1;
 | 
			
		||||
			roomLight.shadow.camera.far = 9;
 | 
			
		||||
			roomLight.shadow.camera.fov = 45;
 | 
			
		||||
 | 
			
		||||
			this.scene.add(roomLight);
 | 
			
		||||
			//#endregion
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//#region Out light
 | 
			
		||||
		const outLight1 = new THREE.SpotLight(0xffffff, 0.4);
 | 
			
		||||
		outLight1.position.set(9, 3, -2);
 | 
			
		||||
		outLight1.castShadow = this.enableShadow;
 | 
			
		||||
		outLight1.shadow.bias = -0.001; // アクネ、アーチファクト対策 その代わりピーターパンが発生する可能性がある
 | 
			
		||||
		outLight1.shadow.mapSize.width = this.shadowQuality;
 | 
			
		||||
		outLight1.shadow.mapSize.height = this.shadowQuality;
 | 
			
		||||
		outLight1.shadow.camera.near = 6;
 | 
			
		||||
		outLight1.shadow.camera.far = 15;
 | 
			
		||||
		outLight1.shadow.camera.fov = 45;
 | 
			
		||||
		this.scene.add(outLight1);
 | 
			
		||||
 | 
			
		||||
		const outLight2 = new THREE.SpotLight(0xffffff, 0.2);
 | 
			
		||||
		outLight2.position.set(-2, 3, 9);
 | 
			
		||||
		outLight2.castShadow = false;
 | 
			
		||||
		outLight2.shadow.bias = -0.001; // アクネ、アーチファクト対策 その代わりピーターパンが発生する可能性がある
 | 
			
		||||
		outLight2.shadow.camera.near = 6;
 | 
			
		||||
		outLight2.shadow.camera.far = 15;
 | 
			
		||||
		outLight2.shadow.camera.fov = 45;
 | 
			
		||||
		this.scene.add(outLight2);
 | 
			
		||||
		//#endregion
 | 
			
		||||
 | 
			
		||||
		//#region Init a controller
 | 
			
		||||
		this.controls = new OrbitControls(this.camera, this.canvas);
 | 
			
		||||
 | 
			
		||||
		this.controls.target.set(0, 1, 0);
 | 
			
		||||
		this.controls.enableZoom = true;
 | 
			
		||||
		this.controls.enablePan = isMyRoom;
 | 
			
		||||
		this.controls.minPolarAngle = 0;
 | 
			
		||||
		this.controls.maxPolarAngle = Math.PI / 2;
 | 
			
		||||
		this.controls.minAzimuthAngle = 0;
 | 
			
		||||
		this.controls.maxAzimuthAngle = Math.PI / 2;
 | 
			
		||||
		this.controls.enableDamping = true;
 | 
			
		||||
		this.controls.dampingFactor = 0.2;
 | 
			
		||||
		this.controls.mouseButtons.LEFT = 1;
 | 
			
		||||
		this.controls.mouseButtons.MIDDLE = 2;
 | 
			
		||||
		this.controls.mouseButtons.RIGHT = 0;
 | 
			
		||||
		//#endregion
 | 
			
		||||
 | 
			
		||||
		//#region POST FXs
 | 
			
		||||
		if (!this.usePostFXs) {
 | 
			
		||||
			this.composer = null;
 | 
			
		||||
		} else {
 | 
			
		||||
			const renderTarget = new THREE.WebGLRenderTarget(width, height, {
 | 
			
		||||
				minFilter: THREE.LinearFilter,
 | 
			
		||||
				magFilter: THREE.LinearFilter,
 | 
			
		||||
				format: THREE.RGBFormat,
 | 
			
		||||
				stencilBuffer: false,
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			const fxaa = new ShaderPass(FXAAShader);
 | 
			
		||||
			fxaa.uniforms['resolution'].value = new THREE.Vector2(1 / width, 1 / height);
 | 
			
		||||
			fxaa.renderToScreen = true;
 | 
			
		||||
 | 
			
		||||
			this.composer = new EffectComposer(this.renderer, renderTarget);
 | 
			
		||||
			this.composer.addPass(new RenderPass(this.scene, this.camera));
 | 
			
		||||
			if (this.graphicsQuality === 'ultra') {
 | 
			
		||||
				this.composer.addPass(new BloomPass(0.25, 30, 128.0, 512));
 | 
			
		||||
			}
 | 
			
		||||
			this.composer.addPass(fxaa);
 | 
			
		||||
		}
 | 
			
		||||
		//#endregion
 | 
			
		||||
		//#endregion
 | 
			
		||||
 | 
			
		||||
		//#region Label
 | 
			
		||||
		//#region Avatar
 | 
			
		||||
		const avatarUrl = `/proxy/?${urlQuery({ url: user.avatarUrl })}`;
 | 
			
		||||
 | 
			
		||||
		const textureLoader = new THREE.TextureLoader();
 | 
			
		||||
		textureLoader.crossOrigin = 'anonymous';
 | 
			
		||||
 | 
			
		||||
		const iconTexture = textureLoader.load(avatarUrl);
 | 
			
		||||
		iconTexture.wrapS = THREE.RepeatWrapping;
 | 
			
		||||
		iconTexture.wrapT = THREE.RepeatWrapping;
 | 
			
		||||
		iconTexture.anisotropy = 16;
 | 
			
		||||
 | 
			
		||||
		const avatarMaterial = new THREE.MeshBasicMaterial({
 | 
			
		||||
			map: iconTexture,
 | 
			
		||||
			side: THREE.DoubleSide,
 | 
			
		||||
			alphaTest: 0.5
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		const iconGeometry = new THREE.PlaneGeometry(1, 1);
 | 
			
		||||
 | 
			
		||||
		const avatarObject = new THREE.Mesh(iconGeometry, avatarMaterial);
 | 
			
		||||
		avatarObject.position.set(-3, 2.5, 2);
 | 
			
		||||
		avatarObject.rotation.y = Math.PI / 2;
 | 
			
		||||
		avatarObject.castShadow = false;
 | 
			
		||||
 | 
			
		||||
		this.scene.add(avatarObject);
 | 
			
		||||
		//#endregion
 | 
			
		||||
 | 
			
		||||
		//#region Username
 | 
			
		||||
		const name = user.username;
 | 
			
		||||
 | 
			
		||||
		new THREE.FontLoader().load('/assets/fonts/helvetiker_regular.typeface.json', font => {
 | 
			
		||||
			const nameGeometry = new THREE.TextGeometry(name, {
 | 
			
		||||
				size: 0.5,
 | 
			
		||||
				height: 0,
 | 
			
		||||
				curveSegments: 8,
 | 
			
		||||
				font: font,
 | 
			
		||||
				bevelThickness: 0,
 | 
			
		||||
				bevelSize: 0,
 | 
			
		||||
				bevelEnabled: false
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			const nameMaterial = new THREE.MeshLambertMaterial({
 | 
			
		||||
				color: 0xffffff
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			const nameObject = new THREE.Mesh(nameGeometry, nameMaterial);
 | 
			
		||||
			nameObject.position.set(-3, 2.25, 1.25);
 | 
			
		||||
			nameObject.rotation.y = Math.PI / 2;
 | 
			
		||||
			nameObject.castShadow = false;
 | 
			
		||||
 | 
			
		||||
			this.scene.add(nameObject);
 | 
			
		||||
		});
 | 
			
		||||
		//#endregion
 | 
			
		||||
		//#endregion
 | 
			
		||||
 | 
			
		||||
		//#region Interaction
 | 
			
		||||
		if (isMyRoom) {
 | 
			
		||||
			this.furnitureControl = new TransformControls(this.camera, this.canvas);
 | 
			
		||||
			this.scene.add(this.furnitureControl);
 | 
			
		||||
 | 
			
		||||
			// Hover highlight
 | 
			
		||||
			this.canvas.onmousemove = this.onmousemove;
 | 
			
		||||
 | 
			
		||||
			// Click
 | 
			
		||||
			this.canvas.onmousedown = this.onmousedown;
 | 
			
		||||
		}
 | 
			
		||||
		//#endregion
 | 
			
		||||
 | 
			
		||||
		//#region Init room
 | 
			
		||||
		this.loadRoom();
 | 
			
		||||
		//#endregion
 | 
			
		||||
 | 
			
		||||
		//#region Load furnitures
 | 
			
		||||
		for (const furniture of this.furnitures) {
 | 
			
		||||
			this.loadFurniture(furniture).then(obj => {
 | 
			
		||||
				this.scene.add(obj.scene);
 | 
			
		||||
				this.objects.push(obj.scene);
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
		//#endregion
 | 
			
		||||
 | 
			
		||||
		// Start render
 | 
			
		||||
		if (this.usePostFXs) {
 | 
			
		||||
			this.renderWithPostFXs();
 | 
			
		||||
		} else {
 | 
			
		||||
			this.renderWithoutPostFXs();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	private renderWithoutPostFXs() {
 | 
			
		||||
		this.renderFrameRequestId =
 | 
			
		||||
			window.requestAnimationFrame(this.renderWithoutPostFXs);
 | 
			
		||||
 | 
			
		||||
		// Update animations
 | 
			
		||||
		const clock = this.clock.getDelta();
 | 
			
		||||
		for (const mixer of this.mixers) {
 | 
			
		||||
			mixer.update(clock);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this.controls.update();
 | 
			
		||||
		this.renderer.render(this.scene, this.camera);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	private renderWithPostFXs() {
 | 
			
		||||
		this.renderFrameRequestId =
 | 
			
		||||
			window.requestAnimationFrame(this.renderWithPostFXs);
 | 
			
		||||
 | 
			
		||||
		// Update animations
 | 
			
		||||
		const clock = this.clock.getDelta();
 | 
			
		||||
		for (const mixer of this.mixers) {
 | 
			
		||||
			mixer.update(clock);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this.controls.update();
 | 
			
		||||
		this.renderer.clear();
 | 
			
		||||
		this.composer.render();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	private loadRoom() {
 | 
			
		||||
		const type = this.roomInfo.roomType;
 | 
			
		||||
		new GLTFLoader().load(`/assets/room/rooms/${type}/${type}.glb`, gltf => {
 | 
			
		||||
			gltf.scene.traverse(child => {
 | 
			
		||||
				if (!(child instanceof THREE.Mesh)) return;
 | 
			
		||||
 | 
			
		||||
				child.receiveShadow = this.enableShadow;
 | 
			
		||||
 | 
			
		||||
				child.material = new THREE.MeshLambertMaterial({
 | 
			
		||||
					color: (child.material as THREE.MeshStandardMaterial).color,
 | 
			
		||||
					map: (child.material as THREE.MeshStandardMaterial).map,
 | 
			
		||||
					name: (child.material as THREE.MeshStandardMaterial).name,
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				// 異方性フィルタリング
 | 
			
		||||
				if ((child.material as THREE.MeshLambertMaterial).map && this.graphicsQuality !== 'cheep') {
 | 
			
		||||
					(child.material as THREE.MeshLambertMaterial).map.minFilter = THREE.LinearMipMapLinearFilter;
 | 
			
		||||
					(child.material as THREE.MeshLambertMaterial).map.magFilter = THREE.LinearMipMapLinearFilter;
 | 
			
		||||
					(child.material as THREE.MeshLambertMaterial).map.anisotropy = 8;
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			gltf.scene.position.set(0, 0, 0);
 | 
			
		||||
 | 
			
		||||
			this.scene.add(gltf.scene);
 | 
			
		||||
			this.roomObj = gltf.scene;
 | 
			
		||||
			if (this.roomInfo.roomType === 'default') {
 | 
			
		||||
				this.applyCarpetColor();
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	private loadFurniture(furniture: Furniture) {
 | 
			
		||||
		const def = furnitureDefs.find(d => d.id === furniture.type);
 | 
			
		||||
		return new Promise<GLTF>((res, rej) => {
 | 
			
		||||
			const loader = new GLTFLoader();
 | 
			
		||||
			loader.load(`/assets/room/furnitures/${furniture.type}/${furniture.type}.glb`, gltf => {
 | 
			
		||||
				const model = gltf.scene;
 | 
			
		||||
 | 
			
		||||
				// Load animation
 | 
			
		||||
				if (gltf.animations.length > 0) {
 | 
			
		||||
					const mixer = new THREE.AnimationMixer(model);
 | 
			
		||||
					this.mixers.push(mixer);
 | 
			
		||||
					for (const clip of gltf.animations) {
 | 
			
		||||
						mixer.clipAction(clip).play();
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				model.name = furniture.id;
 | 
			
		||||
				model.position.x = furniture.position.x;
 | 
			
		||||
				model.position.y = furniture.position.y;
 | 
			
		||||
				model.position.z = furniture.position.z;
 | 
			
		||||
				model.rotation.x = furniture.rotation.x;
 | 
			
		||||
				model.rotation.y = furniture.rotation.y;
 | 
			
		||||
				model.rotation.z = furniture.rotation.z;
 | 
			
		||||
 | 
			
		||||
				model.traverse(child => {
 | 
			
		||||
					if (!(child instanceof THREE.Mesh)) return;
 | 
			
		||||
					child.castShadow = this.enableShadow;
 | 
			
		||||
					child.receiveShadow = this.enableShadow;
 | 
			
		||||
					(child.material as THREE.MeshStandardMaterial).metalness = 0;
 | 
			
		||||
 | 
			
		||||
					// 異方性フィルタリング
 | 
			
		||||
					if ((child.material as THREE.MeshStandardMaterial).map && this.graphicsQuality !== 'cheep') {
 | 
			
		||||
						(child.material as THREE.MeshStandardMaterial).map.minFilter = THREE.LinearMipMapLinearFilter;
 | 
			
		||||
						(child.material as THREE.MeshStandardMaterial).map.magFilter = THREE.LinearMipMapLinearFilter;
 | 
			
		||||
						(child.material as THREE.MeshStandardMaterial).map.anisotropy = 8;
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				if (def.color) { // カスタムカラー
 | 
			
		||||
					this.applyCustomColor(model);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (def.texture) { // カスタムテクスチャ
 | 
			
		||||
					this.applyCustomTexture(model);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				res(gltf);
 | 
			
		||||
			}, null, rej);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	private applyCarpetColor() {
 | 
			
		||||
		this.roomObj.traverse(child => {
 | 
			
		||||
			if (!(child instanceof THREE.Mesh)) return;
 | 
			
		||||
			if (child.material &&
 | 
			
		||||
				(child.material as THREE.MeshStandardMaterial).name &&
 | 
			
		||||
				(child.material as THREE.MeshStandardMaterial).name === 'Carpet'
 | 
			
		||||
			) {
 | 
			
		||||
				const colorHex = parseInt(this.roomInfo.carpetColor.substr(1), 16);
 | 
			
		||||
				(child.material as THREE.MeshStandardMaterial).color.setHex(colorHex);
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	private applyCustomColor(model: THREE.Object3D) {
 | 
			
		||||
		const furniture = this.furnitures.find(furniture => furniture.id === model.name);
 | 
			
		||||
		const def = furnitureDefs.find(d => d.id === furniture.type);
 | 
			
		||||
		if (def.color == null) return;
 | 
			
		||||
		model.traverse(child => {
 | 
			
		||||
			if (!(child instanceof THREE.Mesh)) return;
 | 
			
		||||
			for (const t of Object.keys(def.color)) {
 | 
			
		||||
				if (!child.material ||
 | 
			
		||||
					!(child.material as THREE.MeshStandardMaterial).name ||
 | 
			
		||||
					(child.material as THREE.MeshStandardMaterial).name !== t
 | 
			
		||||
				) continue;
 | 
			
		||||
 | 
			
		||||
				const prop = def.color[t];
 | 
			
		||||
				const val = furniture.props ? furniture.props[prop] : undefined;
 | 
			
		||||
 | 
			
		||||
				if (val == null) continue;
 | 
			
		||||
 | 
			
		||||
				const colorHex = parseInt(val.substr(1), 16);
 | 
			
		||||
				(child.material as THREE.MeshStandardMaterial).color.setHex(colorHex);
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	private applyCustomTexture(model: THREE.Object3D) {
 | 
			
		||||
		const furniture = this.furnitures.find(furniture => furniture.id === model.name);
 | 
			
		||||
		const def = furnitureDefs.find(d => d.id === furniture.type);
 | 
			
		||||
		if (def.texture == null) return;
 | 
			
		||||
 | 
			
		||||
		model.traverse(child => {
 | 
			
		||||
			if (!(child instanceof THREE.Mesh)) return;
 | 
			
		||||
			for (const t of Object.keys(def.texture)) {
 | 
			
		||||
				if (child.name !== t) continue;
 | 
			
		||||
 | 
			
		||||
				const prop = def.texture[t].prop;
 | 
			
		||||
				const val = furniture.props ? furniture.props[prop] : undefined;
 | 
			
		||||
 | 
			
		||||
				if (val == null) continue;
 | 
			
		||||
 | 
			
		||||
				const canvas = document.createElement('canvas');
 | 
			
		||||
				canvas.height = 1024;
 | 
			
		||||
				canvas.width = 1024;
 | 
			
		||||
 | 
			
		||||
				child.material = new THREE.MeshLambertMaterial({
 | 
			
		||||
					emissive: 0x111111,
 | 
			
		||||
					side: THREE.DoubleSide,
 | 
			
		||||
					alphaTest: 0.5,
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				const img = new Image();
 | 
			
		||||
				img.crossOrigin = 'anonymous';
 | 
			
		||||
				img.onload = () => {
 | 
			
		||||
					const uvInfo = def.texture[t].uv;
 | 
			
		||||
 | 
			
		||||
					const ctx = canvas.getContext('2d');
 | 
			
		||||
					ctx.drawImage(img,
 | 
			
		||||
						0, 0, img.width, img.height,
 | 
			
		||||
						uvInfo.x, uvInfo.y, uvInfo.width, uvInfo.height);
 | 
			
		||||
 | 
			
		||||
					const texture = new THREE.Texture(canvas);
 | 
			
		||||
					texture.wrapS = THREE.RepeatWrapping;
 | 
			
		||||
					texture.wrapT = THREE.RepeatWrapping;
 | 
			
		||||
					texture.anisotropy = 16;
 | 
			
		||||
					texture.flipY = false;
 | 
			
		||||
 | 
			
		||||
					(child.material as THREE.MeshLambertMaterial).map = texture;
 | 
			
		||||
					(child.material as THREE.MeshLambertMaterial).needsUpdate = true;
 | 
			
		||||
					(child.material as THREE.MeshLambertMaterial).map.needsUpdate = true;
 | 
			
		||||
				};
 | 
			
		||||
				img.src = val;
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	private onmousemove(ev: MouseEvent) {
 | 
			
		||||
		if (this.isTransformMode) return;
 | 
			
		||||
 | 
			
		||||
		const rect = (ev.target as HTMLElement).getBoundingClientRect();
 | 
			
		||||
		const x = (((ev.clientX * window.devicePixelRatio) - rect.left) / this.canvas.width) * 2 - 1;
 | 
			
		||||
		const y = -(((ev.clientY * window.devicePixelRatio) - rect.top) / this.canvas.height) * 2 + 1;
 | 
			
		||||
		const pos = new THREE.Vector2(x, y);
 | 
			
		||||
 | 
			
		||||
		this.camera.updateMatrixWorld();
 | 
			
		||||
 | 
			
		||||
		const raycaster = new THREE.Raycaster();
 | 
			
		||||
		raycaster.setFromCamera(pos, this.camera);
 | 
			
		||||
 | 
			
		||||
		const intersects = raycaster.intersectObjects(this.objects, true);
 | 
			
		||||
 | 
			
		||||
		for (const object of this.objects) {
 | 
			
		||||
			if (this.isSelectedObject(object)) continue;
 | 
			
		||||
			object.traverse(child => {
 | 
			
		||||
				if (child instanceof THREE.Mesh) {
 | 
			
		||||
					(child.material as THREE.MeshStandardMaterial).emissive.setHex(0x000000);
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (intersects.length > 0) {
 | 
			
		||||
			const intersected = this.getRoot(intersects[0].object);
 | 
			
		||||
			if (this.isSelectedObject(intersected)) return;
 | 
			
		||||
			intersected.traverse(child => {
 | 
			
		||||
				if (child instanceof THREE.Mesh) {
 | 
			
		||||
					(child.material as THREE.MeshStandardMaterial).emissive.setHex(0x191919);
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	private onmousedown(ev: MouseEvent) {
 | 
			
		||||
		if (this.isTransformMode) return;
 | 
			
		||||
		if (ev.target !== this.canvas || ev.button !== 0) return;
 | 
			
		||||
 | 
			
		||||
		const rect = (ev.target as HTMLElement).getBoundingClientRect();
 | 
			
		||||
		const x = (((ev.clientX * window.devicePixelRatio) - rect.left) / this.canvas.width) * 2 - 1;
 | 
			
		||||
		const y = -(((ev.clientY * window.devicePixelRatio) - rect.top) / this.canvas.height) * 2 + 1;
 | 
			
		||||
		const pos = new THREE.Vector2(x, y);
 | 
			
		||||
 | 
			
		||||
		this.camera.updateMatrixWorld();
 | 
			
		||||
 | 
			
		||||
		const raycaster = new THREE.Raycaster();
 | 
			
		||||
		raycaster.setFromCamera(pos, this.camera);
 | 
			
		||||
 | 
			
		||||
		const intersects = raycaster.intersectObjects(this.objects, true);
 | 
			
		||||
 | 
			
		||||
		for (const object of this.objects) {
 | 
			
		||||
			object.traverse(child => {
 | 
			
		||||
				if (child instanceof THREE.Mesh) {
 | 
			
		||||
					(child.material as THREE.MeshStandardMaterial).emissive.setHex(0x000000);
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (intersects.length > 0) {
 | 
			
		||||
			const selectedObj = this.getRoot(intersects[0].object);
 | 
			
		||||
			this.selectFurniture(selectedObj);
 | 
			
		||||
		} else {
 | 
			
		||||
			this.selectedObject = null;
 | 
			
		||||
			this.onChangeSelect(null);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	private getRoot(obj: THREE.Object3D): THREE.Object3D {
 | 
			
		||||
		let found = false;
 | 
			
		||||
		let x = obj.parent;
 | 
			
		||||
		while (!found) {
 | 
			
		||||
			if (x.parent.parent == null) {
 | 
			
		||||
				found = true;
 | 
			
		||||
			} else {
 | 
			
		||||
				x = x.parent;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return x;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	private isSelectedObject(obj: THREE.Object3D): boolean {
 | 
			
		||||
		if (this.selectedObject == null) {
 | 
			
		||||
			return false;
 | 
			
		||||
		} else {
 | 
			
		||||
			return obj.name === this.selectedObject.name;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	private selectFurniture(obj: THREE.Object3D) {
 | 
			
		||||
		this.selectedObject = obj;
 | 
			
		||||
		this.onChangeSelect(obj);
 | 
			
		||||
		obj.traverse(child => {
 | 
			
		||||
			if (child instanceof THREE.Mesh) {
 | 
			
		||||
				(child.material as THREE.MeshStandardMaterial).emissive.setHex(0xff0000);
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 家具の移動/回転モードにします
 | 
			
		||||
	 * @param type 移動か回転か
 | 
			
		||||
	 */
 | 
			
		||||
	@autobind
 | 
			
		||||
	public enterTransformMode(type: 'translate' | 'rotate') {
 | 
			
		||||
		this.isTransformMode = true;
 | 
			
		||||
		this.furnitureControl.setMode(type);
 | 
			
		||||
		this.furnitureControl.attach(this.selectedObject);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 家具の移動/回転モードを終了します
 | 
			
		||||
	 */
 | 
			
		||||
	@autobind
 | 
			
		||||
	public exitTransformMode() {
 | 
			
		||||
		this.isTransformMode = false;
 | 
			
		||||
		this.furnitureControl.detach();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 家具プロパティを更新します
 | 
			
		||||
	 * @param key プロパティ名
 | 
			
		||||
	 * @param value 値
 | 
			
		||||
	 */
 | 
			
		||||
	@autobind
 | 
			
		||||
	public updateProp(key: string, value: any) {
 | 
			
		||||
		const furniture = this.furnitures.find(furniture => furniture.id === this.selectedObject.name);
 | 
			
		||||
		if (furniture.props == null) furniture.props = {};
 | 
			
		||||
		furniture.props[key] = value;
 | 
			
		||||
		this.applyCustomColor(this.selectedObject);
 | 
			
		||||
		this.applyCustomTexture(this.selectedObject);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 部屋に家具を追加します
 | 
			
		||||
	 * @param type 家具の種類
 | 
			
		||||
	 */
 | 
			
		||||
	@autobind
 | 
			
		||||
	public addFurniture(type: string) {
 | 
			
		||||
		const furniture = {
 | 
			
		||||
			id: uuid(),
 | 
			
		||||
			type: type,
 | 
			
		||||
			position: {
 | 
			
		||||
				x: 0,
 | 
			
		||||
				y: 0,
 | 
			
		||||
				z: 0,
 | 
			
		||||
			},
 | 
			
		||||
			rotation: {
 | 
			
		||||
				x: 0,
 | 
			
		||||
				y: 0,
 | 
			
		||||
				z: 0,
 | 
			
		||||
			},
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		this.furnitures.push(furniture);
 | 
			
		||||
 | 
			
		||||
		this.loadFurniture(furniture).then(obj => {
 | 
			
		||||
			this.scene.add(obj.scene);
 | 
			
		||||
			this.objects.push(obj.scene);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 現在選択されている家具を部屋から削除します
 | 
			
		||||
	 */
 | 
			
		||||
	@autobind
 | 
			
		||||
	public removeFurniture() {
 | 
			
		||||
		this.exitTransformMode();
 | 
			
		||||
		const obj = this.selectedObject;
 | 
			
		||||
		this.scene.remove(obj);
 | 
			
		||||
		this.objects = this.objects.filter(object => object.name !== obj.name);
 | 
			
		||||
		this.furnitures = this.furnitures.filter(furniture => furniture.id !== obj.name);
 | 
			
		||||
		this.selectedObject = null;
 | 
			
		||||
		this.onChangeSelect(null);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 全ての家具を部屋から削除します
 | 
			
		||||
	 */
 | 
			
		||||
	@autobind
 | 
			
		||||
	public removeAllFurnitures() {
 | 
			
		||||
		this.exitTransformMode();
 | 
			
		||||
		for (const obj of this.objects) {
 | 
			
		||||
			this.scene.remove(obj);
 | 
			
		||||
		}
 | 
			
		||||
		this.objects = [];
 | 
			
		||||
		this.furnitures = [];
 | 
			
		||||
		this.selectedObject = null;
 | 
			
		||||
		this.onChangeSelect(null);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 部屋の床の色を変更します
 | 
			
		||||
	 * @param color 色
 | 
			
		||||
	 */
 | 
			
		||||
	@autobind
 | 
			
		||||
	public updateCarpetColor(color: string) {
 | 
			
		||||
		this.roomInfo.carpetColor = color;
 | 
			
		||||
		this.applyCarpetColor();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 部屋の種類を変更します
 | 
			
		||||
	 * @param type 種類
 | 
			
		||||
	 */
 | 
			
		||||
	@autobind
 | 
			
		||||
	public changeRoomType(type: string) {
 | 
			
		||||
		this.roomInfo.roomType = type;
 | 
			
		||||
		this.scene.remove(this.roomObj);
 | 
			
		||||
		this.loadRoom();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 部屋データを取得します
 | 
			
		||||
	 */
 | 
			
		||||
	@autobind
 | 
			
		||||
	public getRoomInfo() {
 | 
			
		||||
		for (const obj of this.objects) {
 | 
			
		||||
			const furniture = this.furnitures.find(f => f.id === obj.name);
 | 
			
		||||
			furniture.position.x = obj.position.x;
 | 
			
		||||
			furniture.position.y = obj.position.y;
 | 
			
		||||
			furniture.position.z = obj.position.z;
 | 
			
		||||
			furniture.rotation.x = obj.rotation.x;
 | 
			
		||||
			furniture.rotation.y = obj.rotation.y;
 | 
			
		||||
			furniture.rotation.z = obj.rotation.z;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return this.roomInfo;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 選択されている家具を取得します
 | 
			
		||||
	 */
 | 
			
		||||
	@autobind
 | 
			
		||||
	public getSelectedObject() {
 | 
			
		||||
		return this.selectedObject;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	public findFurnitureById(id: string) {
 | 
			
		||||
		return this.furnitures.find(furniture => furniture.id === id);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * レンダリングを終了します
 | 
			
		||||
	 */
 | 
			
		||||
	@autobind
 | 
			
		||||
	public destroy() {
 | 
			
		||||
		// Stop render loop
 | 
			
		||||
		window.cancelAnimationFrame(this.renderFrameRequestId);
 | 
			
		||||
 | 
			
		||||
		this.controls.dispose();
 | 
			
		||||
		this.scene.dispose();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,16 +1,16 @@
 | 
			
		||||
<template>
 | 
			
		||||
<prism :inline="inline" :language="lang || 'js'">{{ code }}</prism>
 | 
			
		||||
<x-prism :inline="inline" :language="prismLang">{{ code }}</x-prism>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import 'prismjs';
 | 
			
		||||
import 'prismjs/themes/prism-okaidia.css';
 | 
			
		||||
import Prism from 'vue-prism-component';
 | 
			
		||||
import XPrism from 'vue-prism-component';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		Prism
 | 
			
		||||
		XPrism
 | 
			
		||||
	},
 | 
			
		||||
	props: {
 | 
			
		||||
		code: {
 | 
			
		||||
@@ -25,6 +25,12 @@ export default Vue.extend({
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		prismLang() {
 | 
			
		||||
			return Prism.languages[this.lang] ? this.lang : 'js';
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -98,7 +98,7 @@ export default Vue.extend({
 | 
			
		||||
		return {
 | 
			
		||||
			inputValue: this.input && this.input.default ? this.input.default : null,
 | 
			
		||||
			userInputValue: null,
 | 
			
		||||
			selectedValue: this.select ? this.select.items ? this.select.items[0].value : this.select.groupedItems[0].items[0].value : null,
 | 
			
		||||
			selectedValue: this.select ? this.select.default ? this.select.default : this.select.items ? this.select.items[0].value : this.select.groupedItems[0].items[0].value : null,
 | 
			
		||||
			canOk: true,
 | 
			
		||||
			faTimesCircle, faQuestionCircle
 | 
			
		||||
		};
 | 
			
		||||
 
 | 
			
		||||
@@ -57,7 +57,8 @@ export default Vue.extend({
 | 
			
		||||
		},
 | 
			
		||||
		fit: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: true
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: 'cover'
 | 
			
		||||
		},
 | 
			
		||||
		detail: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,8 @@ export default Vue.extend({
 | 
			
		||||
	mounted() {
 | 
			
		||||
		//#region for Safari bug
 | 
			
		||||
		if (this.$refs.grid) {
 | 
			
		||||
			this.$refs.grid.style.height = this.$refs.grid.clientHeight ? `${this.$refs.grid.clientHeight}px` : '128px';
 | 
			
		||||
			this.$refs.grid.style.height = this.$refs.grid.clientHeight ? `${this.$refs.grid.clientHeight}px`
 | 
			
		||||
				: this.$store.state.device.inDeckMode ? '128px' : this.$root.isMobile ? '173px' : '287px';
 | 
			
		||||
		}
 | 
			
		||||
		//#endregion
 | 
			
		||||
	},
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,17 @@
 | 
			
		||||
<template>
 | 
			
		||||
<router-link class="ldlomzub" :to="`/${ canonical }`" v-user-preview="canonical">
 | 
			
		||||
<router-link class="ldlomzub" :to="url" v-user-preview="canonical" v-if="url.startsWith('/')">
 | 
			
		||||
	<span class="me" v-if="isMe">{{ $t('@.you') }}</span>
 | 
			
		||||
	<span class="main">
 | 
			
		||||
		<span class="username">@{{ username }}</span>
 | 
			
		||||
		<span class="host" :class="{ fade: $store.state.settings.contrastedAcct }" v-if="(host != localHost) || $store.state.settings.showFullAcct">@{{ toUnicode(host) }}</span>
 | 
			
		||||
	</span>
 | 
			
		||||
</router-link>
 | 
			
		||||
<a class="ldlomzub" :href="url" target="_blank" rel="noopener" v-else>
 | 
			
		||||
	<span class="main">
 | 
			
		||||
		<span class="username">@{{ username }}</span>
 | 
			
		||||
		<span class="host" :class="{ fade: $store.state.settings.contrastedAcct }">@{{ toUnicode(host) }}</span>
 | 
			
		||||
	</span>
 | 
			
		||||
</a>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
@@ -32,6 +38,15 @@ export default Vue.extend({
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	computed: {
 | 
			
		||||
		url(): string {
 | 
			
		||||
			switch (this.host) {
 | 
			
		||||
				case 'twitter.com':
 | 
			
		||||
				case 'github.com':
 | 
			
		||||
					return `https://${this.host}/${this.username}`;
 | 
			
		||||
				default:
 | 
			
		||||
					return `/${this.canonical}`;
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		canonical(): string {
 | 
			
		||||
			return this.host === localHost ? `@${this.username}` : `@${this.username}@${toUnicode(this.host)}`;
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
@@ -139,6 +139,7 @@ export default Vue.extend({
 | 
			
		||||
					cursor pointer
 | 
			
		||||
 | 
			
		||||
			> .content
 | 
			
		||||
				max-width 100%
 | 
			
		||||
 | 
			
		||||
				> .is-deleted
 | 
			
		||||
					display block
 | 
			
		||||
@@ -155,6 +156,7 @@ export default Vue.extend({
 | 
			
		||||
					padding 8px 16px
 | 
			
		||||
					overflow hidden
 | 
			
		||||
					overflow-wrap break-word
 | 
			
		||||
					word-break break-word
 | 
			
		||||
					font-size 1em
 | 
			
		||||
					color rgba(#000, 0.8)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,7 @@ export default Vue.extend({
 | 
			
		||||
		border-radius 4px
 | 
			
		||||
 | 
			
		||||
	>>> .quote
 | 
			
		||||
		display block
 | 
			
		||||
		margin 8px
 | 
			
		||||
		padding 6px 0 6px 12px
 | 
			
		||||
		color var(--mfmQuote)
 | 
			
		||||
 
 | 
			
		||||
@@ -22,66 +22,95 @@ export default Vue.extend({
 | 
			
		||||
	},
 | 
			
		||||
	computed: {
 | 
			
		||||
		items(): any[] {
 | 
			
		||||
			return [{
 | 
			
		||||
				icon: 'at',
 | 
			
		||||
				text: this.$t('mention'),
 | 
			
		||||
				action: this.mention
 | 
			
		||||
			}, null, {
 | 
			
		||||
				icon: 'info-circle',
 | 
			
		||||
				text: this.$t('detail'),
 | 
			
		||||
				action: this.detail
 | 
			
		||||
			}, {
 | 
			
		||||
				icon: faCopy,
 | 
			
		||||
				text: this.$t('copy-content'),
 | 
			
		||||
				action: this.copyContent
 | 
			
		||||
			}, {
 | 
			
		||||
				icon: 'link',
 | 
			
		||||
				text: this.$t('copy-link'),
 | 
			
		||||
				action: this.copyLink
 | 
			
		||||
			}, this.note.uri ? {
 | 
			
		||||
				icon: 'external-link-square-alt',
 | 
			
		||||
				text: this.$t('remote'),
 | 
			
		||||
				action: () => {
 | 
			
		||||
					window.open(this.note.uri, '_blank');
 | 
			
		||||
				}
 | 
			
		||||
			} : undefined,
 | 
			
		||||
			null,
 | 
			
		||||
			this.isFavorited ? {
 | 
			
		||||
				icon: 'star',
 | 
			
		||||
				text: this.$t('unfavorite'),
 | 
			
		||||
				action: () => this.toggleFavorite(false)
 | 
			
		||||
			} : {
 | 
			
		||||
				icon: 'star',
 | 
			
		||||
				text: this.$t('favorite'),
 | 
			
		||||
				action: () => this.toggleFavorite(true)
 | 
			
		||||
			},
 | 
			
		||||
			this.note.userId != this.$store.state.i.id ? this.isWatching ? {
 | 
			
		||||
				icon: faEyeSlash,
 | 
			
		||||
				text: this.$t('unwatch'),
 | 
			
		||||
				action: () => this.toggleWatch(false)
 | 
			
		||||
			} : {
 | 
			
		||||
				icon: faEye,
 | 
			
		||||
				text: this.$t('watch'),
 | 
			
		||||
				action: () => this.toggleWatch(true)
 | 
			
		||||
			} : undefined,
 | 
			
		||||
			this.note.userId == this.$store.state.i.id ? (this.$store.state.i.pinnedNoteIds || []).includes(this.note.id) ? {
 | 
			
		||||
				icon: 'thumbtack',
 | 
			
		||||
				text: this.$t('unpin'),
 | 
			
		||||
				action: () => this.togglePin(false)
 | 
			
		||||
			} : {
 | 
			
		||||
				icon: 'thumbtack',
 | 
			
		||||
				text: this.$t('pin'),
 | 
			
		||||
				action: () => this.togglePin(true)
 | 
			
		||||
			} : undefined,
 | 
			
		||||
			...(this.note.userId == this.$store.state.i.id || this.$store.state.i.isAdmin || this.$store.state.i.isModerator ? [
 | 
			
		||||
				null, {
 | 
			
		||||
					icon: ['far', 'trash-alt'],
 | 
			
		||||
					text: this.$t('delete'),
 | 
			
		||||
					action: this.del
 | 
			
		||||
				}]
 | 
			
		||||
				: []
 | 
			
		||||
			)]
 | 
			
		||||
			.filter(x => x !== undefined)
 | 
			
		||||
			if (this.$store.getters.isSignedIn) {
 | 
			
		||||
				return [{
 | 
			
		||||
					icon: 'at',
 | 
			
		||||
					text: this.$t('mention'),
 | 
			
		||||
					action: this.mention
 | 
			
		||||
				}, null, {
 | 
			
		||||
					icon: 'info-circle',
 | 
			
		||||
					text: this.$t('detail'),
 | 
			
		||||
					action: this.detail
 | 
			
		||||
				}, {
 | 
			
		||||
					icon: faCopy,
 | 
			
		||||
					text: this.$t('copy-content'),
 | 
			
		||||
					action: this.copyContent
 | 
			
		||||
				}, {
 | 
			
		||||
					icon: 'link',
 | 
			
		||||
					text: this.$t('copy-link'),
 | 
			
		||||
					action: this.copyLink
 | 
			
		||||
				}, this.note.uri ? {
 | 
			
		||||
					icon: 'external-link-square-alt',
 | 
			
		||||
					text: this.$t('remote'),
 | 
			
		||||
					action: () => {
 | 
			
		||||
						window.open(this.note.uri, '_blank');
 | 
			
		||||
					}
 | 
			
		||||
				} : undefined,
 | 
			
		||||
				null,
 | 
			
		||||
				this.isFavorited ? {
 | 
			
		||||
					icon: 'star',
 | 
			
		||||
					text: this.$t('unfavorite'),
 | 
			
		||||
					action: () => this.toggleFavorite(false)
 | 
			
		||||
				} : {
 | 
			
		||||
					icon: 'star',
 | 
			
		||||
					text: this.$t('favorite'),
 | 
			
		||||
					action: () => this.toggleFavorite(true)
 | 
			
		||||
				},
 | 
			
		||||
				this.note.userId != this.$store.state.i.id ? this.isWatching ? {
 | 
			
		||||
					icon: faEyeSlash,
 | 
			
		||||
					text: this.$t('unwatch'),
 | 
			
		||||
					action: () => this.toggleWatch(false)
 | 
			
		||||
				} : {
 | 
			
		||||
					icon: faEye,
 | 
			
		||||
					text: this.$t('watch'),
 | 
			
		||||
					action: () => this.toggleWatch(true)
 | 
			
		||||
				} : undefined,
 | 
			
		||||
				this.note.userId == this.$store.state.i.id ? (this.$store.state.i.pinnedNoteIds || []).includes(this.note.id) ? {
 | 
			
		||||
					icon: 'thumbtack',
 | 
			
		||||
					text: this.$t('unpin'),
 | 
			
		||||
					action: () => this.togglePin(false)
 | 
			
		||||
				} : {
 | 
			
		||||
					icon: 'thumbtack',
 | 
			
		||||
					text: this.$t('pin'),
 | 
			
		||||
					action: () => this.togglePin(true)
 | 
			
		||||
				} : undefined,
 | 
			
		||||
				...(this.note.userId == this.$store.state.i.id || this.$store.state.i.isAdmin || this.$store.state.i.isModerator ? [
 | 
			
		||||
					null,
 | 
			
		||||
					this.note.userId == this.$store.state.i.id ? {
 | 
			
		||||
						icon: 'undo-alt',
 | 
			
		||||
						text: this.$t('delete-and-edit'),
 | 
			
		||||
						action: this.deleteAndEdit
 | 
			
		||||
					} : undefined,
 | 
			
		||||
					{
 | 
			
		||||
						icon: ['far', 'trash-alt'],
 | 
			
		||||
						text: this.$t('delete'),
 | 
			
		||||
						action: this.del
 | 
			
		||||
					}]
 | 
			
		||||
					: []
 | 
			
		||||
				)]
 | 
			
		||||
				.filter(x => x !== undefined);
 | 
			
		||||
			} else {
 | 
			
		||||
				return [{
 | 
			
		||||
					icon: 'info-circle',
 | 
			
		||||
					text: this.$t('detail'),
 | 
			
		||||
					action: this.detail
 | 
			
		||||
				}, {
 | 
			
		||||
					icon: faCopy,
 | 
			
		||||
					text: this.$t('copy-content'),
 | 
			
		||||
					action: this.copyContent
 | 
			
		||||
				}, {
 | 
			
		||||
					icon: 'link',
 | 
			
		||||
					text: this.$t('copy-link'),
 | 
			
		||||
					action: this.copyLink
 | 
			
		||||
				}, this.note.uri ? {
 | 
			
		||||
					icon: 'external-link-square-alt',
 | 
			
		||||
					text: this.$t('remote'),
 | 
			
		||||
					action: () => {
 | 
			
		||||
						window.open(this.note.uri, '_blank');
 | 
			
		||||
					}
 | 
			
		||||
				} : undefined]
 | 
			
		||||
				.filter(x => x !== undefined);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -154,6 +183,25 @@ export default Vue.extend({
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		deleteAndEdit() {
 | 
			
		||||
			this.$root.dialog({
 | 
			
		||||
				type: 'warning',
 | 
			
		||||
				text: this.$t('delete-and-edit-confirm'),
 | 
			
		||||
				showCancelButton: true
 | 
			
		||||
			}).then(({ canceled }) => {
 | 
			
		||||
				if (canceled) return;
 | 
			
		||||
				this.$root.api('notes/delete', {
 | 
			
		||||
					noteId: this.note.id
 | 
			
		||||
				}).then(() => {
 | 
			
		||||
					this.destroyDom();
 | 
			
		||||
				});
 | 
			
		||||
				this.$post({
 | 
			
		||||
					initialNote: this.note,
 | 
			
		||||
					reply: this.note.reply,
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		toggleFavorite(favorite: boolean) {
 | 
			
		||||
			this.$root.api(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', {
 | 
			
		||||
				noteId: this.note.id
 | 
			
		||||
@@ -179,6 +227,7 @@ export default Vue.extend({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		closed() {
 | 
			
		||||
			this.$emit('closed');
 | 
			
		||||
			this.$nextTick(() => {
 | 
			
		||||
				this.destroyDom();
 | 
			
		||||
			});
 | 
			
		||||
 
 | 
			
		||||
@@ -16,10 +16,11 @@ import XIf from './page.if.vue';
 | 
			
		||||
import XTextarea from './page.textarea.vue';
 | 
			
		||||
import XPost from './page.post.vue';
 | 
			
		||||
import XCounter from './page.counter.vue';
 | 
			
		||||
import XRadioButton from './page.radio-button.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XText, XSection, XImage, XButton, XNumberInput, XTextInput, XTextareaInput, XTextarea, XPost, XSwitch, XIf, XCounter
 | 
			
		||||
		XText, XSection, XImage, XButton, XNumberInput, XTextInput, XTextareaInput, XTextarea, XPost, XSwitch, XIf, XCounter, XRadioButton
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,37 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div>
 | 
			
		||||
	<div>{{ script.interpolate(value.title) }}</div>
 | 
			
		||||
	<ui-radio v-for="x in value.values" v-model="v" :value="x" :key="x">{{ x }}</ui-radio>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: {
 | 
			
		||||
		value: {
 | 
			
		||||
			required: true
 | 
			
		||||
		},
 | 
			
		||||
		script: {
 | 
			
		||||
			required: true
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			v: this.value.default,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	watch: {
 | 
			
		||||
		v() {
 | 
			
		||||
			this.script.aiScript.updatePageVar(this.value.name, this.v);
 | 
			
		||||
			this.script.eval();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
</style>
 | 
			
		||||
@@ -26,13 +26,19 @@
 | 
			
		||||
				<option value="after">{{ $t('after') }}</option>
 | 
			
		||||
			</ui-select>
 | 
			
		||||
			<section v-if="expiration === 'at'">
 | 
			
		||||
				<ui-input v-model="atDate" type="date">{{ $t('deadline-date') }}</ui-input>
 | 
			
		||||
				<ui-input v-model="atTime" type="time">{{ $t('deadline-time') }}</ui-input>
 | 
			
		||||
				<ui-input v-model="atDate" type="date">
 | 
			
		||||
					<template #title>{{ $t('deadline-date') }}</template>
 | 
			
		||||
				</ui-input>
 | 
			
		||||
				<ui-input v-model="atTime" type="time">
 | 
			
		||||
					<template #title>{{ $t('deadline-time') }}</template>
 | 
			
		||||
				</ui-input>
 | 
			
		||||
			</section>
 | 
			
		||||
			<section v-if="expiration === 'after'">
 | 
			
		||||
				<ui-input v-model="after" type="number">{{ $t('interval') }}</ui-input>
 | 
			
		||||
				<ui-input v-model="after" type="number">
 | 
			
		||||
					<template #title>{{ $t('interval') }}</template>
 | 
			
		||||
				</ui-input>
 | 
			
		||||
				<ui-select v-model="unit">
 | 
			
		||||
					<template #label>{{ $t('unit') }}</template>
 | 
			
		||||
					<template #title>{{ $t('unit') }}</template>
 | 
			
		||||
					<option value="second">{{ $t('second') }}</option>
 | 
			
		||||
					<option value="minute">{{ $t('minute') }}</option>
 | 
			
		||||
					<option value="hour">{{ $t('hour') }}</option>
 | 
			
		||||
@@ -46,9 +52,11 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import * as moment from 'moment';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import { erase } from '../../../../../prelude/array';
 | 
			
		||||
import { addTimespan } from '../../../../../prelude/time';
 | 
			
		||||
import { formatDateTimeString } from '../../../../../misc/format-time-string';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n('common/views/components/poll-editor.vue'),
 | 
			
		||||
	data() {
 | 
			
		||||
@@ -56,7 +64,7 @@ export default Vue.extend({
 | 
			
		||||
			choices: ['', ''],
 | 
			
		||||
			multiple: false,
 | 
			
		||||
			expiration: 'infinite',
 | 
			
		||||
			atDate: moment().add(1, 'day').toISOString().split('T')[0],
 | 
			
		||||
			atDate: formatDateTimeString(addTimespan(new Date(), 1, 'days'), 'yyyy-MM-dd'),
 | 
			
		||||
			atTime: '00:00',
 | 
			
		||||
			after: 0,
 | 
			
		||||
			unit: 'second'
 | 
			
		||||
@@ -89,7 +97,7 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
		get() {
 | 
			
		||||
			const at = () => {
 | 
			
		||||
				return moment(`${this.atDate} ${this.atTime}`).valueOf();
 | 
			
		||||
				return new Date(`${this.atDate} ${this.atTime}`).getTime();
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			const after = () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ export default Vue.extend({
 | 
			
		||||
			required: true
 | 
			
		||||
		},
 | 
			
		||||
		detachMediaFn: {
 | 
			
		||||
			type: Object,
 | 
			
		||||
			type: Function,
 | 
			
		||||
			required: false
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 
 | 
			
		||||
@@ -276,6 +276,7 @@ export default Vue.extend({
 | 
			
		||||
			font-size 14px
 | 
			
		||||
			color var(--popupFg)
 | 
			
		||||
			border-bottom solid var(--lineWidth) var(--faceDivider)
 | 
			
		||||
			line-height 20px
 | 
			
		||||
 | 
			
		||||
		> .buttons
 | 
			
		||||
			padding 4px 4px 8px 4px
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,122 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<transition name="zoom-in-top">
 | 
			
		||||
		<div class="buebdbiu" ref="popover" v-if="show">
 | 
			
		||||
			<i18n path="few-users" v-if="users.length <= 10">
 | 
			
		||||
				<span slot="users">
 | 
			
		||||
					<b v-for="u in users" :key="u.id" style="margin-right: 12px;">
 | 
			
		||||
						<mk-avatar :user="u" style="width: 24px; height: 24px; margin-right: 2px;"/>
 | 
			
		||||
						<mk-user-name :user="u" :nowrap="false" style="line-height: 24px;"/>
 | 
			
		||||
					</b>
 | 
			
		||||
				</span>
 | 
			
		||||
				<mk-reaction-icon slot="reaction" :reaction="reaction" ref="icon" />
 | 
			
		||||
			</i18n>
 | 
			
		||||
			<i18n path="many-users" v-if="10 < users.length">
 | 
			
		||||
				<span slot="users">
 | 
			
		||||
					<b v-for="u in users" :key="u.id" style="margin-right: 12px;">
 | 
			
		||||
						<mk-avatar :user="u" style="width: 24px; height: 24px; margin-right: 2px;"/>
 | 
			
		||||
						<mk-user-name :user="u" :nowrap="false" style="line-height: 24px;"/>
 | 
			
		||||
					</b>
 | 
			
		||||
				</span>
 | 
			
		||||
				<span slot="ommited">{{ count - 10 }}</span>
 | 
			
		||||
				<mk-reaction-icon slot="reaction" :reaction="reaction" ref="icon" />
 | 
			
		||||
			</i18n>
 | 
			
		||||
		</div>
 | 
			
		||||
	</transition>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n('common/views/components/reactions-viewer.details.vue'),
 | 
			
		||||
	props: {
 | 
			
		||||
		reaction: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: true,
 | 
			
		||||
		},
 | 
			
		||||
		users: {
 | 
			
		||||
			type: Array,
 | 
			
		||||
			required: true,
 | 
			
		||||
		},
 | 
			
		||||
		count: {
 | 
			
		||||
			type: Number,
 | 
			
		||||
			required: true,
 | 
			
		||||
		},
 | 
			
		||||
		source: {
 | 
			
		||||
			required: true,
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			show: false
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.show = true;
 | 
			
		||||
 | 
			
		||||
		this.$nextTick(() => {
 | 
			
		||||
			const popover = this.$refs.popover as any;
 | 
			
		||||
 | 
			
		||||
			if (this.source == null) {
 | 
			
		||||
				this.destroyDom();
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
			const rect = this.source.getBoundingClientRect();
 | 
			
		||||
 | 
			
		||||
			const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
 | 
			
		||||
			const y = rect.top + window.pageYOffset + this.source.offsetHeight;
 | 
			
		||||
			popover.style.left = (x - 28) + 'px';
 | 
			
		||||
			popover.style.top = (y + 16) + 'px';
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
	methods: {
 | 
			
		||||
		close() {
 | 
			
		||||
			this.show = false;
 | 
			
		||||
			setTimeout(this.destroyDom, 300);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.buebdbiu
 | 
			
		||||
	$bgcolor = var(--popupBg)
 | 
			
		||||
	z-index 10000
 | 
			
		||||
	display block
 | 
			
		||||
	position absolute
 | 
			
		||||
	max-width 240px
 | 
			
		||||
	font-size 0.8em
 | 
			
		||||
	padding 6px 8px
 | 
			
		||||
	background $bgcolor
 | 
			
		||||
	text-align center
 | 
			
		||||
	color var(--text)
 | 
			
		||||
	border-radius 4px
 | 
			
		||||
	box-shadow 0 var(--lineWidth) 4px rgba(#000, 0.25)
 | 
			
		||||
	pointer-events none
 | 
			
		||||
	transform-origin center -16px
 | 
			
		||||
 | 
			
		||||
	&:before
 | 
			
		||||
		content ""
 | 
			
		||||
		pointer-events none
 | 
			
		||||
		display block
 | 
			
		||||
		position absolute
 | 
			
		||||
		top -28px
 | 
			
		||||
		left 12px
 | 
			
		||||
		border-top solid 14px transparent
 | 
			
		||||
		border-right solid 14px transparent
 | 
			
		||||
		border-bottom solid 14px rgba(#000, 0.1)
 | 
			
		||||
		border-left solid 14px transparent
 | 
			
		||||
 | 
			
		||||
	&:after
 | 
			
		||||
		content ""
 | 
			
		||||
		pointer-events none
 | 
			
		||||
		display block
 | 
			
		||||
		position absolute
 | 
			
		||||
		top -27px
 | 
			
		||||
		left 12px
 | 
			
		||||
		border-top solid 14px transparent
 | 
			
		||||
		border-right solid 14px transparent
 | 
			
		||||
		border-bottom solid 14px $bgcolor
 | 
			
		||||
		border-left solid 14px transparent
 | 
			
		||||
</style>
 | 
			
		||||
@@ -5,6 +5,9 @@
 | 
			
		||||
	@click="toggleReaction(reaction)"
 | 
			
		||||
	v-if="count > 0"
 | 
			
		||||
	v-particle="!isMe"
 | 
			
		||||
	@mouseover="onMouseover"
 | 
			
		||||
	@mouseleave="onMouseleave"
 | 
			
		||||
	ref="reaction"
 | 
			
		||||
>
 | 
			
		||||
	<mk-reaction-icon :reaction="reaction" ref="icon"/>
 | 
			
		||||
	<span>{{ count }}</span>
 | 
			
		||||
@@ -15,6 +18,7 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import Icon from './reaction-icon.vue';
 | 
			
		||||
import anime from 'animejs';
 | 
			
		||||
import XDetails from './reactions-viewer.details.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: {
 | 
			
		||||
@@ -26,6 +30,10 @@ export default Vue.extend({
 | 
			
		||||
			type: Number,
 | 
			
		||||
			required: true,
 | 
			
		||||
		},
 | 
			
		||||
		isInitial: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: true,
 | 
			
		||||
		},
 | 
			
		||||
		note: {
 | 
			
		||||
			type: Object,
 | 
			
		||||
			required: true,
 | 
			
		||||
@@ -36,14 +44,25 @@ export default Vue.extend({
 | 
			
		||||
			default: true,
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			details: null,
 | 
			
		||||
			detailsTimeoutId: null,
 | 
			
		||||
			isHovering: false
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	computed: {
 | 
			
		||||
		isMe(): boolean {
 | 
			
		||||
			return this.$store.getters.isSignedIn && this.$store.state.i.id === this.note.userId;
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		if (!this.isInitial) this.anime();
 | 
			
		||||
	},
 | 
			
		||||
	watch: {
 | 
			
		||||
		count() {
 | 
			
		||||
			this.anime();
 | 
			
		||||
		count(newCount, oldCount) {
 | 
			
		||||
			if (oldCount < newCount) this.anime();
 | 
			
		||||
			if (this.details != null) this.openDetails();
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
@@ -70,11 +89,49 @@ export default Vue.extend({
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		onMouseover() {
 | 
			
		||||
			this.isHovering = true;
 | 
			
		||||
			this.detailsTimeoutId = setTimeout(this.openDetails, 300);
 | 
			
		||||
		},
 | 
			
		||||
		onMouseleave() {
 | 
			
		||||
			this.isHovering = false;
 | 
			
		||||
			clearTimeout(this.detailsTimeoutId);
 | 
			
		||||
			this.closeDetails();
 | 
			
		||||
		},
 | 
			
		||||
		openDetails() {
 | 
			
		||||
			if (this.$root.isMobile) return;
 | 
			
		||||
			this.$root.api('notes/reactions', {
 | 
			
		||||
				noteId: this.note.id,
 | 
			
		||||
				type: this.reaction,
 | 
			
		||||
				limit: 11
 | 
			
		||||
			}).then((reactions: any[]) => {
 | 
			
		||||
				const users = reactions
 | 
			
		||||
					.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())
 | 
			
		||||
					.map(x => x.user);
 | 
			
		||||
 | 
			
		||||
				this.closeDetails();
 | 
			
		||||
				if (!this.isHovering) return;
 | 
			
		||||
				this.details = this.$root.new(XDetails, {
 | 
			
		||||
					reaction: this.reaction,
 | 
			
		||||
					users,
 | 
			
		||||
					count: this.count,
 | 
			
		||||
					source: this.$refs.reaction
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		closeDetails() {
 | 
			
		||||
			if (this.details != null) {
 | 
			
		||||
				this.details.close();
 | 
			
		||||
				this.details = null;
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		anime() {
 | 
			
		||||
			if (this.$store.state.device.reduceMotion) return;
 | 
			
		||||
			if (document.hidden) return;
 | 
			
		||||
 | 
			
		||||
			this.$nextTick(() => {
 | 
			
		||||
				if (this.$refs.icon == null) return;
 | 
			
		||||
 | 
			
		||||
				const rect = this.$refs.icon.$el.getBoundingClientRect();
 | 
			
		||||
 | 
			
		||||
				const x = rect.left;
 | 
			
		||||
@@ -120,6 +177,14 @@ export default Vue.extend({
 | 
			
		||||
	border-radius 4px
 | 
			
		||||
	cursor pointer
 | 
			
		||||
 | 
			
		||||
	&, *
 | 
			
		||||
		-webkit-touch-callout none
 | 
			
		||||
		-webkit-user-select none
 | 
			
		||||
		-khtml-user-select none
 | 
			
		||||
		-moz-user-select none
 | 
			
		||||
		-ms-user-select none
 | 
			
		||||
		user-select none
 | 
			
		||||
 | 
			
		||||
	*
 | 
			
		||||
		user-select none
 | 
			
		||||
		pointer-events none
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-reactions-viewer" :class="{ isMe }">
 | 
			
		||||
	<x-reaction v-for="(count, reaction) in reactions" :reaction="reaction" :count="count" :note="note" :key="reaction"/>
 | 
			
		||||
	<x-reaction v-for="(count, reaction) in note.reactions" :reaction="reaction" :count="count" :is-initial="initialReactions.has(reaction)" :note="note" :key="reaction"/>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -12,6 +12,11 @@ export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XReaction
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			initialReactions: new Set(Object.keys(this.note.reactions))
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	props: {
 | 
			
		||||
		note: {
 | 
			
		||||
			type: Object,
 | 
			
		||||
@@ -19,9 +24,6 @@ export default Vue.extend({
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	computed: {
 | 
			
		||||
		reactions(): any {
 | 
			
		||||
			return this.note.reactions;
 | 
			
		||||
		},
 | 
			
		||||
		isMe(): boolean {
 | 
			
		||||
			return this.$store.getters.isSignedIn && this.$store.state.i.id === this.note.userId;
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
@@ -29,8 +29,25 @@ export default Vue.extend({
 | 
			
		||||
	computed: {
 | 
			
		||||
		appTypeForce: {
 | 
			
		||||
			get() { return this.$store.state.device.appTypeForce; },
 | 
			
		||||
			set(value) { this.$store.commit('device/set', { key: 'appTypeForce', value }); }
 | 
			
		||||
			set(value) {
 | 
			
		||||
				this.$store.commit('device/set', { key: 'appTypeForce', value });
 | 
			
		||||
				this.reload();
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		reload() {
 | 
			
		||||
			this.$root.dialog({
 | 
			
		||||
				type: 'warning',
 | 
			
		||||
				text: this.$t('@.reload-to-apply-the-setting'),
 | 
			
		||||
				showCancelButton: true
 | 
			
		||||
			}).then(({ canceled }) => {
 | 
			
		||||
				if (!canceled) {
 | 
			
		||||
					location.reload();
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -51,6 +51,26 @@
 | 
			
		||||
				<template #desc v-if="bannerUploading">{{ $t('uploading') }}<mk-ellipsis/></template>
 | 
			
		||||
			</ui-input>
 | 
			
		||||
 | 
			
		||||
			<div class="fields">
 | 
			
		||||
				<header>{{ $t('profile-metadata') }}</header>
 | 
			
		||||
				<ui-horizon-group>
 | 
			
		||||
					<ui-input v-model="fieldName0">{{ $t('metadata-label') }}</ui-input>
 | 
			
		||||
					<ui-input v-model="fieldValue0">{{ $t('metadata-content') }}</ui-input>
 | 
			
		||||
				</ui-horizon-group>
 | 
			
		||||
				<ui-horizon-group>
 | 
			
		||||
					<ui-input v-model="fieldName1">{{ $t('metadata-label') }}</ui-input>
 | 
			
		||||
					<ui-input v-model="fieldValue1">{{ $t('metadata-content') }}</ui-input>
 | 
			
		||||
				</ui-horizon-group>
 | 
			
		||||
				<ui-horizon-group>
 | 
			
		||||
					<ui-input v-model="fieldName2">{{ $t('metadata-label') }}</ui-input>
 | 
			
		||||
					<ui-input v-model="fieldValue2">{{ $t('metadata-content') }}</ui-input>
 | 
			
		||||
				</ui-horizon-group>
 | 
			
		||||
				<ui-horizon-group>
 | 
			
		||||
					<ui-input v-model="fieldName3">{{ $t('metadata-label') }}</ui-input>
 | 
			
		||||
					<ui-input v-model="fieldValue3">{{ $t('metadata-content') }}</ui-input>
 | 
			
		||||
				</ui-horizon-group>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<ui-button @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
 | 
			
		||||
		</ui-form>
 | 
			
		||||
	</section>
 | 
			
		||||
@@ -139,6 +159,14 @@ export default Vue.extend({
 | 
			
		||||
			username: null,
 | 
			
		||||
			location: null,
 | 
			
		||||
			description: null,
 | 
			
		||||
			fieldName0: null,
 | 
			
		||||
			fieldValue0: null,
 | 
			
		||||
			fieldName1: null,
 | 
			
		||||
			fieldValue1: null,
 | 
			
		||||
			fieldName2: null,
 | 
			
		||||
			fieldValue2: null,
 | 
			
		||||
			fieldName3: null,
 | 
			
		||||
			fieldValue3: null,
 | 
			
		||||
			lang: null,
 | 
			
		||||
			birthday: null,
 | 
			
		||||
			avatarId: null,
 | 
			
		||||
@@ -189,6 +217,15 @@ export default Vue.extend({
 | 
			
		||||
		this.isLocked = this.$store.state.i.isLocked;
 | 
			
		||||
		this.carefulBot = this.$store.state.i.carefulBot;
 | 
			
		||||
		this.autoAcceptFollowed = this.$store.state.i.autoAcceptFollowed;
 | 
			
		||||
 | 
			
		||||
		this.fieldName0 = this.$store.state.i.fields[0] ? this.$store.state.i.fields[0].name : null;
 | 
			
		||||
		this.fieldValue0 = this.$store.state.i.fields[0] ? this.$store.state.i.fields[0].value : null;
 | 
			
		||||
		this.fieldName1 = this.$store.state.i.fields[1] ? this.$store.state.i.fields[1].name : null;
 | 
			
		||||
		this.fieldValue1 = this.$store.state.i.fields[1] ? this.$store.state.i.fields[1].value : null;
 | 
			
		||||
		this.fieldName2 = this.$store.state.i.fields[2] ? this.$store.state.i.fields[2].name : null;
 | 
			
		||||
		this.fieldValue2 = this.$store.state.i.fields[2] ? this.$store.state.i.fields[2].value : null;
 | 
			
		||||
		this.fieldName3 = this.$store.state.i.fields[3] ? this.$store.state.i.fields[3].name : null;
 | 
			
		||||
		this.fieldValue3 = this.$store.state.i.fields[3] ? this.$store.state.i.fields[3].value : null;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
@@ -237,6 +274,13 @@ export default Vue.extend({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		save(notify) {
 | 
			
		||||
			const fields = [
 | 
			
		||||
				{ name: this.fieldName0, value: this.fieldValue0 },
 | 
			
		||||
				{ name: this.fieldName1, value: this.fieldValue1 },
 | 
			
		||||
				{ name: this.fieldName2, value: this.fieldValue2 },
 | 
			
		||||
				{ name: this.fieldName3, value: this.fieldValue3 },
 | 
			
		||||
			];
 | 
			
		||||
 | 
			
		||||
			this.saving = true;
 | 
			
		||||
 | 
			
		||||
			this.$root.api('i/update', {
 | 
			
		||||
@@ -247,6 +291,7 @@ export default Vue.extend({
 | 
			
		||||
				birthday: this.birthday || null,
 | 
			
		||||
				avatarId: this.avatarId || undefined,
 | 
			
		||||
				bannerId: this.bannerId || undefined,
 | 
			
		||||
				fields,
 | 
			
		||||
				isCat: !!this.isCat,
 | 
			
		||||
				isBot: !!this.isBot,
 | 
			
		||||
				isLocked: !!this.isLocked,
 | 
			
		||||
@@ -265,6 +310,29 @@ export default Vue.extend({
 | 
			
		||||
						text: this.$t('saved')
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
			}).catch(err => {
 | 
			
		||||
				this.saving = false;
 | 
			
		||||
				switch(err.id) {
 | 
			
		||||
					case 'f419f9f8-2f4d-46b1-9fb4-49d3a2fd7191':
 | 
			
		||||
						this.$root.dialog({
 | 
			
		||||
							type: 'error',
 | 
			
		||||
							title: this.$t('unable-to-process'),
 | 
			
		||||
							text: this.$t('avatar-not-an-image')
 | 
			
		||||
						});
 | 
			
		||||
						break;
 | 
			
		||||
					case '75aedb19-2afd-4e6d-87fc-67941256fa60':
 | 
			
		||||
						this.$root.dialog({
 | 
			
		||||
							type: 'error',
 | 
			
		||||
							title: this.$t('unable-to-process'),
 | 
			
		||||
							text: this.$t('banner-not-an-image')
 | 
			
		||||
						});
 | 
			
		||||
						break;
 | 
			
		||||
					default:
 | 
			
		||||
						this.$root.dialog({
 | 
			
		||||
							type: 'error',
 | 
			
		||||
							text: this.$t('unable-to-process')
 | 
			
		||||
						});
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
@@ -366,4 +434,9 @@ export default Vue.extend({
 | 
			
		||||
			height 72px
 | 
			
		||||
			margin auto
 | 
			
		||||
 | 
			
		||||
.fields
 | 
			
		||||
	> header
 | 
			
		||||
		padding 8px 0px
 | 
			
		||||
		font-weight bold
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -47,6 +47,7 @@
 | 
			
		||||
				<ui-switch v-model="disableAnimatedMfm">{{ $t('@._settings.disable-animated-mfm') }}</ui-switch>
 | 
			
		||||
				<ui-switch v-model="disableShowingAnimatedImages">{{ $t('@._settings.disable-showing-animated-images') }}</ui-switch>
 | 
			
		||||
				<ui-switch v-model="remainDeletedNote">{{ $t('@._settings.remain-deleted-note') }}</ui-switch>
 | 
			
		||||
				<ui-switch v-model="enableMobileQuickNotificationView">{{ $t('@._settings.enable-quick-notification-view') }}</ui-switch>
 | 
			
		||||
			</section>
 | 
			
		||||
			<section>
 | 
			
		||||
				<header>{{ $t('@._settings.line-width') }}</header>
 | 
			
		||||
@@ -143,17 +144,34 @@
 | 
			
		||||
				<ui-input v-model="webSearchEngine">{{ $t('@._settings.web-search-engine') }}
 | 
			
		||||
					<template #desc>{{ $t('@._settings.web-search-engine-desc') }}</template>
 | 
			
		||||
				</ui-input>
 | 
			
		||||
				<ui-button @click="save('webSearchEngine', webSearchEngine)"><fa :icon="faSave"/> {{ $t('@._settings.save') }}</ui-button>
 | 
			
		||||
			</section>
 | 
			
		||||
 | 
			
		||||
			<section v-if="!$root.isMobile">
 | 
			
		||||
				<header>{{ $t('@._settings.paste') }}</header>
 | 
			
		||||
				<ui-input v-model="pastedFileName">{{ $t('@._settings.pasted-file-name') }}
 | 
			
		||||
					<template #desc>{{ $t('@._settings.pasted-file-name-desc') }}</template>
 | 
			
		||||
					<template v-if="pastedFileName === this.$store.state.settings.pastedFileName" #desc>{{ $t('@._settings.pasted-file-name-desc') }}</template>
 | 
			
		||||
					<template v-else #desc>{{ pastedFileNamePreview() }}</template>
 | 
			
		||||
				</ui-input>
 | 
			
		||||
				<ui-button @click="save('pastedFileName', pastedFileName)"><fa :icon="faSave"/> {{ $t('@._settings.save') }}</ui-button>
 | 
			
		||||
 | 
			
		||||
				<ui-switch v-model="pasteDialog">{{ $t('@._settings.paste-dialog') }}
 | 
			
		||||
					<template #desc>{{ $t('@._settings.paste-dialog-desc') }}</template>
 | 
			
		||||
				</ui-switch>
 | 
			
		||||
			</section>
 | 
			
		||||
 | 
			
		||||
			<section>
 | 
			
		||||
				<header>{{ $t('@._settings.room') }}</header>
 | 
			
		||||
				<ui-select v-model="roomGraphicsQuality">
 | 
			
		||||
					<template #label>{{ $t('@._settings._room.graphicsQuality') }}</template>
 | 
			
		||||
					<option value="ultra">{{ $t('@._settings._room._graphicsQuality.ultra') }}</option>
 | 
			
		||||
					<option value="high">{{ $t('@._settings._room._graphicsQuality.high') }}</option>
 | 
			
		||||
					<option value="medium">{{ $t('@._settings._room._graphicsQuality.medium') }}</option>
 | 
			
		||||
					<option value="low">{{ $t('@._settings._room._graphicsQuality.low') }}</option>
 | 
			
		||||
					<option value="cheep">{{ $t('@._settings._room._graphicsQuality.cheep') }}</option>
 | 
			
		||||
				</ui-select>
 | 
			
		||||
				<ui-switch v-model="roomUseOrthographicCamera">{{ $t('@._settings._room.useOrthographicCamera') }}</ui-switch>
 | 
			
		||||
			</section>
 | 
			
		||||
		</ui-card>
 | 
			
		||||
 | 
			
		||||
		<ui-card>
 | 
			
		||||
@@ -289,6 +307,8 @@ import XNotification from './notification.vue';
 | 
			
		||||
 | 
			
		||||
import { url, version } from '../../../../config';
 | 
			
		||||
import checkForUpdate from '../../../scripts/check-for-update';
 | 
			
		||||
import { formatTimeString } from '../../../../../../misc/format-time-string';
 | 
			
		||||
import { faSave } from '@fortawesome/free-regular-svg-icons';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n(),
 | 
			
		||||
@@ -319,8 +339,11 @@ export default Vue.extend({
 | 
			
		||||
		return {
 | 
			
		||||
			meta: null,
 | 
			
		||||
			version,
 | 
			
		||||
			webSearchEngine: this.$store.state.settings.webSearchEngine,
 | 
			
		||||
			pastedFileName : this.$store.state.settings.pastedFileName,
 | 
			
		||||
			latestVersion: undefined,
 | 
			
		||||
			checkingForUpdate: false
 | 
			
		||||
			checkingForUpdate: false,
 | 
			
		||||
			faSave
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	computed: {
 | 
			
		||||
@@ -419,16 +442,6 @@ export default Vue.extend({
 | 
			
		||||
			set(value) { this.$store.dispatch('settings/set', { key: 'defaultNoteVisibility', value }); }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		webSearchEngine: {
 | 
			
		||||
			get() { return this.$store.state.settings.webSearchEngine; },
 | 
			
		||||
			set(value) { this.$store.dispatch('settings/set', { key: 'webSearchEngine', value }); }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		pastedFileName: {
 | 
			
		||||
			get() { return this.$store.state.settings.pastedFileName; },
 | 
			
		||||
			set(value) { this.$store.dispatch('settings/set', { key: 'pastedFileName', value }); }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		pasteDialog: {
 | 
			
		||||
			get() { return this.$store.state.settings.pasteDialog; },
 | 
			
		||||
			set(value) { this.$store.dispatch('settings/set', { key: 'pasteDialog', value }); }
 | 
			
		||||
@@ -503,6 +516,16 @@ export default Vue.extend({
 | 
			
		||||
			set(value) { this.$store.dispatch('settings/set', { key: 'iLikeSushi', value }); }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		roomUseOrthographicCamera: {
 | 
			
		||||
			get() { return this.$store.state.device.roomUseOrthographicCamera; },
 | 
			
		||||
			set(value) { this.$store.commit('device/set', { key: 'roomUseOrthographicCamera', value }); }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		roomGraphicsQuality: {
 | 
			
		||||
			get() { return this.$store.state.device.roomGraphicsQuality; },
 | 
			
		||||
			set(value) { this.$store.commit('device/set', { key: 'roomGraphicsQuality', value }); }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		games_reversi_showBoardLabels: {
 | 
			
		||||
			get() { return this.$store.state.settings.gamesReversiShowBoardLabels; },
 | 
			
		||||
			set(value) { this.$store.dispatch('settings/set', { key: 'gamesReversiShowBoardLabels', value }); }
 | 
			
		||||
@@ -533,6 +556,11 @@ export default Vue.extend({
 | 
			
		||||
			set(value) { this.$store.commit('device/set', { key: 'mobileNotificationPosition', value }); }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		enableMobileQuickNotificationView: {
 | 
			
		||||
			get() { return this.$store.state.device.enableMobileQuickNotificationView; },
 | 
			
		||||
			set(value) { this.$store.commit('device/set', { key: 'enableMobileQuickNotificationView', value }); }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		homeProfile: {
 | 
			
		||||
			get() { return this.$store.state.device.homeProfile; },
 | 
			
		||||
			set(value) { this.$store.commit('device/set', { key: 'homeProfile', value }); }
 | 
			
		||||
@@ -565,6 +593,17 @@ export default Vue.extend({
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		save(key, value) {
 | 
			
		||||
			this.$store.dispatch('settings/set', {
 | 
			
		||||
				key,
 | 
			
		||||
				value
 | 
			
		||||
			}).then(() => {
 | 
			
		||||
				this.$root.dialog({
 | 
			
		||||
					type: 'success',
 | 
			
		||||
					text: this.$t('@._settings.saved')
 | 
			
		||||
				})
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		customizeHome() {
 | 
			
		||||
			location.href = '/?customize';
 | 
			
		||||
		},
 | 
			
		||||
@@ -600,7 +639,10 @@ export default Vue.extend({
 | 
			
		||||
			const sound = new Audio(`${url}/assets/message.mp3`);
 | 
			
		||||
			sound.volume = this.$store.state.device.soundVolume;
 | 
			
		||||
			sound.play();
 | 
			
		||||
		}
 | 
			
		||||
		},
 | 
			
		||||
		pastedFileNamePreview() {
 | 
			
		||||
			return `${formatTimeString(new Date(), this.pastedFileName).replace(/{{number}}/g, `1`)}.png`
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../../i18n';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { v4 as uuid } from 'uuid';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n('desktop/views/components/settings.tags.vue'),
 | 
			
		||||
 
 | 
			
		||||
@@ -125,7 +125,7 @@ import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../../i18n';
 | 
			
		||||
import { lightTheme, darkTheme, builtinThemes, applyTheme, Theme } from '../../../../theme';
 | 
			
		||||
import { Chrome } from 'vue-color';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { v4 as uuid } from 'uuid';
 | 
			
		||||
import * as tinycolor from 'tinycolor2';
 | 
			
		||||
import * as JSON5 from 'json5';
 | 
			
		||||
import { faMoon, faSun } from '@fortawesome/free-regular-svg-icons';
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@
 | 
			
		||||
			</i18n>
 | 
			
		||||
		</ui-switch>
 | 
			
		||||
		<div v-if="meta.enableRecaptcha" class="g-recaptcha" :data-sitekey="meta.recaptchaSiteKey" style="margin: 16px 0;"></div>
 | 
			
		||||
		<ui-button type="submit" :disabled="!(meta.ToSUrl ? ToSAgreement : true) || passwordRetypeState == 'not-match'">{{ $t('create') }}</ui-button>
 | 
			
		||||
		<ui-button type="submit" :disabled=" submitting || !(meta.ToSUrl ? ToSAgreement : true) || passwordRetypeState == 'not-match'">{{ $t('create') }}</ui-button>
 | 
			
		||||
	</template>
 | 
			
		||||
</form>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -70,6 +70,7 @@ export default Vue.extend({
 | 
			
		||||
			passwordStrength: '',
 | 
			
		||||
			passwordRetypeState: null,
 | 
			
		||||
			meta: {},
 | 
			
		||||
			submitting: false,
 | 
			
		||||
			ToSAgreement: false
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
@@ -145,6 +146,9 @@ export default Vue.extend({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onSubmit() {
 | 
			
		||||
			if (this.submitting) return;
 | 
			
		||||
			this.submitting = true;
 | 
			
		||||
 | 
			
		||||
			this.$root.api('signup', {
 | 
			
		||||
				username: this.username,
 | 
			
		||||
				password: this.password,
 | 
			
		||||
@@ -159,6 +163,8 @@ export default Vue.extend({
 | 
			
		||||
					location.href = '/';
 | 
			
		||||
				});
 | 
			
		||||
			}).catch(() => {
 | 
			
		||||
				this.submitting = false;
 | 
			
		||||
 | 
			
		||||
				this.$root.dialog({
 | 
			
		||||
					type: 'error',
 | 
			
		||||
					text: this.$t('some-error')
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { v4 as uuid } from 'uuid';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: {
 | 
			
		||||
 
 | 
			
		||||
@@ -64,8 +64,7 @@ export default Vue.extend({
 | 
			
		||||
	methods: {
 | 
			
		||||
		onMousedown(e: MouseEvent) {
 | 
			
		||||
			function distance(p, q) {
 | 
			
		||||
				const sqrt = Math.sqrt, pow = Math.pow;
 | 
			
		||||
				return sqrt(pow(p.x - q.x, 2) + pow(p.y - q.y, 2));
 | 
			
		||||
				return Math.hypot(p.x - q.x, p.y - q.y);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			function calcCircleScale(boxW, boxH, circleCenterX, circleCenterY) {
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,7 @@ export default Vue.extend({
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		toggle() {
 | 
			
		||||
			if (this.disabled) return;
 | 
			
		||||
			this.$emit('change', !this.checked);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -66,6 +66,7 @@ export default Vue.extend({
 | 
			
		||||
			(this.url.substr(local.length) === '/') ||
 | 
			
		||||
			this.url.substr(local.length).startsWith('/@') ||
 | 
			
		||||
			this.url.substr(local.length).startsWith('/notes/') ||
 | 
			
		||||
			this.url.substr(local.length).startsWith('/tags/') ||
 | 
			
		||||
			this.url.substr(local.length).startsWith('/pages/');
 | 
			
		||||
		return {
 | 
			
		||||
			local,
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ export default Vue.extend({
 | 
			
		||||
			(this.url.substr(local.length) === '/') ||
 | 
			
		||||
			this.url.substr(local.length).startsWith('/@') ||
 | 
			
		||||
			this.url.substr(local.length).startsWith('/notes/') ||
 | 
			
		||||
			this.url.substr(local.length).startsWith('/tags/') ||
 | 
			
		||||
			this.url.substr(local.length).startsWith('/pages/'));
 | 
			
		||||
		return {
 | 
			
		||||
			local,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mfm :text="user.name || user.username" :plain="true" :nowrap="true" :custom-emojis="user.emojis"/>
 | 
			
		||||
<mfm :text="user.name || user.username" :plain="true" :nowrap="nowrap" :custom-emojis="user.emojis"/>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
@@ -10,7 +10,11 @@ export default Vue.extend({
 | 
			
		||||
		user: {
 | 
			
		||||
			type: Object,
 | 
			
		||||
			required: true
 | 
			
		||||
		}
 | 
			
		||||
		},
 | 
			
		||||
		nowrap: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			default: true
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
			<template v-if="active"><fa icon="angle-up"/></template>
 | 
			
		||||
			<template v-else><fa icon="angle-down"/></template>
 | 
			
		||||
		</button>
 | 
			
		||||
		<span><slot name="header"></slot></span>
 | 
			
		||||
		<span class="header"><slot name="header"></slot></span>
 | 
			
		||||
		<span class="count" v-if="count > 0">({{ count }})</span>
 | 
			
		||||
		<button v-if="!isTemporaryColumn" class="menu" ref="menu" @click.stop="showMenu"><fa icon="caret-down"/></button>
 | 
			
		||||
		<button v-else class="close" @click.stop="close"><fa icon="times"/></button>
 | 
			
		||||
@@ -395,13 +395,22 @@ export default Vue.extend({
 | 
			
		||||
		&.indicate
 | 
			
		||||
			box-shadow 0 3px 0 0 var(--primary)
 | 
			
		||||
 | 
			
		||||
		> span
 | 
			
		||||
		> .header
 | 
			
		||||
			display inline-block
 | 
			
		||||
			align-items center
 | 
			
		||||
			overflow hidden
 | 
			
		||||
			text-overflow ellipsis
 | 
			
		||||
			white-space nowrap
 | 
			
		||||
 | 
			
		||||
			[data-icon]
 | 
			
		||||
				margin-right 8px
 | 
			
		||||
 | 
			
		||||
		> .count
 | 
			
		||||
			margin-left 4px
 | 
			
		||||
			opacity 0.5
 | 
			
		||||
		
 | 
			
		||||
		> span:only-of-type
 | 
			
		||||
			width 100%
 | 
			
		||||
 | 
			
		||||
		> .toggleActive
 | 
			
		||||
		> .menu
 | 
			
		||||
 
 | 
			
		||||
@@ -54,8 +54,8 @@
 | 
			
		||||
		<div>
 | 
			
		||||
			<header>
 | 
			
		||||
				<fa icon="user-clock" class="icon"/>
 | 
			
		||||
				<router-link :to="notification.user | userPage">
 | 
			
		||||
					<mk-user-name :user="notification.user" class="name"/>
 | 
			
		||||
				<router-link :to="notification.user | userPage" class="name">
 | 
			
		||||
					<mk-user-name :user="notification.user"/>
 | 
			
		||||
				</router-link>
 | 
			
		||||
				<mk-time :time="notification.createdAt"/>
 | 
			
		||||
			</header>
 | 
			
		||||
@@ -67,8 +67,8 @@
 | 
			
		||||
		<div>
 | 
			
		||||
			<header>
 | 
			
		||||
				<fa icon="chart-pie" class="icon"/>
 | 
			
		||||
				<router-link :to="notification.user | userPage">
 | 
			
		||||
					<mk-user-name :user="notification.user" class="name"/>
 | 
			
		||||
				<router-link :to="notification.user | userPage" class="name">
 | 
			
		||||
					<mk-user-name :user="notification.user"/>
 | 
			
		||||
				</router-link>
 | 
			
		||||
				<mk-time :time="notification.createdAt"/>
 | 
			
		||||
			</header>
 | 
			
		||||
@@ -167,6 +167,10 @@ export default Vue.extend({
 | 
			
		||||
					display inline-block
 | 
			
		||||
					margin-right 3px
 | 
			
		||||
 | 
			
		||||
		&.reaction
 | 
			
		||||
			> div > header
 | 
			
		||||
				align-items normal
 | 
			
		||||
 | 
			
		||||
		&.renote
 | 
			
		||||
			> div > header [data-icon]
 | 
			
		||||
				color #77B255
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<template>
 | 
			
		||||
<x-column :name="name" :column="column" :is-stacked="isStacked">
 | 
			
		||||
<x-column :name="name" :column="column" :is-stacked="isStacked" :menu="menu">
 | 
			
		||||
	<template #header><fa :icon="['far', 'bell']"/>{{ name }}</template>
 | 
			
		||||
 | 
			
		||||
	<x-notifications/>
 | 
			
		||||
	<x-notifications :type="column.notificationType === 'all' ? null : column.notificationType"/>
 | 
			
		||||
</x-column>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -30,11 +30,46 @@ export default Vue.extend({
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			menu: null,
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		name(): string {
 | 
			
		||||
			if (this.column.name) return this.column.name;
 | 
			
		||||
			return this.$t('@deck.notifications');
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	created() {
 | 
			
		||||
		if (this.column.notificationType == null) {
 | 
			
		||||
			this.column.notificationType = 'all';
 | 
			
		||||
			this.$store.commit('updateDeckColumn', this.column);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this.menu = [{
 | 
			
		||||
			icon: 'cog',
 | 
			
		||||
			text: this.$t('@.notification-type'),
 | 
			
		||||
			action: () => {
 | 
			
		||||
				this.$root.dialog({
 | 
			
		||||
					title: this.$t('@.notification-type'),
 | 
			
		||||
					type: null,
 | 
			
		||||
					select: {
 | 
			
		||||
						items: ['all', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest'].map(x => ({
 | 
			
		||||
							value: x, text: this.$t('@.notification-types.' + x)
 | 
			
		||||
						}))
 | 
			
		||||
						default: this.column.notificationType,
 | 
			
		||||
					},
 | 
			
		||||
					showCancelButton: true
 | 
			
		||||
				}).then(({ canceled, result: type }) => {
 | 
			
		||||
					if (canceled) return;
 | 
			
		||||
					this.column.notificationType = type;
 | 
			
		||||
					this.$store.commit('updateDeckColumn', this.column);
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		}];
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -47,12 +47,22 @@ export default Vue.extend({
 | 
			
		||||
		}),
 | 
			
		||||
	],
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
		type: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: false
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			connection: null,
 | 
			
		||||
			pagination: {
 | 
			
		||||
				endpoint: 'i/notifications',
 | 
			
		||||
				limit: 20,
 | 
			
		||||
				params: () => ({
 | 
			
		||||
					includeTypes: this.type ? [this.type] : undefined
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
@@ -69,6 +79,12 @@ export default Vue.extend({
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	watch: {
 | 
			
		||||
		type() {
 | 
			
		||||
			this.reload();
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.connection = this.$root.stream.useSharedConnection('main');
 | 
			
		||||
		this.connection.on('notification', this.onNotification);
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
				<mk-follow-button v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" class="follow" mini/>
 | 
			
		||||
				<mk-avatar class="avatar" :user="user" :disable-preview="true" :key="user.id"/>
 | 
			
		||||
				<router-link class="name" :to="user | userPage()">
 | 
			
		||||
					<mk-user-name :user="user" :key="user.id"/>
 | 
			
		||||
					<mk-user-name :user="user" :key="user.id" :nowrap="false"/>
 | 
			
		||||
				</router-link>
 | 
			
		||||
				<span class="acct">@{{ user | acct }} <fa v-if="user.isLocked == true" class="locked" icon="lock" fixed-width/></span>
 | 
			
		||||
				<span class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</span>
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ import i18n from '../../../i18n';
 | 
			
		||||
import XColumnCore from './deck.column-core.vue';
 | 
			
		||||
import Menu from '../../../common/views/components/menu.vue';
 | 
			
		||||
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { v4 as uuid } from 'uuid';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n('deck'),
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@ import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import XColumn from './deck.column.vue';
 | 
			
		||||
import * as XDraggable from 'vuedraggable';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { v4 as uuid } from 'uuid';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n(),
 | 
			
		||||
 
 | 
			
		||||
@@ -143,7 +143,11 @@ export default Vue.extend({
 | 
			
		||||
		this.$root.getMeta().then(meta => {
 | 
			
		||||
			this.meta = meta;
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		document.title = this.$root.instanceName;
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -40,5 +40,9 @@ export default Vue.extend({
 | 
			
		||||
			icon: faStar
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		document.title = this.$root.instanceName;
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -40,5 +40,9 @@ export default Vue.extend({
 | 
			
		||||
			icon: faNewspaper
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		document.title = this.$root.instanceName;
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { v4 as uuid } from 'uuid';
 | 
			
		||||
import { faPlus, faQuestion } from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import i18n from '../../../../../i18n';
 | 
			
		||||
import XContainer from '../page-editor.container.vue';
 | 
			
		||||
@@ -76,7 +76,7 @@ export default Vue.extend({
 | 
			
		||||
			});
 | 
			
		||||
			if (canceled) return;
 | 
			
		||||
 | 
			
		||||
			const id = uuid.v4();
 | 
			
		||||
			const id = uuid();
 | 
			
		||||
			this.value.children.push({ id, type });
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,53 @@
 | 
			
		||||
<template>
 | 
			
		||||
<x-container @remove="() => $emit('remove')" :draggable="true">
 | 
			
		||||
	<template #header><fa :icon="faBolt"/> {{ $t('blocks.radioButton') }}</template>
 | 
			
		||||
 | 
			
		||||
	<section style="padding: 0 16px 16px 16px;">
 | 
			
		||||
		<ui-input v-model="value.name"><template #prefix><fa :icon="faMagic"/></template><span>{{ $t('blocks._radioButton.name') }}</span></ui-input>
 | 
			
		||||
		<ui-input v-model="value.title"><span>{{ $t('blocks._radioButton.title') }}</span></ui-input>
 | 
			
		||||
		<ui-textarea v-model="values"><span>{{ $t('blocks._radioButton.values') }}</span></ui-textarea>
 | 
			
		||||
		<ui-input v-model="value.default"><span>{{ $t('blocks._radioButton.default') }}</span></ui-input>
 | 
			
		||||
	</section>
 | 
			
		||||
</x-container>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import { faBolt, faMagic } from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import i18n from '../../../../../i18n';
 | 
			
		||||
import XContainer from '../page-editor.container.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n('pages'),
 | 
			
		||||
 | 
			
		||||
	components: {
 | 
			
		||||
		XContainer
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
		value: {
 | 
			
		||||
			required: true
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			values: '',
 | 
			
		||||
			faBolt, faMagic
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	watch: {
 | 
			
		||||
		values() {
 | 
			
		||||
			Vue.set(this.value, 'values', this.values.split('\n'));
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	created() {
 | 
			
		||||
		if (this.value.name == null) Vue.set(this.value, 'name', '');
 | 
			
		||||
		if (this.value.title == null) Vue.set(this.value, 'title', '');
 | 
			
		||||
		if (this.value.values == null) Vue.set(this.value, 'values', []);
 | 
			
		||||
		this.values = this.value.values.join('\n');
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { v4 as uuid } from 'uuid';
 | 
			
		||||
import { faPlus, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import { faStickyNote } from '@fortawesome/free-regular-svg-icons';
 | 
			
		||||
import i18n from '../../../../../i18n';
 | 
			
		||||
@@ -88,7 +88,7 @@ export default Vue.extend({
 | 
			
		||||
			});
 | 
			
		||||
			if (canceled) return;
 | 
			
		||||
 | 
			
		||||
			const id = uuid.v4();
 | 
			
		||||
			const id = uuid();
 | 
			
		||||
			this.value.children.push({ id, type });
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,10 +19,11 @@ import XSwitch from './els/page-editor.el.switch.vue';
 | 
			
		||||
import XIf from './els/page-editor.el.if.vue';
 | 
			
		||||
import XPost from './els/page-editor.el.post.vue';
 | 
			
		||||
import XCounter from './els/page-editor.el.counter.vue';
 | 
			
		||||
import XRadioButton from './els/page-editor.el.radio-button.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XDraggable, XSection, XText, XImage, XButton, XTextarea, XTextInput, XTextareaInput, XNumberInput, XSwitch, XIf, XPost, XCounter
 | 
			
		||||
		XDraggable, XSection, XText, XImage, XButton, XTextarea, XTextInput, XTextareaInput, XNumberInput, XSwitch, XIf, XPost, XCounter, XRadioButton
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ import i18n from '../../../../i18n';
 | 
			
		||||
import XContainer from './page-editor.container.vue';
 | 
			
		||||
import { faPencilAlt, faPlug } from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import { isLiteralBlock, funcDefs, blockDefs } from '../../../../../../misc/aiscript/index';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { v4 as uuid } from 'uuid';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n('pages'),
 | 
			
		||||
@@ -143,7 +143,7 @@ export default Vue.extend({
 | 
			
		||||
			this.warn = null;
 | 
			
		||||
 | 
			
		||||
			if (this.value.type === 'fn') {
 | 
			
		||||
				const id = uuid.v4();
 | 
			
		||||
				const id = uuid();
 | 
			
		||||
				this.value.value = {};
 | 
			
		||||
				Vue.set(this.value.value, 'slots', []);
 | 
			
		||||
				Vue.set(this.value.value, 'expression', { id, type: null });
 | 
			
		||||
@@ -156,7 +156,7 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
				const empties = [];
 | 
			
		||||
				for (let i = 0; i < fn.value.slots.length; i++) {
 | 
			
		||||
					const id = uuid.v4();
 | 
			
		||||
					const id = uuid();
 | 
			
		||||
					empties.push({ id, type: null });
 | 
			
		||||
				}
 | 
			
		||||
				Vue.set(this.value, 'args', empties);
 | 
			
		||||
@@ -167,7 +167,7 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
			const empties = [];
 | 
			
		||||
			for (let i = 0; i < funcDefs[this.value.type].in.length; i++) {
 | 
			
		||||
				const id = uuid.v4();
 | 
			
		||||
				const id = uuid();
 | 
			
		||||
				empties.push({ id, type: null });
 | 
			
		||||
			}
 | 
			
		||||
			Vue.set(this.value, 'args', empties);
 | 
			
		||||
 
 | 
			
		||||
@@ -99,7 +99,7 @@ import { faSave, faStickyNote, faTrashAlt } from '@fortawesome/free-regular-svg-
 | 
			
		||||
import i18n from '../../../../i18n';
 | 
			
		||||
import XVariable from './page-editor.script-block.vue';
 | 
			
		||||
import XBlocks from './page-editor.blocks.vue';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { v4 as uuid } from 'uuid';
 | 
			
		||||
import { blockDefs } from '../../../../../../misc/aiscript/index';
 | 
			
		||||
import { ASTypeChecker } from '../../../../../../misc/aiscript/type-checker';
 | 
			
		||||
import { url } from '../../../../config';
 | 
			
		||||
@@ -201,7 +201,7 @@ export default Vue.extend({
 | 
			
		||||
			this.variables = this.page.variables;
 | 
			
		||||
			this.eyeCatchingImageId = this.page.eyeCatchingImageId;
 | 
			
		||||
		} else {
 | 
			
		||||
			const id = uuid.v4();
 | 
			
		||||
			const id = uuid();
 | 
			
		||||
			this.content = [{
 | 
			
		||||
				id,
 | 
			
		||||
				type: 'text',
 | 
			
		||||
@@ -220,37 +220,48 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		save() {
 | 
			
		||||
			const options = {
 | 
			
		||||
				title: this.title.trim(),
 | 
			
		||||
				name: this.name.trim(),
 | 
			
		||||
				summary: this.summary,
 | 
			
		||||
				font: this.font,
 | 
			
		||||
				hideTitleWhenPinned: this.hideTitleWhenPinned,
 | 
			
		||||
				alignCenter: this.alignCenter,
 | 
			
		||||
				content: this.content,
 | 
			
		||||
				variables: this.variables,
 | 
			
		||||
				eyeCatchingImageId: this.eyeCatchingImageId,
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			const onError = err => {
 | 
			
		||||
				if (err.id == '3d81ceae-475f-4600-b2a8-2bc116157532') {
 | 
			
		||||
					if (err.info.param == 'name') {
 | 
			
		||||
						this.$root.dialog({
 | 
			
		||||
							type: 'error',
 | 
			
		||||
							title: this.$t('title-invalid-name'),
 | 
			
		||||
							text: this.$t('text-invalid-name')
 | 
			
		||||
						});
 | 
			
		||||
					}
 | 
			
		||||
				} else if (err.code == 'NAME_ALREADY_EXISTS') {
 | 
			
		||||
					this.$root.dialog({
 | 
			
		||||
						type: 'error',
 | 
			
		||||
						text: this.$t('name-already-exists')
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			if (this.pageId) {
 | 
			
		||||
				this.$root.api('pages/update', {
 | 
			
		||||
					pageId: this.pageId,
 | 
			
		||||
					title: this.title.trim(),
 | 
			
		||||
					name: this.name.trim(),
 | 
			
		||||
					summary: this.summary,
 | 
			
		||||
					font: this.font,
 | 
			
		||||
					hideTitleWhenPinned: this.hideTitleWhenPinned,
 | 
			
		||||
					alignCenter: this.alignCenter,
 | 
			
		||||
					content: this.content,
 | 
			
		||||
					variables: this.variables,
 | 
			
		||||
					eyeCatchingImageId: this.eyeCatchingImageId,
 | 
			
		||||
				}).then(page => {
 | 
			
		||||
				options.pageId = this.pageId;
 | 
			
		||||
				this.$root.api('pages/update', options)
 | 
			
		||||
				.then(page => {
 | 
			
		||||
					this.currentName = this.name.trim();
 | 
			
		||||
					this.$root.dialog({
 | 
			
		||||
						type: 'success',
 | 
			
		||||
						text: this.$t('page-updated')
 | 
			
		||||
					});
 | 
			
		||||
				});
 | 
			
		||||
				}).catch(onError);
 | 
			
		||||
			} else {
 | 
			
		||||
				this.$root.api('pages/create', {
 | 
			
		||||
					title: this.title.trim(),
 | 
			
		||||
					name: this.name.trim(),
 | 
			
		||||
					summary: this.summary,
 | 
			
		||||
					font: this.font,
 | 
			
		||||
					hideTitleWhenPinned: this.hideTitleWhenPinned,
 | 
			
		||||
					alignCenter: this.alignCenter,
 | 
			
		||||
					content: this.content,
 | 
			
		||||
					variables: this.variables,
 | 
			
		||||
					eyeCatchingImageId: this.eyeCatchingImageId,
 | 
			
		||||
				}).then(page => {
 | 
			
		||||
				this.$root.api('pages/create', options)
 | 
			
		||||
				.then(page => {
 | 
			
		||||
					this.pageId = page.id;
 | 
			
		||||
					this.currentName = this.name.trim();
 | 
			
		||||
					this.$root.dialog({
 | 
			
		||||
@@ -258,7 +269,7 @@ export default Vue.extend({
 | 
			
		||||
						text: this.$t('page-created')
 | 
			
		||||
					});
 | 
			
		||||
					this.$router.push(`/i/pages/edit/${this.pageId}`);
 | 
			
		||||
				});
 | 
			
		||||
				}).catch(onError);
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
@@ -292,7 +303,7 @@ export default Vue.extend({
 | 
			
		||||
			});
 | 
			
		||||
			if (canceled) return;
 | 
			
		||||
 | 
			
		||||
			const id = uuid.v4();
 | 
			
		||||
			const id = uuid();
 | 
			
		||||
			this.content.push({ id, type });
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
@@ -316,7 +327,7 @@ export default Vue.extend({
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			const id = uuid.v4();
 | 
			
		||||
			const id = uuid();
 | 
			
		||||
			this.variables.push({ id, name, type: null });
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
@@ -342,6 +353,7 @@ export default Vue.extend({
 | 
			
		||||
				label: this.$t('input-blocks'),
 | 
			
		||||
				items: [
 | 
			
		||||
					{ value: 'button', text: this.$t('blocks.button') },
 | 
			
		||||
					{ value: 'radioButton', text: this.$t('blocks.radioButton') },
 | 
			
		||||
					{ value: 'textInput', text: this.$t('blocks.textInput') },
 | 
			
		||||
					{ value: 'textareaInput', text: this.$t('blocks.textareaInput') },
 | 
			
		||||
					{ value: 'numberInput', text: this.$t('blocks.numberInput') },
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,9 @@ export default Vue.extend({
 | 
			
		||||
			icon: faStickyNote
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		document.title = this.$root.instanceName;
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		create() {
 | 
			
		||||
			this.$router.push(`/i/pages/new`);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										106
									
								
								src/client/app/common/views/pages/room/preview.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								src/client/app/common/views/pages/room/preview.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,106 @@
 | 
			
		||||
<template>
 | 
			
		||||
<canvas width=224 height=128></canvas>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import * as THREE from 'three';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			selected: null,
 | 
			
		||||
			objectHeight: 0,
 | 
			
		||||
			orbitRadius: 5
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		const canvas = this.$el;
 | 
			
		||||
 | 
			
		||||
		const width = canvas.width;
 | 
			
		||||
		const height = canvas.height;
 | 
			
		||||
 | 
			
		||||
		const scene = new THREE.Scene();
 | 
			
		||||
 | 
			
		||||
		const renderer = new THREE.WebGLRenderer({
 | 
			
		||||
			canvas: canvas,
 | 
			
		||||
			antialias: true,
 | 
			
		||||
			alpha: false
 | 
			
		||||
		});
 | 
			
		||||
		renderer.setPixelRatio(window.devicePixelRatio);
 | 
			
		||||
		renderer.setSize(width, height);
 | 
			
		||||
		renderer.setClearColor(0x000000);
 | 
			
		||||
		renderer.autoClear = false;
 | 
			
		||||
		renderer.shadowMap.enabled = true;
 | 
			
		||||
		renderer.shadowMap.cullFace = THREE.CullFaceBack;
 | 
			
		||||
 | 
			
		||||
		const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 100);
 | 
			
		||||
		camera.zoom = 10;
 | 
			
		||||
		camera.position.x = 0;
 | 
			
		||||
		camera.position.y = 2;
 | 
			
		||||
		camera.position.z = 0;
 | 
			
		||||
		camera.updateProjectionMatrix();
 | 
			
		||||
		scene.add(camera);
 | 
			
		||||
 | 
			
		||||
		const ambientLight = new THREE.AmbientLight(0xffffff, 1);
 | 
			
		||||
		ambientLight.castShadow = false;
 | 
			
		||||
		scene.add(ambientLight);
 | 
			
		||||
 | 
			
		||||
		const light = new THREE.PointLight(0xffffff, 1, 100);
 | 
			
		||||
		light.position.set(3, 3, 3);
 | 
			
		||||
		scene.add(light);
 | 
			
		||||
 | 
			
		||||
		const grid = new THREE.GridHelper(5, 16, 0x444444, 0x222222);
 | 
			
		||||
		scene.add(grid);
 | 
			
		||||
 | 
			
		||||
		const render = () => {
 | 
			
		||||
			const timer = Date.now() * 0.0004;
 | 
			
		||||
			requestAnimationFrame(render);
 | 
			
		||||
			
 | 
			
		||||
			camera.position.y = Math.sin(Math.PI / 6) * this.orbitRadius;	// Math.PI / 6 => 30deg
 | 
			
		||||
			camera.position.z = Math.cos(timer) * this.orbitRadius;
 | 
			
		||||
			camera.position.x = Math.sin(timer) * this.orbitRadius;
 | 
			
		||||
			camera.lookAt(new THREE.Vector3(0, this.objectHeight / 2, 0));
 | 
			
		||||
			renderer.render(scene, camera);
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		this.selected = selected => {
 | 
			
		||||
			const obj = selected.clone();
 | 
			
		||||
 | 
			
		||||
			// Remove current object
 | 
			
		||||
			const current = scene.getObjectByName('obj');
 | 
			
		||||
			if (current != null) {
 | 
			
		||||
				scene.remove(current);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Add new object
 | 
			
		||||
			obj.name = 'obj';
 | 
			
		||||
			obj.position.x = 0;
 | 
			
		||||
			obj.position.y = 0;
 | 
			
		||||
			obj.position.z = 0;
 | 
			
		||||
			obj.rotation.x = 0;
 | 
			
		||||
			obj.rotation.y = 0;
 | 
			
		||||
			obj.rotation.z = 0;
 | 
			
		||||
			obj.traverse(child => {
 | 
			
		||||
				if (child instanceof THREE.Mesh) {
 | 
			
		||||
					child.material = child.material.clone();
 | 
			
		||||
					return child.material.emissive.setHex(0x000000);
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
			const objectBoundingBox = new THREE.Box3().setFromObject(obj);
 | 
			
		||||
			this.objectHeight = objectBoundingBox.max.y - objectBoundingBox.min.y;
 | 
			
		||||
 | 
			
		||||
			const objectWidth = objectBoundingBox.max.x - objectBoundingBox.min.x;
 | 
			
		||||
			const objectDepth = objectBoundingBox.max.z - objectBoundingBox.min.z;
 | 
			
		||||
 | 
			
		||||
			const horizontal = Math.hypot(objectWidth, objectDepth) / camera.aspect;
 | 
			
		||||
			this.orbitRadius = Math.max(horizontal, this.objectHeight) * camera.zoom * 0.625 / Math.tan(camera.fov * 0.5 * (Math.PI / 180));
 | 
			
		||||
		
 | 
			
		||||
			scene.add(obj);
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		render();
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user