Compare commits
	
		
			62 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					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 | 
							
								
								
									
										4
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							@@ -1,3 +1,7 @@
 | 
			
		||||
*.svg -diff -text
 | 
			
		||||
*.psd -diff -text
 | 
			
		||||
*.ai -diff -text
 | 
			
		||||
*.mqo -diff -text
 | 
			
		||||
*.glb -diff -text
 | 
			
		||||
*.blend -diff -text
 | 
			
		||||
*.afdesign -diff -text
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -30,3 +30,10 @@ api-docs.json
 | 
			
		||||
.DS_Store
 | 
			
		||||
/files
 | 
			
		||||
ormconfig.json
 | 
			
		||||
 | 
			
		||||
# blender backups
 | 
			
		||||
*.blend1
 | 
			
		||||
*.blend2
 | 
			
		||||
*.blend3
 | 
			
		||||
*.blend4
 | 
			
		||||
*.blend5
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
v12.6.0
 | 
			
		||||
v12.8.1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										55
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,6 +1,61 @@
 | 
			
		||||
ChangeLog
 | 
			
		||||
=========
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 
 | 
			
		||||
@@ -7,12 +7,18 @@ 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 the develop branch by @syuilo.
 | 
			
		||||
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 cannot find the language you want to contribute with, please open an issue.
 | 
			
		||||
If your language is not listed in Crowdin, please open an issue.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
@@ -32,8 +38,17 @@ Documentation of Vue I18n is available at http://kazupon.github.io/vue-i18n/intr
 | 
			
		||||
Misskey uses CircleCI for executing automated tests.
 | 
			
		||||
Configuration files are located in [`/.circleci`](/.circleci).
 | 
			
		||||
 | 
			
		||||
## FAQ
 | 
			
		||||
## Adding MisskeyRoom items
 | 
			
		||||
Currently, we accept only 3D models created with [Blender](https://www.blender.org/).
 | 
			
		||||
 | 
			
		||||
* Use English for material, object and texture names
 | 
			
		||||
* Use meter for unit of length
 | 
			
		||||
* Your PR must include all source files of your models (for later editing)
 | 
			
		||||
* Your PR must include the glTF binary files (.glb) of your models
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
@@ -93,6 +108,8 @@ Good:
 | 
			
		||||
if (foo) bar;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Make sure that the condition and the body statement are on the same line.
 | 
			
		||||
 | 
			
		||||
### Do not use `==` when it can simply be replaced with `===`.
 | 
			
		||||
🥰
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
FROM node:12.6-alpine AS base
 | 
			
		||||
FROM node:12.8-alpine AS base
 | 
			
		||||
 | 
			
		||||
ENV NODE_ENV=production
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							@@ -104,21 +104,17 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
 | 
			
		||||
<!-- PATREON_START -->
 | 
			
		||||
<table><tr>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5888816/36da0f7c15954df0ab13f9abdf227f66/1.jpeg?token-time=2145916800&token-hash=at8QpJXJ8C0zINY_NmoMKv-MhXVoUK-YzTgaJPJzJYU%3D" alt="Hiroshi Seki" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/20010324/b8af4bd31ae34fbf8806cc0e6228e400/1.png?token-time=2145916800&token-hash=iyiocfousNIUwASmatsIDq8EOsmLUdrQNkWyktHlmJg%3D" alt="Nemo" width="100"></td>
 | 
			
		||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp" width="100"></td>
 | 
			
		||||
<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>
 | 
			
		||||
</tr><tr>
 | 
			
		||||
<td><a href="https://www.patreon.com/rane_hs">Hiroshi Seki</a></td>
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=20010324">Nemo</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>
 | 
			
		||||
</tr></table>
 | 
			
		||||
<table><tr>
 | 
			
		||||
@@ -126,32 +122,27 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
 | 
			
		||||
<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://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=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>
 | 
			
		||||
@@ -161,7 +152,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
 | 
			
		||||
<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>
 | 
			
		||||
@@ -175,7 +165,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
 | 
			
		||||
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
 | 
			
		||||
</tr></table>
 | 
			
		||||
 | 
			
		||||
**Last updated:** Fri, 19 Jul 2019 15:41:09 UTC
 | 
			
		||||
**Last updated:** Mon, 05 Aug 2019 20:46:06 UTC
 | 
			
		||||
<!-- PATREON_END -->
 | 
			
		||||
 | 
			
		||||
:four_leaf_clover: Copyright
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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í"
 | 
			
		||||
@@ -99,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"
 | 
			
		||||
@@ -180,6 +194,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)"
 | 
			
		||||
@@ -489,6 +504,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í"
 | 
			
		||||
@@ -1264,6 +1280,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í"
 | 
			
		||||
@@ -1332,3 +1350,8 @@ pages:
 | 
			
		||||
        arg1: "Seznamy"
 | 
			
		||||
    types:
 | 
			
		||||
      array: "Seznamy"
 | 
			
		||||
room:
 | 
			
		||||
  translate: "Přesunout"
 | 
			
		||||
  save: "Uložit"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "Po"
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
@@ -1606,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:
 | 
			
		||||
@@ -1901,3 +1910,8 @@ pages:
 | 
			
		||||
    enviromentVariables: "Miljø variabel"
 | 
			
		||||
    pageVariables: "Side element"
 | 
			
		||||
    argVariables: "Input slot"
 | 
			
		||||
room:
 | 
			
		||||
  translate: "Flyt"
 | 
			
		||||
  save: "Gem"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "Man"
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
@@ -876,6 +880,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"
 | 
			
		||||
@@ -940,3 +946,7 @@ pages:
 | 
			
		||||
        arg1: "Listen"
 | 
			
		||||
    types:
 | 
			
		||||
      array: "Listen"
 | 
			
		||||
room:
 | 
			
		||||
  save: "Speichern"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "Mo"
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
@@ -202,6 +215,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"
 | 
			
		||||
@@ -531,6 +545,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:
 | 
			
		||||
@@ -618,7 +634,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:
 | 
			
		||||
@@ -1698,6 +1714,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:
 | 
			
		||||
@@ -2056,3 +2074,8 @@ pages:
 | 
			
		||||
    enviromentVariables: "Environment variable"
 | 
			
		||||
    pageVariables: "Page element"
 | 
			
		||||
    argVariables: "Input slot"
 | 
			
		||||
room:
 | 
			
		||||
  translate: "Move"
 | 
			
		||||
  save: "Save"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "M"
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
@@ -1089,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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
@@ -392,6 +405,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"
 | 
			
		||||
@@ -519,6 +533,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"
 | 
			
		||||
@@ -587,6 +602,7 @@ common/views/components/emoji-picker.vue:
 | 
			
		||||
  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"
 | 
			
		||||
@@ -709,6 +725,8 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  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."
 | 
			
		||||
@@ -894,6 +912,8 @@ desktop/views/components/drive.folder.vue:
 | 
			
		||||
  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"
 | 
			
		||||
@@ -1007,6 +1027,7 @@ 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é"
 | 
			
		||||
@@ -1038,6 +1059,7 @@ 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:
 | 
			
		||||
@@ -1146,11 +1168,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"
 | 
			
		||||
@@ -1183,12 +1207,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"
 | 
			
		||||
@@ -1366,6 +1392,7 @@ admin/views/moderators.vue:
 | 
			
		||||
    title: "Journaux"
 | 
			
		||||
    moderator: "Modérateurs"
 | 
			
		||||
    type: "Actions"
 | 
			
		||||
    at: "Date de modification"
 | 
			
		||||
    info: "Informations"
 | 
			
		||||
admin/views/emoji.vue:
 | 
			
		||||
  add-emoji:
 | 
			
		||||
@@ -1643,6 +1670,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:
 | 
			
		||||
@@ -1970,3 +1999,8 @@ pages:
 | 
			
		||||
    emptySlot: "Slot vide"
 | 
			
		||||
    enviromentVariables: "Variables d'environnement"
 | 
			
		||||
    pageVariables: "Élément de page"
 | 
			
		||||
room:
 | 
			
		||||
  translate: "Déplacer"
 | 
			
		||||
  save: "Enregistrer"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "L"
 | 
			
		||||
 
 | 
			
		||||
@@ -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:
 | 
			
		||||
@@ -214,6 +227,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: "常にメディアを閲覧注意として投稿"
 | 
			
		||||
@@ -294,6 +308,16 @@ common:
 | 
			
		||||
    saved: "保存しました"
 | 
			
		||||
    home-profile: "ホームのプロファイル"
 | 
			
		||||
    deck-profile: "デッキのプロファイル"
 | 
			
		||||
    room: "ルーム"
 | 
			
		||||
    _room:
 | 
			
		||||
      graphicsQuality: "グラフィックの品質"
 | 
			
		||||
      _graphicsQuality:
 | 
			
		||||
        ultra: "最高"
 | 
			
		||||
        high: "高"
 | 
			
		||||
        medium: "中"
 | 
			
		||||
        low: "低"
 | 
			
		||||
        cheep: "最低"
 | 
			
		||||
      useOrthographicCamera: "平行投影カメラを使用"
 | 
			
		||||
 | 
			
		||||
  search: "検索"
 | 
			
		||||
  delete: "削除"
 | 
			
		||||
@@ -569,6 +593,8 @@ common/views/components/note-menu.vue:
 | 
			
		||||
  unpin: "ピン留め解除"
 | 
			
		||||
  delete: "削除"
 | 
			
		||||
  delete-confirm: "この投稿を削除しますか?"
 | 
			
		||||
  delete-and-edit: "削除して編集"
 | 
			
		||||
  delete-and-edit-confirm: "この投稿を削除してもう一度編集しますか?この投稿へのリアクション、Renote、返信も全て削除されます。"
 | 
			
		||||
  remote: "投稿元で見る"
 | 
			
		||||
  pin-limit-exceeded: "これ以上ピン留めできません。"
 | 
			
		||||
 | 
			
		||||
@@ -1234,6 +1260,7 @@ desktop/views/components/ui.header.account.vue:
 | 
			
		||||
  groups: "グループ"
 | 
			
		||||
  follow-requests: "フォロー申請"
 | 
			
		||||
  admin: "管理"
 | 
			
		||||
  room: "ルーム"
 | 
			
		||||
 | 
			
		||||
desktop/views/components/ui.header.nav.vue:
 | 
			
		||||
  game: "ゲーム"
 | 
			
		||||
@@ -1883,6 +1910,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: "{}としてサインイン中"
 | 
			
		||||
 | 
			
		||||
@@ -2262,3 +2292,55 @@ pages:
 | 
			
		||||
    enviromentVariables: "環境変数"
 | 
			
		||||
    pageVariables: "ページ要素"
 | 
			
		||||
    argVariables: "入力スロット"
 | 
			
		||||
 | 
			
		||||
room:
 | 
			
		||||
  add-furniture: "家具を置く"
 | 
			
		||||
  translate: "移動"
 | 
			
		||||
  rotate: "回転"
 | 
			
		||||
  exit: "戻る"
 | 
			
		||||
  remove: "しまう"
 | 
			
		||||
  save: "保存"
 | 
			
		||||
  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: "ポスター(縦長)"
 | 
			
		||||
 
 | 
			
		||||
@@ -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: "カスタマイズのヒント"
 | 
			
		||||
@@ -1176,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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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": "리액션 수정"
 | 
			
		||||
@@ -202,6 +215,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: "항상 미디어를 열람주의로 설정하여 게시"
 | 
			
		||||
@@ -531,6 +545,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:
 | 
			
		||||
@@ -1698,6 +1714,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:
 | 
			
		||||
@@ -2056,3 +2074,8 @@ pages:
 | 
			
		||||
    enviromentVariables: "환경 변수"
 | 
			
		||||
    pageVariables: "페이지 요소"
 | 
			
		||||
    argVariables: "입력 슬롯"
 | 
			
		||||
room:
 | 
			
		||||
  translate: "이동"
 | 
			
		||||
  save: "저장"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "월"
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
@@ -569,6 +574,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:
 | 
			
		||||
@@ -631,3 +638,7 @@ pages:
 | 
			
		||||
        arg1: "Lijsten"
 | 
			
		||||
    types:
 | 
			
		||||
      array: "Lijsten"
 | 
			
		||||
room:
 | 
			
		||||
  translate: "Verplaatsen"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "M"
 | 
			
		||||
 
 | 
			
		||||
@@ -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!"
 | 
			
		||||
@@ -467,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"
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
@@ -1137,6 +1149,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:
 | 
			
		||||
@@ -1242,3 +1256,8 @@ pages:
 | 
			
		||||
        arg1: "Listy"
 | 
			
		||||
    types:
 | 
			
		||||
      array: "Listy"
 | 
			
		||||
room:
 | 
			
		||||
  translate: "Przenieś"
 | 
			
		||||
  save: "Zapisz"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "Pn"
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -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: "自定义提示"
 | 
			
		||||
@@ -202,6 +215,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 来标记附件"
 | 
			
		||||
@@ -531,6 +545,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:
 | 
			
		||||
@@ -726,6 +742,8 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  uploading: "正在上传"
 | 
			
		||||
  upload-failed: "上传失败"
 | 
			
		||||
  unable-to-process: "无法完成操作"
 | 
			
		||||
  avatar-not-an-image: "选择的头像文件不是图片格式"
 | 
			
		||||
  banner-not-an-image: "选择的横幅背景不是图片格式"
 | 
			
		||||
  email: "邮件设置"
 | 
			
		||||
  email-address: "电子邮件地址"
 | 
			
		||||
  email-verified: "电子邮件地址已验证"
 | 
			
		||||
@@ -745,6 +763,8 @@ 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: "用户"
 | 
			
		||||
@@ -1694,6 +1714,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:
 | 
			
		||||
@@ -2052,3 +2074,8 @@ pages:
 | 
			
		||||
    enviromentVariables: "环境变量"
 | 
			
		||||
    pageVariables: "页面元素"
 | 
			
		||||
    argVariables: "输入槽函数"
 | 
			
		||||
room:
 | 
			
		||||
  translate: "移动"
 | 
			
		||||
  save: "保存"
 | 
			
		||||
  furnitures:
 | 
			
		||||
    moon: "一"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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"`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										68
									
								
								package.json
									
									
									
									
									
								
							@@ -1,7 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "misskey",
 | 
			
		||||
	"author": "syuilo <i@syuilo.com>",
 | 
			
		||||
	"version": "11.26.1",
 | 
			
		||||
	"version": "11.28.2",
 | 
			
		||||
	"codename": "daybreak",
 | 
			
		||||
	"repository": {
 | 
			
		||||
		"type": "git",
 | 
			
		||||
@@ -30,7 +30,7 @@
 | 
			
		||||
		"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",
 | 
			
		||||
@@ -66,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",
 | 
			
		||||
@@ -99,35 +98,37 @@
 | 
			
		||||
		"@types/websocket": "0.0.40",
 | 
			
		||||
		"@types/ws": "6.0.1",
 | 
			
		||||
		"@typescript-eslint/parser": "1.11.0",
 | 
			
		||||
		"animejs": "3.0.1",
 | 
			
		||||
		"apexcharts": "3.8.2",
 | 
			
		||||
		"agentkeepalive": "4.0.2",
 | 
			
		||||
		"animejs": "3.1.0",
 | 
			
		||||
		"apexcharts": "3.8.4",
 | 
			
		||||
		"autobind-decorator": "2.4.0",
 | 
			
		||||
		"autosize": "4.0.2",
 | 
			
		||||
		"autwh": "0.1.0",
 | 
			
		||||
		"aws-sdk": "2.512.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.2.1",
 | 
			
		||||
		"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.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.1",
 | 
			
		||||
		"file-type": "12.1.0",
 | 
			
		||||
		"fluent-ffmpeg": "2.1.2",
 | 
			
		||||
		"fuckadblock": "3.2.1",
 | 
			
		||||
		"gulp": "4.0.2",
 | 
			
		||||
@@ -145,6 +146,7 @@
 | 
			
		||||
		"hard-source-webpack-plugin": "0.13.1",
 | 
			
		||||
		"html-minifier": "4.0.0",
 | 
			
		||||
		"http-signature": "1.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",
 | 
			
		||||
@@ -153,13 +155,13 @@
 | 
			
		||||
		"json5": "2.1.0",
 | 
			
		||||
		"json5-loader": "3.0.0",
 | 
			
		||||
		"jsrsasign": "8.0.12",
 | 
			
		||||
		"katex": "0.10.2",
 | 
			
		||||
		"katex": "0.11.0",
 | 
			
		||||
		"koa": "2.7.0",
 | 
			
		||||
		"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,36 +170,33 @@
 | 
			
		||||
		"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.1",
 | 
			
		||||
		"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.4.0",
 | 
			
		||||
		"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",
 | 
			
		||||
		"redis": "2.8.0",
 | 
			
		||||
@@ -210,8 +209,8 @@
 | 
			
		||||
		"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",
 | 
			
		||||
@@ -220,10 +219,11 @@
 | 
			
		||||
		"stylus": "0.54.5",
 | 
			
		||||
		"stylus-loader": "3.0.2",
 | 
			
		||||
		"summaly": "2.3.0",
 | 
			
		||||
		"systeminformation": "4.14.3",
 | 
			
		||||
		"systeminformation": "4.14.4",
 | 
			
		||||
		"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.107.0",
 | 
			
		||||
		"tinycolor2": "1.4.1",
 | 
			
		||||
		"tmp": "0.1.0",
 | 
			
		||||
		"ts-loader": "5.3.3",
 | 
			
		||||
@@ -231,10 +231,10 @@
 | 
			
		||||
		"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",
 | 
			
		||||
		"url-loader": "2.1.0",
 | 
			
		||||
		"uuid": "3.3.2",
 | 
			
		||||
		"v-animate-css": "0.0.3",
 | 
			
		||||
		"v-debounce": "0.1.2",
 | 
			
		||||
@@ -242,13 +242,13 @@
 | 
			
		||||
		"vue-color": "2.7.0",
 | 
			
		||||
		"vue-content-loading": "1.6.0",
 | 
			
		||||
		"vue-cropperjs": "4.0.0",
 | 
			
		||||
		"vue-i18n": "8.12.0",
 | 
			
		||||
		"vue-i18n": "8.14.0",
 | 
			
		||||
		"vue-js-modal": "1.3.31",
 | 
			
		||||
		"vue-json-pretty": "1.6.0",
 | 
			
		||||
		"vue-loader": "15.7.0",
 | 
			
		||||
		"vue-loader": "15.7.1",
 | 
			
		||||
		"vue-marquee-text-component": "1.1.1",
 | 
			
		||||
		"vue-prism-component": "1.1.1",
 | 
			
		||||
		"vue-router": "3.0.7",
 | 
			
		||||
		"vue-router": "3.1.2",
 | 
			
		||||
		"vue-sequential-entrance": "1.1.3",
 | 
			
		||||
		"vue-style-loader": "4.1.2",
 | 
			
		||||
		"vue-svg-inline-loader": "1.2.16",
 | 
			
		||||
@@ -258,10 +258,10 @@
 | 
			
		||||
		"vuex": "3.1.1",
 | 
			
		||||
		"vuex-persistedstate": "2.5.4",
 | 
			
		||||
		"web-push": "3.3.5",
 | 
			
		||||
		"webpack": "4.35.3",
 | 
			
		||||
		"webpack-cli": "3.3.5",
 | 
			
		||||
		"webpack": "4.39.2",
 | 
			
		||||
		"webpack-cli": "3.3.7",
 | 
			
		||||
		"websocket": "1.0.29",
 | 
			
		||||
		"ws": "7.1.0",
 | 
			
		||||
		"ws": "7.1.2",
 | 
			
		||||
		"xev": "2.0.1"
 | 
			
		||||
	},
 | 
			
		||||
	"devDependencies": {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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;
 | 
			
		||||
}
 | 
			
		||||
@@ -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,6 +22,8 @@ type Opts = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default (opts: Opts = {}) => ({
 | 
			
		||||
	i18n: i18n(),
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			showContent: false,
 | 
			
		||||
@@ -108,6 +112,7 @@ export default (opts: Opts = {}) => ({
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		reply(viaKeyboard = false) {
 | 
			
		||||
			pleaseLogin(this.$root);
 | 
			
		||||
			this.$root.$post({
 | 
			
		||||
				reply: this.appearNote,
 | 
			
		||||
				animation: !viaKeyboard,
 | 
			
		||||
@@ -118,6 +123,7 @@ export default (opts: Opts = {}) => ({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		renote(viaKeyboard = false) {
 | 
			
		||||
			pleaseLogin(this.$root);
 | 
			
		||||
			this.$root.$post({
 | 
			
		||||
				renote: this.appearNote,
 | 
			
		||||
				animation: !viaKeyboard,
 | 
			
		||||
@@ -134,6 +140,7 @@ export default (opts: Opts = {}) => ({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		react(viaKeyboard = false) {
 | 
			
		||||
			pleaseLogin(this.$root);
 | 
			
		||||
			this.blur();
 | 
			
		||||
			this.$root.new(MkReactionPicker, {
 | 
			
		||||
				source: this.$refs.reactButton,
 | 
			
		||||
@@ -159,6 +166,7 @@ export default (opts: Opts = {}) => ({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		favorite() {
 | 
			
		||||
			pleaseLogin(this.$root);
 | 
			
		||||
			this.$root.api('notes/favorites/create', {
 | 
			
		||||
				noteId: this.appearNote.id
 | 
			
		||||
			}).then(() => {
 | 
			
		||||
@@ -170,8 +178,16 @@ 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
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
@@ -195,6 +199,28 @@ export default (opts) => ({
 | 
			
		||||
					this.$emit('change-attached-files', this.files);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			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());
 | 
			
		||||
		});
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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>;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										324
									
								
								src/client/app/common/scripts/room/furnitures.json5
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										324
									
								
								src/client/app/common/scripts/room/furnitures.json5
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,324 @@
 | 
			
		||||
// 家具メタデータ
 | 
			
		||||
 | 
			
		||||
// 家具にはユーザーが設定できるプロパティを設定可能です:
 | 
			
		||||
//
 | 
			
		||||
// 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"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		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,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										696
									
								
								src/client/app/common/scripts/room/room.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										696
									
								
								src/client/app/common/scripts/room/room.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,696 @@
 | 
			
		||||
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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
	public canvas: HTMLCanvasElement;
 | 
			
		||||
 | 
			
		||||
	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.gammaOutput = true;
 | 
			
		||||
		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
 | 
			
		||||
 | 
			
		||||
		this.canvas = this.renderer.domElement;
 | 
			
		||||
		container.appendChild(this.renderer.domElement);
 | 
			
		||||
		//#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 outLight = new THREE.SpotLight(0xffffff, 0.4);
 | 
			
		||||
 | 
			
		||||
		outLight.position.set(9, 3, -2);
 | 
			
		||||
		outLight.castShadow = this.enableShadow;
 | 
			
		||||
		outLight.shadow.bias = -0.001; // アクネ、アーチファクト対策 その代わりピーターパンが発生する可能性がある
 | 
			
		||||
		outLight.shadow.mapSize.width = this.shadowQuality;
 | 
			
		||||
		outLight.shadow.mapSize.height = this.shadowQuality;
 | 
			
		||||
		outLight.shadow.camera.near = 6;
 | 
			
		||||
		outLight.shadow.camera.far = 15;
 | 
			
		||||
		outLight.shadow.camera.fov = 45;
 | 
			
		||||
 | 
			
		||||
		this.scene.add(outLight);
 | 
			
		||||
		//#endregion
 | 
			
		||||
 | 
			
		||||
		//#region Init a controller
 | 
			
		||||
		this.controls = new OrbitControls(this.camera, this.renderer.domElement);
 | 
			
		||||
 | 
			
		||||
		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.MeshLambertMaterial({
 | 
			
		||||
			emissive: 0x111111,
 | 
			
		||||
			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.renderer.domElement);
 | 
			
		||||
			this.scene.add(this.furnitureControl);
 | 
			
		||||
 | 
			
		||||
			// Hover highlight
 | 
			
		||||
			this.renderer.domElement.onmousemove = this.onmousemove;
 | 
			
		||||
 | 
			
		||||
			// Click
 | 
			
		||||
			this.renderer.domElement.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() {
 | 
			
		||||
		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() {
 | 
			
		||||
		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() {
 | 
			
		||||
		new GLTFLoader().load(`/assets/room/rooms/${this.roomInfo.roomType}/${this.roomInfo.roomType}.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 = 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;
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				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') {
 | 
			
		||||
				(child.material as THREE.MeshStandardMaterial).color.setHex(parseInt(this.roomInfo.carpetColor.substr(1), 16));
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	public 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;
 | 
			
		||||
 | 
			
		||||
				(child.material as THREE.MeshStandardMaterial).color.setHex(parseInt(val.substr(1), 16));
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	public 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.renderer.domElement.width) * 2 - 1;
 | 
			
		||||
		const y = -(((ev.clientY * window.devicePixelRatio) - rect.top) / this.renderer.domElement.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)) {
 | 
			
		||||
				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.renderer.domElement || ev.button !== 0) return;
 | 
			
		||||
 | 
			
		||||
		const rect = (ev.target as HTMLElement).getBoundingClientRect();
 | 
			
		||||
		const x = (((ev.clientX * window.devicePixelRatio) - rect.left) / this.renderer.domElement.width) * 2 - 1;
 | 
			
		||||
		const y = -(((ev.clientY * window.devicePixelRatio) - rect.top) / this.renderer.domElement.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);
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@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();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@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 updateCarpetColor(color: string) {
 | 
			
		||||
		this.roomInfo.carpetColor = color;
 | 
			
		||||
		this.applyCarpetColor();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@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);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
		};
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -52,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() {
 | 
			
		||||
@@ -62,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'
 | 
			
		||||
@@ -95,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
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 
 | 
			
		||||
@@ -159,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,
 | 
			
		||||
@@ -210,16 +218,14 @@ export default Vue.extend({
 | 
			
		||||
		this.carefulBot = this.$store.state.i.carefulBot;
 | 
			
		||||
		this.autoAcceptFollowed = this.$store.state.i.autoAcceptFollowed;
 | 
			
		||||
 | 
			
		||||
		if (this.$store.state.i.fields) {
 | 
			
		||||
			this.fieldName0 = this.$store.state.i.fields[0].name;
 | 
			
		||||
			this.fieldValue0 = this.$store.state.i.fields[0].value;
 | 
			
		||||
			this.fieldName1 = this.$store.state.i.fields[1].name;
 | 
			
		||||
			this.fieldValue1 = this.$store.state.i.fields[1].value;
 | 
			
		||||
			this.fieldName2 = this.$store.state.i.fields[2].name;
 | 
			
		||||
			this.fieldValue2 = this.$store.state.i.fields[2].value;
 | 
			
		||||
			this.fieldName3 = this.$store.state.i.fields[3].name;
 | 
			
		||||
			this.fieldValue3 = this.$store.state.i.fields[3].value;
 | 
			
		||||
		}
 | 
			
		||||
		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: {
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
@@ -158,6 +159,19 @@
 | 
			
		||||
					<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>
 | 
			
		||||
@@ -502,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 }); }
 | 
			
		||||
@@ -532,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 }); }
 | 
			
		||||
 
 | 
			
		||||
@@ -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';
 | 
			
		||||
 
 | 
			
		||||
@@ -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: {
 | 
			
		||||
 
 | 
			
		||||
@@ -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 });
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 });
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -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',
 | 
			
		||||
@@ -292,7 +292,7 @@ export default Vue.extend({
 | 
			
		||||
			});
 | 
			
		||||
			if (canceled) return;
 | 
			
		||||
 | 
			
		||||
			const id = uuid.v4();
 | 
			
		||||
			const id = uuid();
 | 
			
		||||
			this.content.push({ id, type });
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
@@ -316,7 +316,7 @@ export default Vue.extend({
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			const id = uuid.v4();
 | 
			
		||||
			const id = uuid();
 | 
			
		||||
			this.variables.push({ id, name, type: null });
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,9 @@ export default Vue.extend({
 | 
			
		||||
			icon: faStickyNote
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		document.title = this.$root.instanceName;
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		create() {
 | 
			
		||||
			this.$router.push(`/i/pages/new`);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										98
									
								
								src/client/app/common/views/pages/room/preview.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/client/app/common/views/pages/room/preview.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
			
		||||
<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
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	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 = 2 + this.objectHeight / 2;
 | 
			
		||||
			camera.position.z = Math.cos(timer) * 10;
 | 
			
		||||
			camera.position.x = Math.sin(timer) * 10;
 | 
			
		||||
			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;
 | 
			
		||||
			scene.add(obj);
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		render();
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										244
									
								
								src/client/app/common/views/pages/room/room.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										244
									
								
								src/client/app/common/views/pages/room/room.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,244 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="hveuntkp">
 | 
			
		||||
	<div class="controller" v-if="objectSelected">
 | 
			
		||||
		<section>
 | 
			
		||||
			<p class="name">{{ selectedFurnitureName }}</p>
 | 
			
		||||
			<x-preview ref="preview"/>
 | 
			
		||||
			<template v-if="selectedFurnitureInfo.props">
 | 
			
		||||
				<div v-for="k in Object.keys(selectedFurnitureInfo.props)" :key="k">
 | 
			
		||||
					<p>{{ k }}</p>
 | 
			
		||||
					<template v-if="selectedFurnitureInfo.props[k] === 'image'">
 | 
			
		||||
						<ui-button @click="chooseImage(k)">{{ $t('chooseImage') }}</ui-button>
 | 
			
		||||
					</template>
 | 
			
		||||
					<template v-else-if="selectedFurnitureInfo.props[k] === 'color'">
 | 
			
		||||
						<input type="color" :value="selectedFurnitureProps ? selectedFurnitureProps[k] : null" @change="updateColor(k, $event)"/>
 | 
			
		||||
					</template>
 | 
			
		||||
				</div>
 | 
			
		||||
			</template>
 | 
			
		||||
		</section>
 | 
			
		||||
		<section>
 | 
			
		||||
			<ui-button @click="translate()" :primary="isTranslateMode"><fa :icon="faArrowsAlt"/> {{ $t('translate') }}</ui-button>
 | 
			
		||||
			<ui-button @click="rotate()" :primary="isRotateMode"><fa :icon="faUndo"/> {{ $t('rotate') }}</ui-button>
 | 
			
		||||
			<ui-button v-if="isTranslateMode || isRotateMode" @click="exit()"><fa :icon="faBan"/> {{ $t('exit') }}</ui-button>
 | 
			
		||||
		</section>
 | 
			
		||||
		<section>
 | 
			
		||||
			<ui-button @click="remove()"><fa :icon="faTrashAlt"/> {{ $t('remove') }}</ui-button>
 | 
			
		||||
		</section>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class="menu" v-if="isMyRoom">
 | 
			
		||||
		<section>
 | 
			
		||||
			<ui-button @click="add()"><fa :icon="faBoxOpen"/> {{ $t('add-furniture') }}</ui-button>
 | 
			
		||||
		</section>
 | 
			
		||||
		<section>
 | 
			
		||||
			<ui-select :value="roomType" @input="updateRoomType($event)">
 | 
			
		||||
				<template #label>{{ $t('room-type') }}</template>
 | 
			
		||||
				<option value="default">{{ $t('rooms.default') }}</option>
 | 
			
		||||
				<option value="washitsu">{{ $t('rooms.washitsu') }}</option>
 | 
			
		||||
			</ui-select>
 | 
			
		||||
			<label v-if="roomType === 'default'">
 | 
			
		||||
				<span>{{ $t('carpet-color') }}</span>
 | 
			
		||||
				<input type="color" :value="carpetColor" @change="updateCarpetColor($event)"/>
 | 
			
		||||
			</label>
 | 
			
		||||
		</section>
 | 
			
		||||
		<section>
 | 
			
		||||
			<ui-button primary @click="save()"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
 | 
			
		||||
		</section>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../../i18n';
 | 
			
		||||
import { Room } from '../../../scripts/room/room';
 | 
			
		||||
import parseAcct from '../../../../../../misc/acct/parse';
 | 
			
		||||
import XPreview from './preview.vue';
 | 
			
		||||
const storeItems = require('../../../scripts/room/furnitures.json5');
 | 
			
		||||
import { faBoxOpen, faUndo, faArrowsAlt, faBan } from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import { faSave, faTrashAlt } from '@fortawesome/free-regular-svg-icons';
 | 
			
		||||
import { query as urlQuery } from '../../../../../../prelude/url';
 | 
			
		||||
 | 
			
		||||
let room: Room;
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n('room'),
 | 
			
		||||
 | 
			
		||||
	components: {
 | 
			
		||||
		XPreview
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
		acct: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: true
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			objectSelected: false,
 | 
			
		||||
			selectedFurnitureName: null,
 | 
			
		||||
			selectedFurnitureInfo: null,
 | 
			
		||||
			selectedFurnitureProps: null,
 | 
			
		||||
			roomType: null,
 | 
			
		||||
			carpetColor: null,
 | 
			
		||||
			isTranslateMode: false,
 | 
			
		||||
			isRotateMode: false,
 | 
			
		||||
			isMyRoom: false,
 | 
			
		||||
			faBoxOpen, faSave, faTrashAlt, faUndo, faArrowsAlt, faBan,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	async mounted() {
 | 
			
		||||
		const user = await this.$root.api('users/show', {
 | 
			
		||||
			...parseAcct(this.acct)
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.isMyRoom = this.$store.getters.isSignedIn && this.$store.state.i.id === user.id;
 | 
			
		||||
 | 
			
		||||
		const roomInfo = await this.$root.api('room/show', {
 | 
			
		||||
			userId: user.id
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.roomType = roomInfo.roomType;
 | 
			
		||||
		this.carpetColor = roomInfo.carpetColor;
 | 
			
		||||
 | 
			
		||||
		room = new Room(user, this.isMyRoom, roomInfo, this.$el, {
 | 
			
		||||
			graphicsQuality: this.$store.state.device.roomGraphicsQuality,
 | 
			
		||||
			onChangeSelect: obj => {
 | 
			
		||||
				this.objectSelected = obj != null;
 | 
			
		||||
				if (obj) {
 | 
			
		||||
					const f = room.findFurnitureById(obj.name);
 | 
			
		||||
					this.selectedFurnitureName = this.$t('furnitures.' + f.type);
 | 
			
		||||
					this.selectedFurnitureInfo = storeItems.find(x => x.id === f.type);
 | 
			
		||||
					this.selectedFurnitureProps = f.props
 | 
			
		||||
						? JSON.parse(JSON.stringify(f.props)) // Disable reactivity
 | 
			
		||||
						: null;
 | 
			
		||||
					this.$nextTick(() => {
 | 
			
		||||
						this.$refs.preview.selected(obj);
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			useOrthographicCamera: this.$store.state.device.roomUseOrthographicCamera
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		async add() {
 | 
			
		||||
			const { canceled, result: id } = await this.$root.dialog({
 | 
			
		||||
				type: null,
 | 
			
		||||
				title: this.$t('add-furniture'),
 | 
			
		||||
				select: {
 | 
			
		||||
					items: storeItems.map(item => ({
 | 
			
		||||
						value: item.id, text: this.$t('furnitures.' + item.id)
 | 
			
		||||
					}))
 | 
			
		||||
				},
 | 
			
		||||
				showCancelButton: true
 | 
			
		||||
			});
 | 
			
		||||
			if (canceled) return;
 | 
			
		||||
			room.addFurniture(id);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		remove() {
 | 
			
		||||
			this.isTranslateMode = false;
 | 
			
		||||
			this.isRotateMode = false;
 | 
			
		||||
			room.removeFurniture();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		save() {
 | 
			
		||||
			this.$root.api('room/update', {
 | 
			
		||||
				room: room.getRoomInfo()
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		chooseImage(key) {
 | 
			
		||||
			this.$chooseDriveFile({
 | 
			
		||||
				multiple: false
 | 
			
		||||
			}).then(file => {
 | 
			
		||||
				room.updateProp(key, `/proxy/?${urlQuery({ url: file.thumbnailUrl })}`);
 | 
			
		||||
				this.$refs.preview.selected(room.getSelectedObject());
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		updateColor(key, ev) {
 | 
			
		||||
			room.updateProp(key, ev.target.value);
 | 
			
		||||
			this.$refs.preview.selected(room.getSelectedObject());
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		updateCarpetColor(ev) {
 | 
			
		||||
			room.updateCarpetColor(ev.target.value);
 | 
			
		||||
			this.carpetColor = ev.target.value;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		updateRoomType(type) {
 | 
			
		||||
			room.changeRoomType(type);
 | 
			
		||||
			this.roomType = type;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		translate() {
 | 
			
		||||
			if (this.isTranslateMode) {
 | 
			
		||||
				this.exit();
 | 
			
		||||
			} else {
 | 
			
		||||
				this.isRotateMode = false;
 | 
			
		||||
				this.isTranslateMode = true;
 | 
			
		||||
				room.enterTransformMode('translate');
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		rotate() {
 | 
			
		||||
			if (this.isRotateMode) {
 | 
			
		||||
				this.exit();
 | 
			
		||||
			} else {
 | 
			
		||||
				this.isTranslateMode = false;
 | 
			
		||||
				this.isRotateMode = true;
 | 
			
		||||
				room.enterTransformMode('rotate');
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		exit() {
 | 
			
		||||
			this.isTranslateMode = false;
 | 
			
		||||
			this.isRotateMode = false;
 | 
			
		||||
			room.exitTransformMode();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.hveuntkp
 | 
			
		||||
	> .controller
 | 
			
		||||
	> .menu
 | 
			
		||||
		position fixed
 | 
			
		||||
		z-index 1
 | 
			
		||||
		padding 16px
 | 
			
		||||
		background var(--face)
 | 
			
		||||
		color var(--text)
 | 
			
		||||
 | 
			
		||||
		> section
 | 
			
		||||
			padding 16px 0
 | 
			
		||||
 | 
			
		||||
			&:first-child
 | 
			
		||||
				padding-top 0
 | 
			
		||||
 | 
			
		||||
			&:last-child
 | 
			
		||||
				padding-bottom 0
 | 
			
		||||
 | 
			
		||||
			&:not(:last-child)
 | 
			
		||||
				border-bottom solid 1px var(--faceDivider)
 | 
			
		||||
 | 
			
		||||
	> .controller
 | 
			
		||||
		top 16px
 | 
			
		||||
		left 16px
 | 
			
		||||
		width 256px
 | 
			
		||||
 | 
			
		||||
		> section
 | 
			
		||||
			> .name
 | 
			
		||||
				margin 0
 | 
			
		||||
 | 
			
		||||
	> .menu
 | 
			
		||||
		top 16px
 | 
			
		||||
		right 16px
 | 
			
		||||
		width 256px
 | 
			
		||||
	
 | 
			
		||||
</style>
 | 
			
		||||
@@ -62,6 +62,8 @@ export default Vue.extend({
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		document.title = this.$root.instanceName;
 | 
			
		||||
 | 
			
		||||
		this.$root.api('users/groups/owned').then(groups => {
 | 
			
		||||
			this.ownedGroups = groups;
 | 
			
		||||
		});
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,8 @@ export default Vue.extend({
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		document.title = this.$root.instanceName;
 | 
			
		||||
		
 | 
			
		||||
		this.$root.api('users/lists/list').then(lists => {
 | 
			
		||||
			this.fetching = false;
 | 
			
		||||
			this.lists = lists;
 | 
			
		||||
 
 | 
			
		||||
@@ -71,7 +71,7 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import define from '../../../common/define-widget';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { v4 as uuid } from 'uuid';
 | 
			
		||||
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'posts-monitor',
 | 
			
		||||
 
 | 
			
		||||
@@ -63,7 +63,7 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { v4 as uuid } from 'uuid';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: ['connection'],
 | 
			
		||||
 
 | 
			
		||||
@@ -63,7 +63,10 @@ init(async (launch, os) => {
 | 
			
		||||
					this.$root.newAsync(() => import('./views/components/post-form-window.vue').then(m => m.default), {
 | 
			
		||||
						reply: o.reply,
 | 
			
		||||
						mention: o.mention,
 | 
			
		||||
						animation: o.animation == null ? true : o.animation
 | 
			
		||||
						animation: o.animation == null ? true : o.animation,
 | 
			
		||||
						initialText: o.initialText,
 | 
			
		||||
						instant: o.instant,
 | 
			
		||||
						initialNote: o.initialNote,
 | 
			
		||||
					}).then(vm => {
 | 
			
		||||
						if (o.cb) vm.$once('closed', o.cb);
 | 
			
		||||
					});
 | 
			
		||||
@@ -184,6 +187,7 @@ init(async (launch, os) => {
 | 
			
		||||
			{ path: '/i/drive/folder/:folder', component: MkDrive },
 | 
			
		||||
			{ path: '/i/settings', component: MkSettings },
 | 
			
		||||
			{ path: '/selectdrive', component: MkSelectDrive },
 | 
			
		||||
			{ path: '/@:acct/room', props: true, component: () => import('../common/views/pages/room/room.vue').then(m => m.default) },
 | 
			
		||||
			{ path: '/share', component: MkShare },
 | 
			
		||||
			{ path: '/games/reversi/:game?', component: MkReversi },
 | 
			
		||||
			{ path: '/authorize-follow', component: MkFollow },
 | 
			
		||||
 
 | 
			
		||||
@@ -11,17 +11,16 @@
 | 
			
		||||
		<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition" tag="div">
 | 
			
		||||
			<template v-for="(notification, i) in _notifications">
 | 
			
		||||
				<div class="notification" :class="notification.type" :key="notification.id">
 | 
			
		||||
					<mk-time :time="notification.createdAt"/>
 | 
			
		||||
 | 
			
		||||
					<template v-if="notification.type == 'reaction'">
 | 
			
		||||
						<mk-avatar class="avatar" :user="notification.user"/>
 | 
			
		||||
						<div class="text">
 | 
			
		||||
							<p>
 | 
			
		||||
								<mk-reaction-icon :reaction="notification.reaction"/>
 | 
			
		||||
								<router-link :to="notification.user | userPage" v-user-preview="notification.user.id">
 | 
			
		||||
							<header>
 | 
			
		||||
								<mk-reaction-icon :reaction="notification.reaction" class="icon"/>
 | 
			
		||||
								<router-link :to="notification.user | userPage" v-user-preview="notification.user.id" class="name">
 | 
			
		||||
									<mk-user-name :user="notification.user"/>
 | 
			
		||||
								</router-link>
 | 
			
		||||
							</p>
 | 
			
		||||
								<mk-time :time="notification.createdAt"/>
 | 
			
		||||
							</header>
 | 
			
		||||
							<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
 | 
			
		||||
								<fa icon="quote-left"/>
 | 
			
		||||
									<mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :custom-emojis="notification.note.emojis"/>
 | 
			
		||||
@@ -33,11 +32,13 @@
 | 
			
		||||
					<template v-if="notification.type == 'renote'">
 | 
			
		||||
						<mk-avatar class="avatar" :user="notification.note.user"/>
 | 
			
		||||
						<div class="text">
 | 
			
		||||
							<p><fa icon="retweet"/>
 | 
			
		||||
								<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">
 | 
			
		||||
							<header>
 | 
			
		||||
								<fa icon="retweet" class="icon"/>
 | 
			
		||||
								<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId" class="name">
 | 
			
		||||
									<mk-user-name :user="notification.note.user"/>
 | 
			
		||||
								</router-link>
 | 
			
		||||
							</p>
 | 
			
		||||
								<mk-time :time="notification.createdAt"/>
 | 
			
		||||
							</header>
 | 
			
		||||
							<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note.renote)">
 | 
			
		||||
								<fa icon="quote-left"/>
 | 
			
		||||
									<mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="true" :custom-emojis="notification.note.renote.emojis"/>
 | 
			
		||||
@@ -49,11 +50,13 @@
 | 
			
		||||
					<template v-if="notification.type == 'quote'">
 | 
			
		||||
						<mk-avatar class="avatar" :user="notification.note.user"/>
 | 
			
		||||
						<div class="text">
 | 
			
		||||
							<p><fa icon="quote-left"/>
 | 
			
		||||
								<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">
 | 
			
		||||
							<header>
 | 
			
		||||
								<fa icon="quote-left" class="icon"/>
 | 
			
		||||
								<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId" class="name">
 | 
			
		||||
									<mk-user-name :user="notification.note.user"/>
 | 
			
		||||
								</router-link>
 | 
			
		||||
							</p>
 | 
			
		||||
								<mk-time :time="notification.createdAt"/>
 | 
			
		||||
							</header>
 | 
			
		||||
							<router-link class="note-preview" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
 | 
			
		||||
								<mfm :text="getNoteSummary(notification.note)" :plain="true" :custom-emojis="notification.note.emojis"/>
 | 
			
		||||
							</router-link>
 | 
			
		||||
@@ -63,33 +66,39 @@
 | 
			
		||||
					<template v-if="notification.type == 'follow'">
 | 
			
		||||
						<mk-avatar class="avatar" :user="notification.user"/>
 | 
			
		||||
						<div class="text">
 | 
			
		||||
							<p><fa icon="user-plus"/>
 | 
			
		||||
								<router-link :to="notification.user | userPage" v-user-preview="notification.user.id">
 | 
			
		||||
							<header>
 | 
			
		||||
								<fa icon="user-plus" class="icon"/>
 | 
			
		||||
								<router-link :to="notification.user | userPage" v-user-preview="notification.user.id" class="name">
 | 
			
		||||
									<mk-user-name :user="notification.user"/>
 | 
			
		||||
								</router-link>
 | 
			
		||||
							</p>
 | 
			
		||||
								<mk-time :time="notification.createdAt"/>
 | 
			
		||||
							</header>
 | 
			
		||||
						</div>
 | 
			
		||||
					</template>
 | 
			
		||||
 | 
			
		||||
					<template v-if="notification.type == 'receiveFollowRequest'">
 | 
			
		||||
						<mk-avatar class="avatar" :user="notification.user"/>
 | 
			
		||||
						<div class="text">
 | 
			
		||||
							<p><fa icon="user-clock"/>
 | 
			
		||||
								<router-link :to="notification.user | userPage" v-user-preview="notification.user.id">
 | 
			
		||||
							<header>
 | 
			
		||||
								<fa icon="user-clock" class="icon"/>
 | 
			
		||||
								<router-link :to="notification.user | userPage" v-user-preview="notification.user.id" class="name">
 | 
			
		||||
									<mk-user-name :user="notification.user"/>
 | 
			
		||||
								</router-link>
 | 
			
		||||
							</p>
 | 
			
		||||
								<mk-time :time="notification.createdAt"/>
 | 
			
		||||
							</header>
 | 
			
		||||
						</div>
 | 
			
		||||
					</template>
 | 
			
		||||
 | 
			
		||||
					<template v-if="notification.type == 'reply'">
 | 
			
		||||
						<mk-avatar class="avatar" :user="notification.note.user"/>
 | 
			
		||||
						<div class="text">
 | 
			
		||||
							<p><fa icon="reply"/>
 | 
			
		||||
								<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">
 | 
			
		||||
							<header>
 | 
			
		||||
								<fa icon="reply" class="icon"/>
 | 
			
		||||
								<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId" class="name">
 | 
			
		||||
									<mk-user-name :user="notification.note.user"/>
 | 
			
		||||
								</router-link>
 | 
			
		||||
							</p>
 | 
			
		||||
								<mk-time :time="notification.createdAt"/>
 | 
			
		||||
							</header>
 | 
			
		||||
							<router-link class="note-preview" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
 | 
			
		||||
								<mfm :text="getNoteSummary(notification.note)" :plain="true" :custom-emojis="notification.note.emojis"/>
 | 
			
		||||
							</router-link>
 | 
			
		||||
@@ -99,11 +108,13 @@
 | 
			
		||||
					<template v-if="notification.type == 'mention'">
 | 
			
		||||
						<mk-avatar class="avatar" :user="notification.note.user"/>
 | 
			
		||||
						<div class="text">
 | 
			
		||||
							<p><fa icon="at"/>
 | 
			
		||||
								<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">
 | 
			
		||||
							<header>
 | 
			
		||||
								<fa icon="at" class="icon"/>
 | 
			
		||||
								<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId" class="name">
 | 
			
		||||
									<mk-user-name :user="notification.note.user"/>
 | 
			
		||||
								</router-link>
 | 
			
		||||
							</p>
 | 
			
		||||
								<mk-time :time="notification.createdAt"/>
 | 
			
		||||
							</header>
 | 
			
		||||
							<router-link class="note-preview" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
 | 
			
		||||
								<mfm :text="getNoteSummary(notification.note)" :plain="true" :custom-emojis="notification.note.emojis"/>
 | 
			
		||||
							</router-link>
 | 
			
		||||
@@ -113,9 +124,13 @@
 | 
			
		||||
					<template v-if="notification.type == 'pollVote'">
 | 
			
		||||
						<mk-avatar class="avatar" :user="notification.user"/>
 | 
			
		||||
						<div class="text">
 | 
			
		||||
							<p><fa icon="chart-pie"/><router-link :to="notification.user | userPage" v-user-preview="notification.user.id">
 | 
			
		||||
								<mk-user-name :user="notification.user"/>
 | 
			
		||||
							</router-link></p>
 | 
			
		||||
							<header>
 | 
			
		||||
								<fa icon="chart-pie" class="icon"/>
 | 
			
		||||
								<router-link :to="notification.user | userPage" v-user-preview="notification.user.id" class="name">
 | 
			
		||||
									<mk-user-name :user="notification.user"/>
 | 
			
		||||
								</router-link>
 | 
			
		||||
								<mk-time :time="notification.createdAt"/>
 | 
			
		||||
							</header>
 | 
			
		||||
							<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
 | 
			
		||||
								<fa icon="quote-left"/>
 | 
			
		||||
									<mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :custom-emojis="notification.note.emojis"/>
 | 
			
		||||
@@ -150,9 +165,18 @@ export default Vue.extend({
 | 
			
		||||
	i18n: i18n(),
 | 
			
		||||
 | 
			
		||||
	mixins: [
 | 
			
		||||
		paging({}),
 | 
			
		||||
		paging({
 | 
			
		||||
			isContainer: true
 | 
			
		||||
		}),
 | 
			
		||||
	],
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
		type: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: false
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			connection: null,
 | 
			
		||||
@@ -160,6 +184,9 @@ export default Vue.extend({
 | 
			
		||||
			pagination: {
 | 
			
		||||
				endpoint: 'i/notifications',
 | 
			
		||||
				limit: 10,
 | 
			
		||||
				params: () => ({
 | 
			
		||||
					includeTypes: this.type ? [this.type] : undefined
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
@@ -176,6 +203,12 @@ export default Vue.extend({
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	watch: {
 | 
			
		||||
		type() {
 | 
			
		||||
			this.reload();
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.connection = this.$root.stream.useSharedConnection('main');
 | 
			
		||||
		this.connection.on('notification', this.onNotification);
 | 
			
		||||
@@ -225,15 +258,6 @@ export default Vue.extend({
 | 
			
		||||
				&:last-child
 | 
			
		||||
					border-bottom none
 | 
			
		||||
 | 
			
		||||
				> .mk-time
 | 
			
		||||
					display inline
 | 
			
		||||
					position absolute
 | 
			
		||||
					top 16px
 | 
			
		||||
					right 12px
 | 
			
		||||
					vertical-align top
 | 
			
		||||
					color var(--noteHeaderInfo)
 | 
			
		||||
					font-size small
 | 
			
		||||
 | 
			
		||||
				&:after
 | 
			
		||||
					content ""
 | 
			
		||||
					display block
 | 
			
		||||
@@ -254,12 +278,23 @@ export default Vue.extend({
 | 
			
		||||
					width calc(100% - 36px)
 | 
			
		||||
					padding-left 8px
 | 
			
		||||
 | 
			
		||||
					p
 | 
			
		||||
						margin 0
 | 
			
		||||
					> header
 | 
			
		||||
						display flex
 | 
			
		||||
						align-items baseline
 | 
			
		||||
						white-space nowrap
 | 
			
		||||
 | 
			
		||||
						[data-icon], .mk-reaction-icon
 | 
			
		||||
						> .icon
 | 
			
		||||
							margin-right 4px
 | 
			
		||||
 | 
			
		||||
						> .name
 | 
			
		||||
							overflow hidden
 | 
			
		||||
							text-overflow ellipsis
 | 
			
		||||
 | 
			
		||||
						> .mk-time
 | 
			
		||||
							margin-left auto
 | 
			
		||||
							color var(--noteHeaderInfo)
 | 
			
		||||
							font-size 0.9em
 | 
			
		||||
 | 
			
		||||
				.note-preview
 | 
			
		||||
					color var(--noteText)
 | 
			
		||||
					display inline-block
 | 
			
		||||
@@ -280,20 +315,24 @@ export default Vue.extend({
 | 
			
		||||
						display inline-block
 | 
			
		||||
						margin-right 3px
 | 
			
		||||
 | 
			
		||||
				&.reaction
 | 
			
		||||
					.text header
 | 
			
		||||
						align-items normal
 | 
			
		||||
 | 
			
		||||
				&.renote, &.quote
 | 
			
		||||
					.text p [data-icon]
 | 
			
		||||
					.text header [data-icon]
 | 
			
		||||
						color #77B255
 | 
			
		||||
 | 
			
		||||
				&.follow
 | 
			
		||||
					.text p [data-icon]
 | 
			
		||||
					.text header [data-icon]
 | 
			
		||||
						color #53c7ce
 | 
			
		||||
 | 
			
		||||
				&.receiveFollowRequest
 | 
			
		||||
					.text p [data-icon]
 | 
			
		||||
					.text header [data-icon]
 | 
			
		||||
						color #888
 | 
			
		||||
 | 
			
		||||
				&.reply, &.mention
 | 
			
		||||
					.text p [data-icon]
 | 
			
		||||
					.text header [data-icon]
 | 
			
		||||
						color #555
 | 
			
		||||
 | 
			
		||||
			> .date
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,8 @@
 | 
			
		||||
			<span class="icon" v-if="geo"><fa icon="map-marker-alt"/></span>
 | 
			
		||||
			<span v-if="!reply">{{ $t('note') }}</span>
 | 
			
		||||
			<span v-if="reply">{{ $t('reply') }}</span>
 | 
			
		||||
			<span class="count" v-if="files.length != 0">{{ this.$t('attaches').replace('{}', files.length) }}</span>
 | 
			
		||||
			<span class="count" v-if="uploadings.length != 0">{{ this.$t('uploading-media').replace('{}', uploadings.length) }}<mk-ellipsis/></span>
 | 
			
		||||
			<span class="count" v-if="files.length != 0">{{ $t('attaches').replace('{}', files.length) }}</span>
 | 
			
		||||
			<span class="count" v-if="uploadings.length != 0">{{ $t('uploading-media').replace('{}', uploadings.length) }}<mk-ellipsis/></span>
 | 
			
		||||
		</span>
 | 
			
		||||
	</template>
 | 
			
		||||
 | 
			
		||||
@@ -15,6 +15,10 @@
 | 
			
		||||
		<x-post-form ref="form"
 | 
			
		||||
			:reply="reply"
 | 
			
		||||
			:mention="mention"
 | 
			
		||||
			:initial-text="initialText"
 | 
			
		||||
			:initial-note="initialNote"
 | 
			
		||||
			:instant="instant"
 | 
			
		||||
 | 
			
		||||
			@posted="onPosted"
 | 
			
		||||
			@change-uploadings="onChangeUploadings"
 | 
			
		||||
			@change-attached-files="onChangeFiles"
 | 
			
		||||
@@ -50,7 +54,23 @@ export default Vue.extend({
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: true
 | 
			
		||||
		}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		initialText: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		initialNote: {
 | 
			
		||||
			type: Object,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		instant: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
 
 | 
			
		||||
@@ -56,6 +56,13 @@
 | 
			
		||||
						<i><fa icon="angle-right"/></i>
 | 
			
		||||
					</router-link>
 | 
			
		||||
				</li>
 | 
			
		||||
				<li>
 | 
			
		||||
					<router-link :to="`/@${ $store.state.i.username }/room`">
 | 
			
		||||
						<i><fa :icon="faDoorOpen" fixed-width/></i>
 | 
			
		||||
						<span>{{ $t('room') }}</span>
 | 
			
		||||
						<i><fa icon="angle-right"/></i>
 | 
			
		||||
					</router-link>
 | 
			
		||||
				</li>
 | 
			
		||||
			</ul>
 | 
			
		||||
			<ul>
 | 
			
		||||
				<li>
 | 
			
		||||
@@ -106,7 +113,7 @@ import i18n from '../../../i18n';
 | 
			
		||||
// import MkSettingsWindow from './settings-window.vue';
 | 
			
		||||
import MkDriveWindow from './drive-window.vue';
 | 
			
		||||
import contains from '../../../common/scripts/contains';
 | 
			
		||||
import { faHome, faColumns, faUsers } from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import { faHome, faColumns, faUsers, faDoorOpen } from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import { faMoon, faSun, faStickyNote } from '@fortawesome/free-regular-svg-icons';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
@@ -114,7 +121,7 @@ export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			isOpen: false,
 | 
			
		||||
			faHome, faColumns, faMoon, faSun, faStickyNote, faUsers
 | 
			
		||||
			faHome, faColumns, faMoon, faSun, faStickyNote, faUsers, faDoorOpen
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	computed: {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
		<div class="banner" :style="u.bannerUrl ? `background-image: url(${u.bannerUrl})` : ''"></div>
 | 
			
		||||
		<mk-avatar class="avatar" :user="u" :disable-preview="true"/>
 | 
			
		||||
		<div class="title">
 | 
			
		||||
			<router-link class="name" :to="u | userPage"><mk-user-name :user="u"/></router-link>
 | 
			
		||||
			<router-link class="name" :to="u | userPage"><mk-user-name :user="u" :nowrap="false"/></router-link>
 | 
			
		||||
			<p class="username"><mk-acct :user="u"/></p>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="description">
 | 
			
		||||
 
 | 
			
		||||
@@ -79,7 +79,7 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import * as XDraggable from 'vuedraggable';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { v4 as uuid } from 'uuid';
 | 
			
		||||
import XWelcome from '../pages/welcome.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
@@ -224,6 +224,8 @@ export default Vue.extend({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		addWidget() {
 | 
			
		||||
			if(this.widgetAdderSelected == null) return;
 | 
			
		||||
 | 
			
		||||
			this.$store.commit('addHomeWidget', {
 | 
			
		||||
				name: this.widgetAdderSelected,
 | 
			
		||||
				id: uuid(),
 | 
			
		||||
 
 | 
			
		||||
@@ -94,6 +94,8 @@ export default Vue.extend({
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		document.title = this.$root.instanceName;
 | 
			
		||||
 | 
			
		||||
		(this.$refs.tl as any).$once('loaded', () => {
 | 
			
		||||
			this.$emit('loaded');
 | 
			
		||||
		});
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
		<div class="fade"></div>
 | 
			
		||||
		<div class="title">
 | 
			
		||||
			<p class="name">
 | 
			
		||||
				<mk-user-name :user="user"/>
 | 
			
		||||
				<mk-user-name :user="user" :nowrap="false"/>
 | 
			
		||||
			</p>
 | 
			
		||||
			<div>
 | 
			
		||||
				<span class="username"><mk-acct :user="user" :detail="true" /></span>
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,9 @@ export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XSettings: () => import('../components/settings.vue').then(m => m.default)
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		document.title = this.$root.instanceName;
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mkw-notifications">
 | 
			
		||||
	<ui-container :show-header="!props.compact">
 | 
			
		||||
		<template #header><fa :icon="['far', 'bell']"/>{{ $t('title') }}</template>
 | 
			
		||||
		<!-- <button #func :title="$t('title')" @click="settings"><fa icon="cog"/></button> -->
 | 
			
		||||
		<template #header><fa :icon="['far', 'bell']"/>{{ props.type === 'all' ? $t('title') : $t('@.notification-types.' + props.type) }}</template>
 | 
			
		||||
		<template #func><button :title="$t('@.notification-type')" @click="settings"><fa icon="cog"/></button></template>
 | 
			
		||||
 | 
			
		||||
		<mk-notifications :class="$style.notifications"/>
 | 
			
		||||
		<mk-notifications :class="$style.notifications" :type="props.type === 'all' ? null : props.type"/>
 | 
			
		||||
	</ui-container>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -16,13 +16,28 @@ import i18n from '../../../i18n';
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'notifications',
 | 
			
		||||
	props: () => ({
 | 
			
		||||
		compact: false
 | 
			
		||||
		compact: false,
 | 
			
		||||
		type: 'all'
 | 
			
		||||
	})
 | 
			
		||||
}).extend({
 | 
			
		||||
	i18n: i18n('desktop/views/widgets/notifications.vue'),
 | 
			
		||||
	methods: {
 | 
			
		||||
		settings() {
 | 
			
		||||
			alert('not implemented yet');
 | 
			
		||||
			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.props.type,
 | 
			
		||||
				},
 | 
			
		||||
				showCancelButton: true
 | 
			
		||||
			}).then(({ canceled, result: type }) => {
 | 
			
		||||
				if (canceled) return;
 | 
			
		||||
				this.props.type = type;
 | 
			
		||||
				this.save();
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		func() {
 | 
			
		||||
			this.props.compact = !this.props.compact;
 | 
			
		||||
 
 | 
			
		||||
@@ -125,7 +125,8 @@ import {
 | 
			
		||||
	faMapMarker,
 | 
			
		||||
	faRobot,
 | 
			
		||||
	faHourglassHalf,
 | 
			
		||||
	faGavel
 | 
			
		||||
	faGavel,
 | 
			
		||||
	faUndoAlt,
 | 
			
		||||
} from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
@@ -258,6 +259,7 @@ library.add(
 | 
			
		||||
	faRobot,
 | 
			
		||||
	faHourglassHalf,
 | 
			
		||||
	faGavel,
 | 
			
		||||
	faUndoAlt,
 | 
			
		||||
 | 
			
		||||
	farBell,
 | 
			
		||||
	farEnvelope,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import autobind from 'autobind-decorator';
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import { EventEmitter } from 'eventemitter3';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { v4 as uuid } from 'uuid';
 | 
			
		||||
 | 
			
		||||
import initStore from './store';
 | 
			
		||||
import { apiUrl, version, locale } from './config';
 | 
			
		||||
@@ -28,7 +28,12 @@ export default class MiOS extends EventEmitter {
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	public get instanceName() {
 | 
			
		||||
		return this.meta ? (this.meta.data.name || 'Misskey') : 'Misskey';
 | 
			
		||||
		const siteName = document.querySelector('meta[property="og:site_name"]') as HTMLMetaElement;
 | 
			
		||||
		if (siteName && siteName.content) {
 | 
			
		||||
			return siteName.content;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return 'Misskey';
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private isMetaFetching = false;
 | 
			
		||||
@@ -174,7 +179,7 @@ export default class MiOS extends EventEmitter {
 | 
			
		||||
			// Init service worker
 | 
			
		||||
			if (this.shouldRegisterSw) {
 | 
			
		||||
				this.getMeta().then(data => {
 | 
			
		||||
					this.registerSw(data.swPublickey);
 | 
			
		||||
					if (data.swPublickey) this.registerSw(data.swPublickey);
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
@@ -291,7 +296,7 @@ export default class MiOS extends EventEmitter {
 | 
			
		||||
	 * Register service worker
 | 
			
		||||
	 */
 | 
			
		||||
	@autobind
 | 
			
		||||
	private registerSw(swPublickey) {
 | 
			
		||||
	private registerSw(swPublickey: string) {
 | 
			
		||||
		// Check whether service worker and push manager supported
 | 
			
		||||
		const isSwSupported =
 | 
			
		||||
			('serviceWorker' in navigator) && ('PushManager' in window);
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ import MkIndex from './views/pages/index.vue';
 | 
			
		||||
import MkSignup from './views/pages/signup.vue';
 | 
			
		||||
import MkSelectDrive from './views/pages/selectdrive.vue';
 | 
			
		||||
import MkDrive from './views/pages/drive.vue';
 | 
			
		||||
import MkNotifications from './views/pages/notifications.vue';
 | 
			
		||||
import MkMessaging from './views/pages/messaging.vue';
 | 
			
		||||
import MkMessagingRoom from './views/pages/messaging-room.vue';
 | 
			
		||||
import MkNote from './views/pages/note.vue';
 | 
			
		||||
@@ -54,7 +55,10 @@ init((launch, os) => {
 | 
			
		||||
				const vm = this.$root.new(PostFormDialog, {
 | 
			
		||||
					reply: o.reply,
 | 
			
		||||
					mention: o.mention,
 | 
			
		||||
					renote: o.renote
 | 
			
		||||
					renote: o.renote,
 | 
			
		||||
					initialText: o.initialText,
 | 
			
		||||
					instant: o.instant,
 | 
			
		||||
					initialNote: o.initialNote,
 | 
			
		||||
				});
 | 
			
		||||
				vm.$once('cancel', recover);
 | 
			
		||||
				vm.$once('posted', recover);
 | 
			
		||||
@@ -144,6 +148,7 @@ init((launch, os) => {
 | 
			
		||||
			{ path: '/i/groups/:group', component: UI, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.group }) },
 | 
			
		||||
			{ path: '/i/follow-requests', name: 'follow-requests', component: UI, props: route => ({ component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }) },
 | 
			
		||||
			{ path: '/i/widgets', name: 'widgets', component: () => import('./views/pages/widgets.vue').then(m => m.default) },
 | 
			
		||||
			{ path: '/i/notifications', name: 'notifications', component: MkNotifications },
 | 
			
		||||
			{ path: '/i/messaging', name: 'messaging', component: MkMessaging },
 | 
			
		||||
			{ path: '/i/messaging/group/:group', component: MkMessagingRoom },
 | 
			
		||||
			{ path: '/i/messaging/:user', component: MkMessagingRoom },
 | 
			
		||||
@@ -166,6 +171,7 @@ init((launch, os) => {
 | 
			
		||||
			]},
 | 
			
		||||
			{ path: '/@:user/pages/:page', component: UI, props: route => ({ component: () => import('../common/views/pages/page.vue').then(m => m.default), pageName: route.params.page, username: route.params.user }) },
 | 
			
		||||
			{ path: '/@:user/pages/:pageName/view-source', component: UI, props: route => ({ component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), initUser: route.params.user, initPageName: route.params.pageName }) },
 | 
			
		||||
			{ path: '/@:acct/room', props: true, component: () => import('../common/views/pages/room/room.vue').then(m => m.default) },
 | 
			
		||||
			{ path: '/notes/:note', component: MkNote },
 | 
			
		||||
			{ path: '/authorize-follow', component: MkFollow },
 | 
			
		||||
			{ path: '*', component: MkNotFound }
 | 
			
		||||
 
 | 
			
		||||
@@ -100,6 +100,27 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.mk-notification
 | 
			
		||||
 | 
			
		||||
	&.wide
 | 
			
		||||
		> .notification
 | 
			
		||||
			@media (min-width 350px)
 | 
			
		||||
				font-size 14px
 | 
			
		||||
 | 
			
		||||
			@media (min-width 500px)
 | 
			
		||||
				font-size 16px
 | 
			
		||||
 | 
			
		||||
			@media (min-width 600px)
 | 
			
		||||
				padding 24px 32px
 | 
			
		||||
 | 
			
		||||
			> .avatar
 | 
			
		||||
				@media (min-width 500px)
 | 
			
		||||
					width 42px
 | 
			
		||||
					height 42px
 | 
			
		||||
 | 
			
		||||
			> div
 | 
			
		||||
				@media (min-width 500px)
 | 
			
		||||
					width calc(100% - 42px)
 | 
			
		||||
 | 
			
		||||
	> .notification
 | 
			
		||||
		padding 16px
 | 
			
		||||
		font-size 12px
 | 
			
		||||
@@ -159,6 +180,10 @@ export default Vue.extend({
 | 
			
		||||
					display inline-block
 | 
			
		||||
					margin-right 3px
 | 
			
		||||
 | 
			
		||||
		&.reaction
 | 
			
		||||
			> div > header
 | 
			
		||||
				align-items normal
 | 
			
		||||
 | 
			
		||||
		&.renote
 | 
			
		||||
			> div > header [data-icon]
 | 
			
		||||
				color #77B255
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
	<!-- トランジションを有効にするとなぜかメモリリークする -->
 | 
			
		||||
	<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications" tag="div">
 | 
			
		||||
		<template v-for="(notification, i) in _notifications">
 | 
			
		||||
			<mk-notification :notification="notification" :key="notification.id"/>
 | 
			
		||||
			<mk-notification :notification="notification" :key="notification.id" :class="{ wide: wide }"/>
 | 
			
		||||
			<p class="date" :key="notification.id + '_date'" v-if="i != items.length - 1 && notification._date != _notifications[i + 1]._date">
 | 
			
		||||
				<span><fa icon="angle-up"/>{{ notification._datetext }}</span>
 | 
			
		||||
				<span><fa icon="angle-down"/>{{ _notifications[i + 1]._datetext }}</span>
 | 
			
		||||
@@ -37,15 +37,37 @@ export default Vue.extend({
 | 
			
		||||
	i18n: i18n('mobile/views/components/notifications.vue'),
 | 
			
		||||
 | 
			
		||||
	mixins: [
 | 
			
		||||
		paging({}),
 | 
			
		||||
		paging({
 | 
			
		||||
			beforeInit: (self) => {
 | 
			
		||||
				self.$emit('beforeInit');
 | 
			
		||||
			},
 | 
			
		||||
			onInited: (self) => {
 | 
			
		||||
				self.$emit('inited');
 | 
			
		||||
			}
 | 
			
		||||
		}),
 | 
			
		||||
	],
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
		type: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		wide: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			connection: null,
 | 
			
		||||
			pagination: {
 | 
			
		||||
				endpoint: 'i/notifications',
 | 
			
		||||
				limit: 15,
 | 
			
		||||
				params: () => ({
 | 
			
		||||
					includeTypes: this.type ? [this.type] : undefined
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
@@ -62,6 +84,12 @@ export default Vue.extend({
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	watch: {
 | 
			
		||||
		type() {
 | 
			
		||||
			this.reload();
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.connection = this.$root.stream.useSharedConnection('main');
 | 
			
		||||
		this.connection.on('notification', this.onNotification);
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@
 | 
			
		||||
			:renote="renote"
 | 
			
		||||
			:mention="mention"
 | 
			
		||||
			:initial-text="initialText"
 | 
			
		||||
			:initial-note="initialNote"
 | 
			
		||||
			:instant="instant"
 | 
			
		||||
			@posted="onPosted"
 | 
			
		||||
			@cancel="onCanceled"/>
 | 
			
		||||
@@ -41,6 +42,10 @@ export default Vue.extend({
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		initialNote: {
 | 
			
		||||
			type: Object,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		instant: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,8 @@
 | 
			
		||||
				<div class="links">
 | 
			
		||||
					<ul>
 | 
			
		||||
						<li><router-link to="/" :data-active="$route.name == 'index'"><i><fa icon="home" fixed-width/></i>{{ $t('timeline') }}<i><fa icon="angle-right"/></i></router-link></li>
 | 
			
		||||
						<li><p @click="showNotifications = true"><i><fa :icon="['far', 'bell']" fixed-width/></i>{{ $t('notifications') }}<i v-if="hasUnreadNotification" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></p></li>
 | 
			
		||||
						<li v-if="$store.state.device.enableMobileQuickNotificationView"><p @click="showNotifications = true"><i><fa :icon="faBell" fixed-width/></i>{{ $t('notifications') }}<i v-if="hasUnreadNotification" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></p></li>
 | 
			
		||||
						<li v-else><router-link to="/i/notifications" :data-active="$route.name == 'notifications'"><i><fa :icon="faBell" fixed-width/></i>{{ $t('notifications') }}<i v-if="hasUnreadNotification" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
 | 
			
		||||
						<li><router-link to="/i/messaging" :data-active="$route.name == 'messaging'"><i><fa :icon="['far', 'comments']" fixed-width/></i>{{ $t('@.messaging') }}<i v-if="hasUnreadMessagingMessage" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
 | 
			
		||||
						<li v-if="$store.getters.isSignedIn && ($store.state.i.isLocked || $store.state.i.carefulBot)"><router-link to="/i/follow-requests" :data-active="$route.name == 'follow-requests'"><i><fa :icon="['far', 'envelope']" fixed-width/></i>{{ $t('follow-requests') }}<i v-if="$store.getters.isSignedIn && $store.state.i.pendingReceivedFollowRequestsCount" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
 | 
			
		||||
						<li><router-link to="/featured" :data-active="$route.name == 'featured'"><i><fa :icon="faNewspaper" fixed-width/></i>{{ $t('@.featured-notes') }}<i><fa icon="angle-right"/></i></router-link></li>
 | 
			
		||||
@@ -68,7 +69,7 @@ import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import { lang } from '../../../config';
 | 
			
		||||
import { faNewspaper, faHashtag, faHome, faColumns, faUsers } from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import { faMoon, faSun, faStickyNote } from '@fortawesome/free-regular-svg-icons';
 | 
			
		||||
import { faMoon, faSun, faStickyNote, faBell } from '@fortawesome/free-regular-svg-icons';
 | 
			
		||||
import { search } from '../../../common/scripts/search';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
@@ -88,7 +89,7 @@ export default Vue.extend({
 | 
			
		||||
			announcements: [],
 | 
			
		||||
			searching: false,
 | 
			
		||||
			showNotifications: false,
 | 
			
		||||
			faNewspaper, faHashtag, faMoon, faSun, faHome, faColumns, faStickyNote, faUsers
 | 
			
		||||
			faNewspaper, faHashtag, faMoon, faSun, faHome, faColumns, faStickyNote, faUsers, faBell,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										71
									
								
								src/client/app/mobile/views/pages/notifications.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/client/app/mobile/views/pages/notifications.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<template #header><fa :icon="faBell"/> {{ $t('notifications') }}</template>
 | 
			
		||||
	<template #func>
 | 
			
		||||
		<button @click="filter()"><fa icon="cog"/></button>
 | 
			
		||||
	</template>
 | 
			
		||||
 | 
			
		||||
	<main>
 | 
			
		||||
		<mk-notifications @before-init="beforeInit()" @inited="inited()" :type="type === 'all' ? null : type" :wide="true" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }"/>
 | 
			
		||||
	</main>
 | 
			
		||||
</mk-ui>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import { faBell } from '@fortawesome/free-regular-svg-icons';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import Progress from '../../../common/scripts/loading';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n('mobile/views/pages/notifications.vue'),
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			type: 'all',
 | 
			
		||||
			faBell,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		document.title = this.$root.instanceName;
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		beforeInit() {
 | 
			
		||||
			Progress.start();
 | 
			
		||||
		},
 | 
			
		||||
		inited() {
 | 
			
		||||
			Progress.done();
 | 
			
		||||
		},
 | 
			
		||||
		filter() {
 | 
			
		||||
			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.type,
 | 
			
		||||
				},
 | 
			
		||||
				showCancelButton: true
 | 
			
		||||
			}).then(({ canceled, result: type }) => {
 | 
			
		||||
				if (canceled) return;
 | 
			
		||||
				this.type = type;
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
main > *
 | 
			
		||||
	overflow hidden
 | 
			
		||||
	background var(--face)
 | 
			
		||||
 | 
			
		||||
	&.round
 | 
			
		||||
		border-radius 8px
 | 
			
		||||
 | 
			
		||||
	&.shadow
 | 
			
		||||
		box-shadow 0 4px 16px rgba(#000, 0.1)
 | 
			
		||||
 | 
			
		||||
		@media (min-width 500px)
 | 
			
		||||
			box-shadow 0 8px 32px rgba(#000, 0.1)
 | 
			
		||||
</style>
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
					<mk-follow-button v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="title">
 | 
			
		||||
					<h1><mk-user-name :user="user" :key="user.id"/></h1>
 | 
			
		||||
					<h1><mk-user-name :user="user" :key="user.id" :nowrap="false"/></h1>
 | 
			
		||||
					<span class="username"><mk-acct :user="user" :detail="true" :key="user.id"/></span>
 | 
			
		||||
					<span class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</span>
 | 
			
		||||
				</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import * as XDraggable from 'vuedraggable';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { v4 as uuid } from 'uuid';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n('mobile/views/pages/widgets.vue'),
 | 
			
		||||
@@ -98,12 +98,6 @@ export default Vue.extend({
 | 
			
		||||
				id: 'g', data: {}
 | 
			
		||||
			}]);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this.$watch('$store.getters.mobileHome', () => {
 | 
			
		||||
			this.$store.dispatch('settings/updateMobileHomeProfile');
 | 
			
		||||
		}, {
 | 
			
		||||
			deep: true
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
@@ -128,6 +122,8 @@ export default Vue.extend({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		addWidget() {
 | 
			
		||||
			if(this.widgetAdderSelected == null) return;
 | 
			
		||||
 | 
			
		||||
			this.$store.commit('addMobileHomeWidget', {
 | 
			
		||||
				name: this.widgetAdderSelected,
 | 
			
		||||
				id: uuid(),
 | 
			
		||||
 
 | 
			
		||||
@@ -75,6 +75,9 @@ const defaultDeviceSettings = {
 | 
			
		||||
	disableShowingAnimatedImages: false,
 | 
			
		||||
	expandUsersPhotos: true,
 | 
			
		||||
	expandUsersActivity: true,
 | 
			
		||||
	enableMobileQuickNotificationView: false,
 | 
			
		||||
	roomGraphicsQuality: 'medium',
 | 
			
		||||
	roomUseOrthographicCamera: true,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default (os: MiOS) => new Vuex.Store({
 | 
			
		||||
 
 | 
			
		||||
@@ -11,11 +11,17 @@
 | 
			
		||||
    "sourceMap": false,
 | 
			
		||||
    "target": "es2017",
 | 
			
		||||
    "module": "esnext",
 | 
			
		||||
    "moduleResolution": "node",
 | 
			
		||||
    "removeComments": false,
 | 
			
		||||
    "noLib": false,
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    "strictNullChecks": false,
 | 
			
		||||
    "experimentalDecorators": true
 | 
			
		||||
    "experimentalDecorators": true,
 | 
			
		||||
    "resolveJsonModule": true,
 | 
			
		||||
    "typeRoots": [
 | 
			
		||||
      "node_modules/@types",
 | 
			
		||||
      "src/@types"
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  "compileOnSave": false,
 | 
			
		||||
  "include": [
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/bed/bed.blend
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/bed/bed.blend
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/bed/bed.glb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/bed/bed.glb
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/book/book.blend
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/book/book.blend
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/book/book.glb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/book/book.glb
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/book2/barcode.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/book2/barcode.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 3.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/book2/book2.blend
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/book2/book2.blend
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/book2/book2.glb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/book2/book2.glb
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/book2/texture.afdesign
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/book2/texture.afdesign
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/book2/texture.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/book2/texture.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 60 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/book2/uv.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/client/assets/room/furnitures/book2/uv.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 8.4 KiB  | 
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user