Compare commits
	
		
			136 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 0b7be70935 | ||
|   | 818b71abd6 | ||
|   | 25575e8510 | ||
|   | 6c85adcf23 | ||
|   | 5dc92d7a40 | ||
|   | 4e2b966b80 | ||
|   | d34f8c3cb9 | ||
|   | 9049ecb1cf | ||
|   | 7bebea087c | ||
|   | 1c79e30436 | ||
|   | 1d7933349b | ||
|   | d002f67140 | ||
|   | da3447765b | ||
|   | cbf5663179 | ||
|   | b217fba235 | ||
|   | 7f7e6d5aba | ||
|   | 87c5a9d9a6 | ||
|   | 8ca1fe3f0a | ||
|   | 763ae8f1a6 | ||
|   | c65256d02b | ||
|   | bd2ac515d1 | ||
|   | 681f372889 | ||
|   | c2eec272e6 | ||
|   | bd720491a9 | ||
|   | a408226509 | ||
|   | c015e99e6e | ||
|   | de47a17be7 | ||
|   | d38fc490ad | ||
|   | 662167e792 | ||
|   | 36c91f03d9 | ||
|   | 33ccee26b5 | ||
|   | ed5cb991e3 | ||
|   | bea84ec2bf | ||
|   | 08c176e549 | ||
|   | 810ed50976 | ||
|   | 2684541693 | ||
|   | a5b12bac54 | ||
|   | fea1b06e43 | ||
|   | 182ca5d434 | ||
|   | facde9a75d | ||
|   | 41385640b9 | ||
|   | 7bad9db32e | ||
|   | af66f0a497 | ||
|   | 95e1b80f41 | ||
|   | 556e2eba95 | ||
|   | efe530cb17 | ||
|   | 34e7c99283 | ||
|   | 4157ea8bc3 | ||
|   | 550517bbf3 | ||
|   | eb910cd8a1 | ||
|   | 75131c4e8a | ||
|   | ee29ab95be | ||
|   | e97951fc51 | ||
|   | dfabdef60f | ||
|   | 5a87763193 | ||
|   | 6bb90f56fa | ||
|   | c883ae1350 | ||
|   | 09e25e6a02 | ||
|   | bf5d43054b | ||
|   | 63b3c65691 | ||
|   | f193da7f67 | ||
|   | 40f38c2c0a | ||
|   | db439ef804 | ||
|   | 56eb896a03 | ||
|   | 68d43e43b6 | ||
|   | c60517e49a | ||
|   | 3f59d261f2 | ||
|   | 4068d220e5 | ||
|   | 18968e7208 | ||
|   | 38656103c0 | ||
|   | 0f65b1bcc5 | ||
|   | a628821834 | ||
|   | 6ceff60c1e | ||
|   | d762a6ce58 | ||
|   | 75a8037a46 | ||
|   | 1179920790 | ||
|   | b323a160e3 | ||
|   | b157e9535e | ||
|   | 7668475bd6 | ||
|   | 8bda2a1fb7 | ||
|   | b092086b5b | ||
|   | 69a0d9034f | ||
|   | f5be8fd313 | ||
|   | 7f835d7f76 | ||
|   | ddbb7c5993 | ||
|   | 00a3fe39e8 | ||
|   | 7537fb88d4 | ||
|   | a81bc71a1e | ||
|   | 0a0aa0e2db | ||
|   | c56b94ae96 | ||
|   | e90712706d | ||
|   | eb0623331f | ||
|   | d15bd59109 | ||
|   | 60e0b19372 | ||
|   | 922eb937ff | ||
|   | 87573284f1 | ||
|   | a91c585f55 | ||
|   | 953ea21d5e | ||
|   | ecb00968bc | ||
|   | 50ad8adb2d | ||
|   | 16878caf09 | ||
|   | 5bc30c5493 | ||
|   | 85d89cf4c4 | ||
|   | db693f598b | ||
|   | 0494c770a1 | ||
|   | c473b62aed | ||
|   | f19ac5320e | ||
|   | 612e3aafbc | ||
|   | 0e97fec451 | ||
|   | e8c8626ee4 | ||
|   | d89e0f07f8 | ||
|   | e7f81a42ce | ||
|   | ac614148b8 | ||
|   | 5eb02b4901 | ||
|   | 65631525f6 | ||
|   | 969435cfe9 | ||
|   | c932f7a25b | ||
|   | 42d164dc57 | ||
|   | a7e60f80bd | ||
|   | 3dd5f313b7 | ||
|   | 883962c393 | ||
|   | 8a30ff1c76 | ||
|   | e47c354916 | ||
|   | 496f42805d | ||
|   | c3d34bda37 | ||
|   | bb6ede2b8f | ||
|   | 822400a1ba | ||
|   | e3e08843f1 | ||
|   | ce0d4f77fa | ||
|   | 94fdb4e974 | ||
|   | 4d425fc8a4 | ||
|   | c6cdfa2f5a | ||
|   | 0fff2e4f16 | ||
|   | 80a2172715 | ||
|   | 5a0a297634 | ||
|   | 948a133b7b | 
| @@ -1,31 +0,0 @@ | |||||||
| --- |  | ||||||
| name: 🐛 Bug Report (🖥️Client specific) |  | ||||||
| about: Create a report to help us improve |  | ||||||
| title: '' |  | ||||||
| labels: ⚠️bug?, 🖥️Client |  | ||||||
| assignees: '' |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## Summary |  | ||||||
|  |  | ||||||
| <!-- Tell us what the bug is --> |  | ||||||
|  |  | ||||||
| ## Expected Behavior |  | ||||||
|  |  | ||||||
| <!--- Tell us what should happen --> |  | ||||||
|  |  | ||||||
| ## Actual Behavior |  | ||||||
|  |  | ||||||
| <!--- Tell us what happens instead of the expected behavior --> |  | ||||||
|  |  | ||||||
| ## Steps to Reproduce |  | ||||||
|  |  | ||||||
| 1. |  | ||||||
| 2. |  | ||||||
| 3. |  | ||||||
|  |  | ||||||
| ## Environment |  | ||||||
|  |  | ||||||
| <!-- Tell us where on the platform it happens --> |  | ||||||
| <!-- e.g. desktop or mobile version, your browser, your OS --> |  | ||||||
| @@ -1,31 +0,0 @@ | |||||||
| --- |  | ||||||
| name: 🐛 Bug Report (⚙️Server specific) |  | ||||||
| about: Create a report to help us improve |  | ||||||
| title: '' |  | ||||||
| labels: ⚠️bug?, ⚙️Server |  | ||||||
| assignees: '' |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## Summary |  | ||||||
|  |  | ||||||
| <!-- Tell us what the bug is --> |  | ||||||
|  |  | ||||||
| ## Expected Behavior |  | ||||||
|  |  | ||||||
| <!--- Tell us what should happen --> |  | ||||||
|  |  | ||||||
| ## Actual Behavior |  | ||||||
|  |  | ||||||
| <!--- Tell us what happens instead of the expected behavior --> |  | ||||||
|  |  | ||||||
| ## Steps to Reproduce |  | ||||||
|  |  | ||||||
| 1. |  | ||||||
| 2. |  | ||||||
| 3. |  | ||||||
|  |  | ||||||
| ## Environment |  | ||||||
|  |  | ||||||
| <!-- Tell us where on the platform it happens --> |  | ||||||
| <!-- e.g. your Node.js version, your OS --> |  | ||||||
| @@ -1,12 +0,0 @@ | |||||||
| --- |  | ||||||
| name: ✨ Feature Request (🖥️Client specific) |  | ||||||
| about: Suggest an idea for this project |  | ||||||
| title: '' |  | ||||||
| labels: ✨Feature, 🖥️Client |  | ||||||
| assignees: '' |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## Summary |  | ||||||
|  |  | ||||||
| <!-- Tell us what the suggestion is --> |  | ||||||
| @@ -1,12 +0,0 @@ | |||||||
| --- |  | ||||||
| name: ✨ Feature Request (⚙️Server specific) |  | ||||||
| about: Suggest an idea for this project |  | ||||||
| title: '' |  | ||||||
| labels: ✨Feature, ⚙️Server |  | ||||||
| assignees: '' |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## Summary |  | ||||||
|  |  | ||||||
| <!-- Tell us what the suggestion is --> |  | ||||||
							
								
								
									
										84
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										84
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,6 +1,90 @@ | |||||||
| ChangeLog | ChangeLog | ||||||
| ========= | ========= | ||||||
|  |  | ||||||
|  | If you encounter any problems with updating, please try the following: | ||||||
|  | 1. `npm run clean` or `npm run cleanall` | ||||||
|  | 2. Retry update (Don't forget `npm i`) | ||||||
|  |  | ||||||
|  | 10.98.2 | ||||||
|  | ---------- | ||||||
|  | * 他のインスタンスから添付画像が見れない問題を修正 | ||||||
|  |  | ||||||
|  | 10.98.1 | ||||||
|  | ---------- | ||||||
|  | * ドライブのファイルのサムネイルが表示されない問題を修正 | ||||||
|  | * APでカスタム絵文字を送る時に常にimage/pngで送っている問題を修正 | ||||||
|  | * いくらいじってもページリロードするとmisskeyのテーマがdark(future)になっちゃう問題を修正 | ||||||
|  |  | ||||||
|  | 10.98.0 | ||||||
|  | ---------- | ||||||
|  | * ドライブのファイルダウンロード時に元のファイル名を尊重するように | ||||||
|  | * ドライブで画像以外のファイルを分かりやすく表示するように | ||||||
|  | * TwemojiのCDNを変更 | ||||||
|  | * モバイルで通知の設定がない問題を修正 | ||||||
|  | * デザインの調整 | ||||||
|  |  | ||||||
|  | 10.97.2 | ||||||
|  | ---------- | ||||||
|  | * ビルド時に警告が出ないように修正 | ||||||
|  |  | ||||||
|  | 10.97.1 | ||||||
|  | ---------- | ||||||
|  | * デザインの調整 | ||||||
|  |  | ||||||
|  | 10.97.0 | ||||||
|  | ---------- | ||||||
|  | * リアクションに絵文字やカスタム絵文字を使えるように | ||||||
|  | * 不明なリアクションのフォールバックに star を使えるように | ||||||
|  | * デザインの調整 | ||||||
|  |  | ||||||
|  | 10.96.0 | ||||||
|  | ---------- | ||||||
|  | * 連合ユーザーの投稿に対してActivityPubオブジェクトを要求されたら元のインスタンスにリダイレクトするように | ||||||
|  | * updatePersonを試行した時点でもlastFetchedAtを更新するように | ||||||
|  | * 管理画面でリモートインスタンスの登録日時を表示 | ||||||
|  | * ユーザーサジェストが機能しなくなっていた問題を修正 | ||||||
|  | * 最近使ったハッシュタグ表示が機能していない問題を修正 | ||||||
|  | * バグ修正 | ||||||
|  | * デザインの調整 | ||||||
|  |  | ||||||
|  | 10.95.0 | ||||||
|  | ---------- | ||||||
|  | * ジョブを一覧できるように | ||||||
|  | * MFMでURLを明示する構文の追加 | ||||||
|  | * Articleタイプのアクティビティを受け入れるように | ||||||
|  | * 凍結されたユーザーをサジェストしないように | ||||||
|  | * ファビコンが保存されないのを修正 | ||||||
|  | * キューのジョブクリアの動作を修正 | ||||||
|  | * デザインの調整 | ||||||
|  |  | ||||||
|  | 10.94.0 | ||||||
|  | ---------- | ||||||
|  | * Faviconを設定できるように | ||||||
|  | * アカウントを凍結したときすべてのフォローを解除するように | ||||||
|  | * シェアページが機能していない問題を修正 | ||||||
|  | * インスタンスブロックをしていてもRenote等すると取得されてしまう問題を修正 | ||||||
|  | * デザインの調整 | ||||||
|  |  | ||||||
|  | 10.93.1 | ||||||
|  | ---------- | ||||||
|  | * データのエクスポートとインポートの動作を修正 | ||||||
|  | * デザインの調整 | ||||||
|  |  | ||||||
|  | 10.93.0 | ||||||
|  | ---------- | ||||||
|  | * フォローリストをインポートできるように | ||||||
|  | * embedプレイヤーを閉じれるように | ||||||
|  | * リストをインポートしたときにプロキシアカウントがフォローするように修正 | ||||||
|  | * Web Share Targetの動作を修正 | ||||||
|  | * おすすめアンケートのチョイスを修正 | ||||||
|  | * デザインの調整 | ||||||
|  |  | ||||||
|  | 10.92.4 | ||||||
|  | ---------- | ||||||
|  | * リストのエクスポートをできるように | ||||||
|  | * ジョブキューウィジェットを追加 | ||||||
|  | * URLプレビューのサムネイルが表示されないことがある問題を修正 | ||||||
|  |  | ||||||
| 10.92.3 | 10.92.3 | ||||||
| ---------- | ---------- | ||||||
| * 管理画面の各種ジョブ数がおかしい問題を修正 | * 管理画面の各種ジョブ数がおかしい問題を修正 | ||||||
|   | |||||||
| @@ -46,6 +46,9 @@ Convert な(na) to にゃ(nya) | |||||||
| Revert Nyaize | Revert Nyaize | ||||||
|  |  | ||||||
| ## Code style | ## Code style | ||||||
|  | ### Use semicolon | ||||||
|  | To avoid ASI Hazard | ||||||
|  |  | ||||||
| ### Don't use `export default` | ### Don't use `export default` | ||||||
| Bad: | Bad: | ||||||
| ``` ts | ``` ts | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							| @@ -101,6 +101,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md). | |||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
| <!-- PATREON_START --> | <!-- PATREON_START --> | ||||||
| <table><tr> | <table><tr> | ||||||
|  | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5888816/36da0f7c15954df0ab13f9abdf227f66/1?token-time=2145916800&token-hash=HGkZJ7s4bSaQVoOJ5q30mTWHTxDLiw1LuyaogKPLy24%3D" alt="Hiroshi Seki" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/1?token-time=2145916800&token-hash=WeuDzzz24cRXJogyIkU-mxARqkdyms-rcZKbO-GpGjw%3D" alt="weep" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/1?token-time=2145916800&token-hash=WeuDzzz24cRXJogyIkU-mxARqkdyms-rcZKbO-GpGjw%3D" alt="weep" width="100"></td> | ||||||
| <td><img src="https://c8.patreon.com/2/200/12059069" alt="naga_rus" width="100"></td> | <td><img src="https://c8.patreon.com/2/200/12059069" alt="naga_rus" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/4?token-time=2145916800&token-hash=vZdDTTF-ahiKBjjgppS2ev4rkD8H7TTKkXXoxsucs6Y%3D" alt="Melilot" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/4?token-time=2145916800&token-hash=vZdDTTF-ahiKBjjgppS2ev4rkD8H7TTKkXXoxsucs6Y%3D" alt="Melilot" width="100"></td> | ||||||
| @@ -108,6 +109,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md). | |||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/3?token-time=2145916800&token-hash=LtV2lRi3L2jOWMLwccr9qWYfPrFlzIo2jYZHKzHEb6k%3D" alt="Xeltica" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/3?token-time=2145916800&token-hash=LtV2lRi3L2jOWMLwccr9qWYfPrFlzIo2jYZHKzHEb6k%3D" alt="Xeltica" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=1FlxS9MEgmNGH_RHUVHbO5hIXB5I1z0lvA33CTvYvjA%3D" alt="gutfuckllc" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=1FlxS9MEgmNGH_RHUVHbO5hIXB5I1z0lvA33CTvYvjA%3D" alt="gutfuckllc" width="100"></td> | ||||||
| </tr><tr> | </tr><tr> | ||||||
|  | <td><a href="https://www.patreon.com/rane_hs">Hiroshi Seki</a></td> | ||||||
| <td><a href="https://www.patreon.com/weepjp">weep</a></td> | <td><a href="https://www.patreon.com/weepjp">weep</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=12059069">naga_rus</a></td> | <td><a href="https://www.patreon.com/user?u=12059069">naga_rus</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td> | <td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td> | ||||||
| @@ -137,9 +139,9 @@ Please see the [Contribution Guide](./CONTRIBUTING.md). | |||||||
| <td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td> | <td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td> | ||||||
| </tr></table> | </tr></table> | ||||||
| <table><tr> | <table><tr> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/2?token-time=2145916800&token-hash=zcwFxb2zopzWwksKVU1YpfAEjsl4yKT02aQ6yiAFRiQ%3D" alt="natalie" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3?token-time=2145916800&token-hash=-iJszBqgYBhsM5qMdA1knf9wvprhEfESzKfR2oh7mIA%3D" alt="natalie" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=5T8XcaAf9Zyzfg3QubR06s_kJZkArVEM2dwObrBVAU4%3D" alt="Hiratake" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=5T8XcaAf9Zyzfg3QubR06s_kJZkArVEM2dwObrBVAU4%3D" alt="Hiratake" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1?token-time=2145916800&token-hash=D6QK3fPyqiYKJfOzc-QqaSSairUrWdjld-ewp2waj6s%3D" alt="@Hekovic@gyutte.site" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1?token-time=2145916800&token-hash=D6QK3fPyqiYKJfOzc-QqaSSairUrWdjld-ewp2waj6s%3D" alt="Hekovic" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=Ksk_2l3gjPDbnzMUOCSW1E-hdPJsNs2tSR4_RAakRK8%3D" alt="dansup" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=Ksk_2l3gjPDbnzMUOCSW1E-hdPJsNs2tSR4_RAakRK8%3D" alt="dansup" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1?token-time=2145916800&token-hash=CXe9AqlZy9AsYfiWd3OBYVOzvODoN47Litz0Tu4BFpU%3D" alt="Gargron" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1?token-time=2145916800&token-hash=CXe9AqlZy9AsYfiWd3OBYVOzvODoN47Litz0Tu4BFpU%3D" alt="Gargron" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1?token-time=2145916800&token-hash=xhR1n6NAAyEb-IUXLD6_dshkFa3mefU5ZZuk1L8qKTs%3D" alt="Nokotaro Takeda" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1?token-time=2145916800&token-hash=xhR1n6NAAyEb-IUXLD6_dshkFa3mefU5ZZuk1L8qKTs%3D" alt="Nokotaro Takeda" width="100"></td> | ||||||
| @@ -147,14 +149,14 @@ Please see the [Contribution Guide](./CONTRIBUTING.md). | |||||||
| </tr><tr> | </tr><tr> | ||||||
| <td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td> | <td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td> | ||||||
| <td><a href="https://www.patreon.com/hiratake">Hiratake</a></td> | <td><a href="https://www.patreon.com/hiratake">Hiratake</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=18072312">@Hekovic@gyutte.site</a></td> | <td><a href="https://www.patreon.com/hekovic">Hekovic</a></td> | ||||||
| <td><a href="https://www.patreon.com/dansup">dansup</a></td> | <td><a href="https://www.patreon.com/dansup">dansup</a></td> | ||||||
| <td><a href="https://www.patreon.com/mastodon">Gargron</a></td> | <td><a href="https://www.patreon.com/mastodon">Gargron</a></td> | ||||||
| <td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td> | <td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td> | <td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td> | ||||||
| </tr></table> | </tr></table> | ||||||
|  |  | ||||||
| **Last updated:** Thu, 07 Mar 2019 11:30:05 UTC | **Last updated:** Fri, 22 Mar 2019 09:29:06 UTC | ||||||
| <!-- PATREON_END --> | <!-- PATREON_END --> | ||||||
|  |  | ||||||
| :four_leaf_clover: Copyright | :four_leaf_clover: Copyright | ||||||
|   | |||||||
| @@ -118,7 +118,7 @@ CentOSで1024以下のポートを使用してMisskeyを使用する場合は`Ex | |||||||
| 4. `NODE_ENV=production npm run build` | 4. `NODE_ENV=production npm run build` | ||||||
| 5. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する | 5. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する | ||||||
|  |  | ||||||
| なにか問題が発生した場合は、`npm run clean`すると直る場合があります。 | なにか問題が発生した場合は、`npm run clean`または`npm run cleanall`すると直る場合があります。 | ||||||
|  |  | ||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,12 +2,12 @@ | |||||||
| meta: | meta: | ||||||
|   lang: "Čeština" |   lang: "Čeština" | ||||||
| common: | common: | ||||||
|   misskey: "⭐ ve fediverse" |   misskey: "⭐ ve fedivesmíru" | ||||||
|   about-title: "⭐ ve fediverse." |   about-title: "⭐ ve fedivesmíru." | ||||||
|   about: "Děkujeme, že jste našli Misskey. Misskey je <b>decentralizovaná mikroblogovací platforma</b> zrozená na Zemi. Neboť existuje ve Fediverse (vesmíru, kde jsou organizovány různé sociální sítě), je vzájemně propojena s jinými sociálními sítěmi. Co takhle si chvilku odpočinout od ruchu a shonu města a ponořit se do nového internetu?" |   about: "Děkujeme, že jste našli Misskey. Misskey je <b>decentralizovaná mikroblogovací platforma</b> zrozená na Zemi. Neboť existuje ve fedivesmíru (vesmíru, kde jsou organizovány různé sociální sítě), je vzájemně propojena s jinými sociálními sítěmi. Co takhle si chvilku odpočinout od ruchu a shonu města a ponořit se do nového internetu?" | ||||||
|   intro: |   intro: | ||||||
|     title: "Co je Misskey?" |     title: "Co je Misskey?" | ||||||
|     about: "Misskey je open-source <b>decentralizovaný mikroblogovací software</b>. Má sofistikované, zcela přizpůsobitelné uživatelské rozhraní, různé způsoby reagování na příspěvky, bezplatné úložiště souborů nabízející integrovaný management system, a další pokročilé vlastnosti. Misskey je navíc připojeno k systému sítí zvanému „fediverse“, který nám dovoluje komunikovat s uživateli na jiných sociálních sítí. Pokud například něco napíšete, nebude to posláno pouze uživatelů Misskey, ale také lidem na sítích Mastodon a Pleroma. Jen si představte, že planeta posílá jiné planetě rádiový signál, aby s ní komunikovala." |     about: "Misskey je open-source <b>decentralizovaný mikroblogovací software</b>. Má sofistikované, zcela přizpůsobitelné uživatelské rozhraní, různé způsoby reagování na příspěvky, bezplatné úložiště souborů nabízející integrovaný management system, a další pokročilé vlastnosti. Misskey je navíc připojeno k systému sítí zvanému „fedivesmír“ nebo „fediverse“, který nám dovoluje komunikovat s uživateli na jiných sociálních sítí. Pokud například něco napíšete, nebude to posláno pouze uživatelů Misskey, ale také lidem na sítích Mastodon a Pleroma. Jen si představte, že planeta posílá jiné planetě rádiový signál, aby s ní komunikovala." | ||||||
|     features: "Vlastnosti" |     features: "Vlastnosti" | ||||||
|     rich-contents: "Příspěvky" |     rich-contents: "Příspěvky" | ||||||
|     rich-contents-desc: "Pouze napište svoje nápady, žhavá témata a cokoliv, co chcete sdílet. Můžete ozdobit svá slova, připojit vaše oblíbené obrázky, posílat soubory včetně videí či vytvořit hlasování – to je jen několik věcí, co můžete dělat s Misskey!" |     rich-contents-desc: "Pouze napište svoje nápady, žhavá témata a cokoliv, co chcete sdílet. Můžete ozdobit svá slova, připojit vaše oblíbené obrázky, posílat soubory včetně videí či vytvořit hlasování – to je jen několik věcí, co můžete dělat s Misskey!" | ||||||
| @@ -190,6 +190,7 @@ common: | |||||||
|     remain-deleted-note: "I nadále zobrazovat odstraněné příspěvky" |     remain-deleted-note: "I nadále zobrazovat odstraněné příspěvky" | ||||||
|     sound: "Zvuk" |     sound: "Zvuk" | ||||||
|     enable-sounds: "Povolit zvuk" |     enable-sounds: "Povolit zvuk" | ||||||
|  |     enable-sounds-desc: "Přehrát zvuk, například při odeslání nebo přijetí příspěvku, či zprávy. Toto nastavení je uloženo v prohlížeči." | ||||||
|     volume: "Hlasitost" |     volume: "Hlasitost" | ||||||
|     test: "Test" |     test: "Test" | ||||||
|     update: "Aktualizace Misskey" |     update: "Aktualizace Misskey" | ||||||
| @@ -299,9 +300,11 @@ common/views/pages/explore.vue: | |||||||
|   recently-updated-users: "Nedávno aktívni uživatelé" |   recently-updated-users: "Nedávno aktívni uživatelé" | ||||||
|   recently-registered-users: "Nedávno registrovaní uživatelé" |   recently-registered-users: "Nedávno registrovaní uživatelé" | ||||||
|   popular-tags: "Populární tagy" |   popular-tags: "Populární tagy" | ||||||
|   federated: "Z fediverse" |   federated: "Z fedivesmíru" | ||||||
|  |   explore: "Prozkoumat {host}" | ||||||
| common/views/components/url-preview.vue: | common/views/components/url-preview.vue: | ||||||
|   enable-player: "Otevřít v přehrávači" |   enable-player: "Otevřít v přehrávači" | ||||||
|  |   disable-player: "Zavřít přehrávač" | ||||||
| common/views/components/user-list.vue: | common/views/components/user-list.vue: | ||||||
|   no-users: "Žádní uživatelé" |   no-users: "Žádní uživatelé" | ||||||
| common/views/components/games/reversi/reversi.vue: | common/views/components/games/reversi/reversi.vue: | ||||||
| @@ -412,12 +415,15 @@ common/views/components/nav.vue: | |||||||
|   feedback: "Zpětná vazba" |   feedback: "Zpětná vazba" | ||||||
| common/views/components/note-menu.vue: | common/views/components/note-menu.vue: | ||||||
|   mention: "Zmínění" |   mention: "Zmínění" | ||||||
|  |   detail: "Více" | ||||||
|   copy-content: "Zkopírovat obsah" |   copy-content: "Zkopírovat obsah" | ||||||
|   copy-link: "Zkopírovat odkaz" |   copy-link: "Zkopírovat odkaz" | ||||||
|   favorite: "Přidat do oblíbených" |   favorite: "Přidat do oblíbených" | ||||||
|   unfavorite: "Odebrat z oblízených" |   unfavorite: "Odebrat z oblízených" | ||||||
|   watch: "Sledovat" |   watch: "Sledovat" | ||||||
|   unwatch: "Přestat sledovat" |   unwatch: "Přestat sledovat" | ||||||
|  |   pin: "Připnout" | ||||||
|  |   unpin: "Odepnout" | ||||||
|   delete: "Odstranit" |   delete: "Odstranit" | ||||||
|   delete-confirm: "Opravdu chcete smazat tento příspěvek?" |   delete-confirm: "Opravdu chcete smazat tento příspěvek?" | ||||||
|   remote: "Ukázat originální poznámku" |   remote: "Ukázat originální poznámku" | ||||||
| @@ -554,10 +560,12 @@ common/views/components/profile-editor.vue: | |||||||
|   email-verified: "Váš e-mail byl ověřen" |   email-verified: "Váš e-mail byl ověřen" | ||||||
|   email-not-verified: "Váš email není potvrzen. Prosím zkontrolujte si svou schránku." |   email-not-verified: "Váš email není potvrzen. Prosím zkontrolujte si svou schránku." | ||||||
|   export: "Exportovat" |   export: "Exportovat" | ||||||
|  |   import: "Importovat" | ||||||
|   export-targets: |   export-targets: | ||||||
|     following-list: "Seznam sledujících" |     following-list: "Seznam sledujících" | ||||||
|     mute-list: "Seznam ztlumených uživatelů" |     mute-list: "Seznam ztlumených uživatelů" | ||||||
|     blocking-list: "Seznam blokovaných uživatelů" |     blocking-list: "Seznam blokovaných uživatelů" | ||||||
|  |     user-lists: "Seznamy" | ||||||
|   enter-password: "Prosím, zadejte Vaše heslo" |   enter-password: "Prosím, zadejte Vaše heslo" | ||||||
|   danger-zone: "Nebezpečná zóna" |   danger-zone: "Nebezpečná zóna" | ||||||
|   delete-account: "Smazat účet" |   delete-account: "Smazat účet" | ||||||
| @@ -694,6 +702,7 @@ desktop/views/components/note.vue: | |||||||
|   renote: "Renote" |   renote: "Renote" | ||||||
|   add-reaction: "Přidat reakci" |   add-reaction: "Přidat reakci" | ||||||
|   undo-reaction: "Odebrat reakci" |   undo-reaction: "Odebrat reakci" | ||||||
|  |   detail: "Více" | ||||||
|   private: "Tento příspěvek je soukromý" |   private: "Tento příspěvek je soukromý" | ||||||
|   deleted: "Tento příspěvek byl odstraněn" |   deleted: "Tento příspěvek byl odstraněn" | ||||||
| desktop/views/components/notes.vue: | desktop/views/components/notes.vue: | ||||||
| @@ -827,7 +836,7 @@ admin/views/index.vue: | |||||||
|   emoji: "Emoji" |   emoji: "Emoji" | ||||||
|   moderators: "Moderátoři" |   moderators: "Moderátoři" | ||||||
|   users: "Uživatelé" |   users: "Uživatelé" | ||||||
|   federation: "Z fediversu" |   federation: "Federovaná" | ||||||
|   announcements: "Oznámení" |   announcements: "Oznámení" | ||||||
|   hashtags: "Hashtagy" |   hashtags: "Hashtagy" | ||||||
|   queue: "Fronta úloh" |   queue: "Fronta úloh" | ||||||
| @@ -839,9 +848,7 @@ admin/views/dashboard.vue: | |||||||
|   drive: "Disk" |   drive: "Disk" | ||||||
|   instances: "Instance" |   instances: "Instance" | ||||||
|   this-instance: "Tato instance" |   this-instance: "Tato instance" | ||||||
|   federated: "Z fediversu" |   federated: "Federovaná" | ||||||
| admin/views/queue.vue: |  | ||||||
|   operation: "Akce" |  | ||||||
| admin/views/abuse.vue: | admin/views/abuse.vue: | ||||||
|   details: "Popis" |   details: "Popis" | ||||||
|   remove-report: "Odstranit" |   remove-report: "Odstranit" | ||||||
| @@ -988,14 +995,15 @@ admin/views/announcements.vue: | |||||||
| admin/views/hashtags.vue: | admin/views/hashtags.vue: | ||||||
|   hided-tags: "Skryté tagy" |   hided-tags: "Skryté tagy" | ||||||
| admin/views/federation.vue: | admin/views/federation.vue: | ||||||
|   federation: "Z fediversu" |   instance: "Instance" | ||||||
|   host: "Hostitel" |   host: "Hostitel" | ||||||
|   notes: "Poznámky" |   notes: "Poznámky" | ||||||
|   users: "Uživatelé" |   users: "Uživatelé" | ||||||
|  |   caught-at: "Vytvořeno" | ||||||
|   status: "Status" |   status: "Status" | ||||||
|   latest-request-received-at: "Poslední požadavek přijat" |   latest-request-received-at: "Poslední požadavek přijat" | ||||||
|   block: "Blokován" |   block: "Blokován" | ||||||
|   instances: "Instance" |   instances: "Federovaná" | ||||||
|   states: |   states: | ||||||
|     all: "Všechny" |     all: "Všechny" | ||||||
|     blocked: "Blokován" |     blocked: "Blokován" | ||||||
| @@ -1029,8 +1037,6 @@ desktop/views/pages/selectdrive.vue: | |||||||
| desktop/views/pages/search.vue: | desktop/views/pages/search.vue: | ||||||
|   not-available: "Vyhledávání je vypnuté pro tuto instanci." |   not-available: "Vyhledávání je vypnuté pro tuto instanci." | ||||||
|   not-found: "Pro '{q}' nebyly nalezeny žádné příspěvky." |   not-found: "Pro '{q}' nebyly nalezeny žádné příspěvky." | ||||||
| desktop/views/pages/share.vue: |  | ||||||
|   share-with: "Sdílet na {name}" |  | ||||||
| desktop/views/pages/tag.vue: | desktop/views/pages/tag.vue: | ||||||
|   no-posts-found: "Nebyly nalezeny žádné příspěvky s \"{q}\"." |   no-posts-found: "Nebyly nalezeny žádné příspěvky s \"{q}\"." | ||||||
| desktop/views/pages/user-list.users.vue: | desktop/views/pages/user-list.users.vue: | ||||||
|   | |||||||
| @@ -339,6 +339,9 @@ common/views/components/profile-editor.vue: | |||||||
|   banner: "Banner" |   banner: "Banner" | ||||||
|   save: "Speichern" |   save: "Speichern" | ||||||
|   export: "Exportieren" |   export: "Exportieren" | ||||||
|  |   import: "Importieren" | ||||||
|  |   export-targets: | ||||||
|  |     user-lists: "Listen" | ||||||
|   enter-password: "Bitte Passwort eingeben" |   enter-password: "Bitte Passwort eingeben" | ||||||
| common/views/widgets/broadcast.vue: | common/views/widgets/broadcast.vue: | ||||||
|   fetching: "Laden" |   fetching: "Laden" | ||||||
|   | |||||||
| @@ -114,7 +114,7 @@ common: | |||||||
|     a: "What are you doing?" |     a: "What are you doing?" | ||||||
|     b: "What's happening?" |     b: "What's happening?" | ||||||
|     c: "What’s on your mind?" |     c: "What’s on your mind?" | ||||||
|     d: "Would you post any words?" |     d: "What do you want to say?" | ||||||
|     e: "Write here" |     e: "Write here" | ||||||
|     f: "Waiting for your writing." |     f: "Waiting for your writing." | ||||||
|   settings: "Settings" |   settings: "Settings" | ||||||
| @@ -169,9 +169,9 @@ common: | |||||||
|     deck-column-align-flexible: "Flexible" |     deck-column-align-flexible: "Flexible" | ||||||
|     deck-column-width: "Deck column width" |     deck-column-width: "Deck column width" | ||||||
|     deck-column-width-narrow: "Narrow" |     deck-column-width-narrow: "Narrow" | ||||||
|     deck-column-width-narrower: "Somewhat narrow" |     deck-column-width-narrower: "Narrower" | ||||||
|     deck-column-width-normal: "Regular" |     deck-column-width-normal: "Regular" | ||||||
|     deck-column-width-wider: "Somewhat wide" |     deck-column-width-wider: "Slightly wide" | ||||||
|     deck-column-width-wide: "Wide" |     deck-column-width-wide: "Wide" | ||||||
|     use-shadow: "Use shadows in the UI" |     use-shadow: "Use shadows in the UI" | ||||||
|     rounded-corners: "Round the corners of the UI" |     rounded-corners: "Round the corners of the UI" | ||||||
| @@ -285,7 +285,7 @@ auth/views/form.vue: | |||||||
|   account-read: "View account information." |   account-read: "View account information." | ||||||
|   account-write: "Modify account information." |   account-write: "Modify account information." | ||||||
|   note-write: "Post." |   note-write: "Post." | ||||||
|   like-write: "React to posts." |   like-write: "Express yourself about this post." | ||||||
|   following-write: "Follow and unfollow." |   following-write: "Follow and unfollow." | ||||||
|   drive-read: "Read your drive." |   drive-read: "Read your drive." | ||||||
|   drive-write: "Upload/delete files in your drive." |   drive-write: "Upload/delete files in your drive." | ||||||
| @@ -304,7 +304,7 @@ auth/views/index.vue: | |||||||
|   error: "Session does not exist." |   error: "Session does not exist." | ||||||
|   sign-in: "Please sign in." |   sign-in: "Please sign in." | ||||||
| common/views/pages/explore.vue: | common/views/pages/explore.vue: | ||||||
|   verified-users: "Verified accounts" |   verified-users: "Official accounts" | ||||||
|   popular-users: "Popular users" |   popular-users: "Popular users" | ||||||
|   recently-updated-users: "Recently active users" |   recently-updated-users: "Recently active users" | ||||||
|   recently-registered-users: "Users who joined recently" |   recently-registered-users: "Users who joined recently" | ||||||
| @@ -314,6 +314,7 @@ common/views/pages/explore.vue: | |||||||
|   users-info: "Currently, {users} users are registered here" |   users-info: "Currently, {users} users are registered here" | ||||||
| common/views/components/url-preview.vue: | common/views/components/url-preview.vue: | ||||||
|   enable-player: "Enable playback" |   enable-player: "Enable playback" | ||||||
|  |   disable-player: "Close the player" | ||||||
| common/views/components/user-list.vue: | common/views/components/user-list.vue: | ||||||
|   no-users: "There are no users." |   no-users: "There are no users." | ||||||
| common/views/components/games/reversi/reversi.vue: | common/views/components/games/reversi/reversi.vue: | ||||||
| @@ -647,12 +648,16 @@ common/views/components/profile-editor.vue: | |||||||
|   email-verified: "Your email has been verified." |   email-verified: "Your email has been verified." | ||||||
|   email-not-verified: "Email address is not confirmed. Please check your inbox." |   email-not-verified: "Email address is not confirmed. Please check your inbox." | ||||||
|   export: "Export" |   export: "Export" | ||||||
|  |   import: "Import" | ||||||
|  |   export-and-import: "Export and Import" | ||||||
|   export-targets: |   export-targets: | ||||||
|     all-notes: "All posted Notes" |     all-notes: "All posted Notes" | ||||||
|     following-list: "List of followers" |     following-list: "List of followers" | ||||||
|     mute-list: "List of muted accounts" |     mute-list: "List of muted accounts" | ||||||
|     blocking-list: "List of blocked accounts" |     blocking-list: "List of blocked accounts" | ||||||
|  |     user-lists: "Lists" | ||||||
|   export-requested: "You have requested an export. This may take a while. After the export is complete, the resulting file will be added to the drive." |   export-requested: "You have requested an export. This may take a while. After the export is complete, the resulting file will be added to the drive." | ||||||
|  |   import-requested: "You have initiated an import. This may take quite some time." | ||||||
|   enter-password: "Please enter your password" |   enter-password: "Please enter your password" | ||||||
|   danger-zone: "Cautious options" |   danger-zone: "Cautious options" | ||||||
|   delete-account: "Remove the account" |   delete-account: "Remove the account" | ||||||
| @@ -1052,7 +1057,7 @@ admin/views/dashboard.vue: | |||||||
|   this-instance: "This instance" |   this-instance: "This instance" | ||||||
|   federated: "Federated" |   federated: "Federated" | ||||||
| admin/views/queue.vue: | admin/views/queue.vue: | ||||||
|   operation: "Action(s)" |   title: "Queue" | ||||||
|   remove-all-jobs: "Clear all queued jobs" |   remove-all-jobs: "Clear all queued jobs" | ||||||
| admin/views/abuse.vue: | admin/views/abuse.vue: | ||||||
|   title: "Abuse" |   title: "Abuse" | ||||||
| @@ -1108,6 +1113,7 @@ admin/views/instance.vue: | |||||||
|   disable-local-timeline: "Disable the Local Timeline" |   disable-local-timeline: "Disable the Local Timeline" | ||||||
|   disable-global-timeline: "Disable global timeline" |   disable-global-timeline: "Disable global timeline" | ||||||
|   disabling-timelines-info: "Even if you disable these timelines, the administrator as well as moderators can use them continually." |   disabling-timelines-info: "Even if you disable these timelines, the administrator as well as moderators can use them continually." | ||||||
|  |   enable-emoji-reaction: "Enable pictograms for reactions" | ||||||
|   invite: "Invite" |   invite: "Invite" | ||||||
|   save: "Save" |   save: "Save" | ||||||
|   saved: "Saved" |   saved: "Saved" | ||||||
| @@ -1270,12 +1276,13 @@ admin/views/announcements.vue: | |||||||
| admin/views/hashtags.vue: | admin/views/hashtags.vue: | ||||||
|   hided-tags: "Hidden Tags" |   hided-tags: "Hidden Tags" | ||||||
| admin/views/federation.vue: | admin/views/federation.vue: | ||||||
|   federation: "Federation" |   instance: "Instance" | ||||||
|   host: "Host" |   host: "Host" | ||||||
|   notes: "Notes" |   notes: "Notes" | ||||||
|   users: "Users" |   users: "Users" | ||||||
|   following: "Following" |   following: "Following" | ||||||
|   followers: "Followers" |   followers: "Followers" | ||||||
|  |   caught-at: "Created at" | ||||||
|   status: "Statuses" |   status: "Statuses" | ||||||
|   latest-request-sent-at: "Time of last request sent" |   latest-request-sent-at: "Time of last request sent" | ||||||
|   latest-request-received-at: "Last request received at" |   latest-request-received-at: "Last request received at" | ||||||
| @@ -1284,7 +1291,7 @@ admin/views/federation.vue: | |||||||
|   block: "Block" |   block: "Block" | ||||||
|   marked-as-closed: "Marked as closed" |   marked-as-closed: "Marked as closed" | ||||||
|   lookup: "Look up" |   lookup: "Look up" | ||||||
|   instances: "Instances" |   instances: "Federated" | ||||||
|   instance-not-registered: "The instance has not been discovered" |   instance-not-registered: "The instance has not been discovered" | ||||||
|   sort: "Sort by" |   sort: "Sort by" | ||||||
|   sorts: |   sorts: | ||||||
| @@ -1347,8 +1354,6 @@ desktop/views/pages/selectdrive.vue: | |||||||
| desktop/views/pages/search.vue: | desktop/views/pages/search.vue: | ||||||
|   not-available: "Search feature is turned off in the settings for this instance." |   not-available: "Search feature is turned off in the settings for this instance." | ||||||
|   not-found: "No posts were found for '{q}'" |   not-found: "No posts were found for '{q}'" | ||||||
| desktop/views/pages/share.vue: |  | ||||||
|   share-with: "Share on {name}" |  | ||||||
| desktop/views/pages/tag.vue: | desktop/views/pages/tag.vue: | ||||||
|   no-posts-found: "No posts contains \"{q}\" found." |   no-posts-found: "No posts contains \"{q}\" found." | ||||||
| desktop/views/pages/user-list.users.vue: | desktop/views/pages/user-list.users.vue: | ||||||
|   | |||||||
| @@ -103,6 +103,32 @@ common: | |||||||
|     tags: "Etiquetas" |     tags: "Etiquetas" | ||||||
|     blocking: "Bloquear" |     blocking: "Bloquear" | ||||||
|     password: "Contraseña" |     password: "Contraseña" | ||||||
|  |     use-os-default-emojis: "Usar los emoticonos estándar del sistema operativo" | ||||||
|  |     line-width: "Grosor de línea" | ||||||
|  |     line-width-thick: "Grosor" | ||||||
|  |     font-size: "Tamaño del texto" | ||||||
|  |     font-size-x-small: "Muy pequeño" | ||||||
|  |     font-size-small: "Pequeño" | ||||||
|  |     font-size-medium: "Normal" | ||||||
|  |     font-size-large: "Grande" | ||||||
|  |     font-size-x-large: "Muy grande" | ||||||
|  |     deck-column-align: "Alineamiento de las columnas" | ||||||
|  |     deck-column-align-center: "Centrar" | ||||||
|  |     deck-column-align-left: "Izquierda" | ||||||
|  |     deck-column-align-flexible: "Flexible" | ||||||
|  |     deck-column-width: "Ancho de las columnas" | ||||||
|  |     deck-column-width-narrow: "Estrecho" | ||||||
|  |     deck-column-width-narrower: "Un poco estrecho" | ||||||
|  |     deck-column-width-normal: "Normal" | ||||||
|  |     deck-column-width-wider: "Un poco ancho" | ||||||
|  |     deck-column-width-wide: "Ancho" | ||||||
|  |     use-shadow: "Usar sombras en la Interfaz de Usuario" | ||||||
|  |     rounded-corners: "Esquinas redondeadas en la Interfaz de Usuario" | ||||||
|  |     circle-icons: "Usar iconos circulares" | ||||||
|  |     contrasted-acct: "Añadir contraste al nombre de usuario" | ||||||
|  |     wallpaper: "Fondo de pantalla" | ||||||
|  |     choose-wallpaper: "Escoge un fondo de pantalla" | ||||||
|  |     navbar-position-left: "Izquierda" | ||||||
|   search: "Buscar" |   search: "Buscar" | ||||||
|   delete: "eliminar" |   delete: "eliminar" | ||||||
|   loading: "cargando" |   loading: "cargando" | ||||||
| @@ -395,9 +421,11 @@ common/views/components/profile-editor.vue: | |||||||
|   save: "Guardar" |   save: "Guardar" | ||||||
|   email-address: "Correo electrónico" |   email-address: "Correo electrónico" | ||||||
|   export: "Exportar" |   export: "Exportar" | ||||||
|  |   import: "Importar" | ||||||
|   export-targets: |   export-targets: | ||||||
|     mute-list: "Silenciar" |     mute-list: "Silenciar" | ||||||
|     blocking-list: "Bloquear" |     blocking-list: "Bloquear" | ||||||
|  |     user-lists: "Listas" | ||||||
|   enter-password: "Escribe una contraseña" |   enter-password: "Escribe una contraseña" | ||||||
| common/views/components/user-list-editor.vue: | common/views/components/user-list-editor.vue: | ||||||
|   users: "Usuarios" |   users: "Usuarios" | ||||||
| @@ -787,11 +815,11 @@ admin/views/announcements.vue: | |||||||
|   remove: "eliminar" |   remove: "eliminar" | ||||||
|   add: "Agregar" |   add: "Agregar" | ||||||
| admin/views/federation.vue: | admin/views/federation.vue: | ||||||
|  |   instance: "Instancia" | ||||||
|   host: "Host" |   host: "Host" | ||||||
|   following: "Siguiendo" |   following: "Siguiendo" | ||||||
|   status: "Estado" |   status: "Estado" | ||||||
|   block: "Bloquear" |   block: "Bloquear" | ||||||
|   instances: "Instancia" |  | ||||||
|   states: |   states: | ||||||
|     all: "Todo" |     all: "Todo" | ||||||
|     blocked: "Bloquear" |     blocked: "Bloquear" | ||||||
|   | |||||||
| @@ -522,11 +522,13 @@ common/views/components/profile-editor.vue: | |||||||
|   email-verified: "L’adresse du courrier électronique a été vérifiée." |   email-verified: "L’adresse du courrier électronique a été vérifiée." | ||||||
|   email-not-verified: "Adresse de courriel n’est pas confirmée. Veuillez vérifier votre boite de réception." |   email-not-verified: "Adresse de courriel n’est pas confirmée. Veuillez vérifier votre boite de réception." | ||||||
|   export: "Exporter" |   export: "Exporter" | ||||||
|  |   import: "Importer" | ||||||
|   export-targets: |   export-targets: | ||||||
|     all-notes: "Toutes les notes publiées" |     all-notes: "Toutes les notes publiées" | ||||||
|     following-list: "Liste des abonnements" |     following-list: "Liste des abonnements" | ||||||
|     mute-list: "Liste des comptes mis en sourdine" |     mute-list: "Liste des comptes mis en sourdine" | ||||||
|     blocking-list: "Liste des comptes bloqués" |     blocking-list: "Liste des comptes bloqués" | ||||||
|  |     user-lists: "Listes" | ||||||
|   export-requested: "Vous avez demandé une exportation. Cela peut prendre un certain temps. Une fois l'exportation terminée, le fichier résultant sera ajouté dans le Drive." |   export-requested: "Vous avez demandé une exportation. Cela peut prendre un certain temps. Une fois l'exportation terminée, le fichier résultant sera ajouté dans le Drive." | ||||||
|   enter-password: "Veuillez saisir votre mot de passe" |   enter-password: "Veuillez saisir votre mot de passe" | ||||||
|   danger-zone: "Zone de danger" |   danger-zone: "Zone de danger" | ||||||
| @@ -925,7 +927,6 @@ admin/views/dashboard.vue: | |||||||
|   this-instance: "Cette instance" |   this-instance: "Cette instance" | ||||||
|   federated: "Fédérées" |   federated: "Fédérées" | ||||||
| admin/views/queue.vue: | admin/views/queue.vue: | ||||||
|   operation: "Action(s)" |  | ||||||
|   remove-all-jobs: "Enlever toutes les tâches en attente" |   remove-all-jobs: "Enlever toutes les tâches en attente" | ||||||
| admin/views/abuse.vue: | admin/views/abuse.vue: | ||||||
|   title: "Abus" |   title: "Abus" | ||||||
| @@ -1141,12 +1142,13 @@ admin/views/announcements.vue: | |||||||
| admin/views/hashtags.vue: | admin/views/hashtags.vue: | ||||||
|   hided-tags: "Tags cachés" |   hided-tags: "Tags cachés" | ||||||
| admin/views/federation.vue: | admin/views/federation.vue: | ||||||
|   federation: "Fédération" |   instance: "Instance" | ||||||
|   host: "Hôte" |   host: "Hôte" | ||||||
|   notes: "Notes" |   notes: "Notes" | ||||||
|   users: "Utilisateur·rice·s" |   users: "Utilisateur·rice·s" | ||||||
|   following: "Abonnements" |   following: "Abonnements" | ||||||
|   followers: "Abonné·e·s" |   followers: "Abonné·e·s" | ||||||
|  |   caught-at: "Créé le" | ||||||
|   status: "Statuts" |   status: "Statuts" | ||||||
|   latest-request-sent-at: "Dernière requête envoyée" |   latest-request-sent-at: "Dernière requête envoyée" | ||||||
|   latest-request-received-at: "Dernière requête reçue" |   latest-request-received-at: "Dernière requête reçue" | ||||||
| @@ -1154,7 +1156,7 @@ admin/views/federation.vue: | |||||||
|   block: "Bloquer" |   block: "Bloquer" | ||||||
|   marked-as-closed: "Marquées comme fermées" |   marked-as-closed: "Marquées comme fermées" | ||||||
|   lookup: "Recherche" |   lookup: "Recherche" | ||||||
|   instances: "Instances" |   instances: "Fédérées" | ||||||
|   sort: "Trier par" |   sort: "Trier par" | ||||||
|   sorts: |   sorts: | ||||||
|     caughtAtAsc: "Date d’inscription (Ascendant)" |     caughtAtAsc: "Date d’inscription (Ascendant)" | ||||||
| @@ -1207,8 +1209,6 @@ desktop/views/pages/selectdrive.vue: | |||||||
| desktop/views/pages/search.vue: | desktop/views/pages/search.vue: | ||||||
|   not-available: "La fonction de recherche est désactivée dans les paramètres de l’instance." |   not-available: "La fonction de recherche est désactivée dans les paramètres de l’instance." | ||||||
|   not-found: "Aucune publication trouvée pour « {q} »." |   not-found: "Aucune publication trouvée pour « {q} »." | ||||||
| desktop/views/pages/share.vue: |  | ||||||
|   share-with: "Partager avec {name}" |  | ||||||
| desktop/views/pages/tag.vue: | desktop/views/pages/tag.vue: | ||||||
|   no-posts-found: "Aucune publication contenant « {q} » n’a été trouvée." |   no-posts-found: "Aucune publication contenant « {q} » n’a été trouvée." | ||||||
| desktop/views/pages/user-list.users.vue: | desktop/views/pages/user-list.users.vue: | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ const merge = (...args) => args.reduce((a, c) => ({ | |||||||
| }), {}); | }), {}); | ||||||
|  |  | ||||||
| const languages = [ | const languages = [ | ||||||
|  | 	'cs-CZ', | ||||||
| 	'de-DE', | 	'de-DE', | ||||||
| 	'en-US', | 	'en-US', | ||||||
| 	'es-ES', | 	'es-ES', | ||||||
| @@ -24,9 +25,11 @@ const languages = [ | |||||||
| 	'nl-NL', | 	'nl-NL', | ||||||
| 	'pl-PL', | 	'pl-PL', | ||||||
| 	'zh-CN', | 	'zh-CN', | ||||||
|  | 	'zh-TW', | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| const primaries = { | const primaries = { | ||||||
|  | 	'en': 'US', | ||||||
| 	'ja': 'JP', | 	'ja': 'JP', | ||||||
| 	'zh': 'CN', | 	'zh': 'CN', | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -334,6 +334,7 @@ common/views/pages/explore.vue: | |||||||
|  |  | ||||||
| common/views/components/url-preview.vue: | common/views/components/url-preview.vue: | ||||||
|   enable-player: "プレイヤーを開く" |   enable-player: "プレイヤーを開く" | ||||||
|  |   disable-player: "プレイヤーを閉じる" | ||||||
|  |  | ||||||
| common/views/components/user-list.vue: | common/views/components/user-list.vue: | ||||||
|   no-users: "ユーザーがいません" |   no-users: "ユーザーがいません" | ||||||
| @@ -701,12 +702,16 @@ common/views/components/profile-editor.vue: | |||||||
|   email-verified: "メールアドレスが確認されました" |   email-verified: "メールアドレスが確認されました" | ||||||
|   email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。" |   email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。" | ||||||
|   export: "エクスポート" |   export: "エクスポート" | ||||||
|  |   import: "インポート" | ||||||
|  |   export-and-import: "エクスポートとインポート" | ||||||
|   export-targets: |   export-targets: | ||||||
|     all-notes: "すべての投稿データ" |     all-notes: "すべての投稿データ" | ||||||
|     following-list: "フォロー" |     following-list: "フォロー" | ||||||
|     mute-list: "ミュート" |     mute-list: "ミュート" | ||||||
|     blocking-list: "ブロック" |     blocking-list: "ブロック" | ||||||
|  |     user-lists: "リスト" | ||||||
|   export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。" |   export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。" | ||||||
|  |   import-requested: "インポートをリクエストしました。これには時間がかかる場合があります。" | ||||||
|   enter-password: "パスワードを入力してください" |   enter-password: "パスワードを入力してください" | ||||||
|   danger-zone: "危険な設定" |   danger-zone: "危険な設定" | ||||||
|   delete-account: "アカウントを削除" |   delete-account: "アカウントを削除" | ||||||
| @@ -920,12 +925,6 @@ desktop/views/input-dialog.vue: | |||||||
|   cancel: "キャンセル" |   cancel: "キャンセル" | ||||||
|   ok: "決定" |   ok: "決定" | ||||||
|  |  | ||||||
| desktop/views/components/messaging-room-window.vue: |  | ||||||
|   title: "メッセージ:" |  | ||||||
|  |  | ||||||
| desktop/views/components/messaging-window.vue: |  | ||||||
|   title: "メッセージ" |  | ||||||
|  |  | ||||||
| desktop/views/components/note-detail.vue: | desktop/views/components/note-detail.vue: | ||||||
|   private: "この投稿は非公開です" |   private: "この投稿は非公開です" | ||||||
|   deleted: "この投稿は削除されました" |   deleted: "この投稿は削除されました" | ||||||
| @@ -1095,7 +1094,7 @@ desktop/views/components/timeline.vue: | |||||||
|   hybrid: "ソーシャル" |   hybrid: "ソーシャル" | ||||||
|   global: "グローバル" |   global: "グローバル" | ||||||
|   mentions: "あなた宛て" |   mentions: "あなた宛て" | ||||||
|   messages: "メッセージ" |   messages: "ダイレクト投稿" | ||||||
|   list: "リスト" |   list: "リスト" | ||||||
|   hashtag: "ハッシュタグ" |   hashtag: "ハッシュタグ" | ||||||
|   add-tag-timeline: "ハッシュタグを追加" |   add-tag-timeline: "ハッシュタグを追加" | ||||||
| @@ -1175,7 +1174,7 @@ admin/views/dashboard.vue: | |||||||
|   federated: "連合" |   federated: "連合" | ||||||
|  |  | ||||||
| admin/views/queue.vue: | admin/views/queue.vue: | ||||||
|   operation: "操作" |   title: "キュー" | ||||||
|   remove-all-jobs: "すべてのジョブをクリア" |   remove-all-jobs: "すべてのジョブをクリア" | ||||||
|  |  | ||||||
| admin/views/abuse.vue: | admin/views/abuse.vue: | ||||||
| @@ -1233,6 +1232,8 @@ admin/views/instance.vue: | |||||||
|   disable-local-timeline: "ローカルタイムラインを無効にする" |   disable-local-timeline: "ローカルタイムラインを無効にする" | ||||||
|   disable-global-timeline: "グローバルタイムラインを無効にする" |   disable-global-timeline: "グローバルタイムラインを無効にする" | ||||||
|   disabling-timelines-info: "これらのタイムラインを無効にしても、管理者およびモデレーターは引き続き利用できます。" |   disabling-timelines-info: "これらのタイムラインを無効にしても、管理者およびモデレーターは引き続き利用できます。" | ||||||
|  |   enable-emoji-reaction: "リアクションに絵文字を使えるようにする" | ||||||
|  |   use-star-for-reaction-fallback: "不明なリアクションのフォールバックに star を使う" | ||||||
|   invite: "招待" |   invite: "招待" | ||||||
|   save: "保存" |   save: "保存" | ||||||
|   saved: "保存しました" |   saved: "保存しました" | ||||||
| @@ -1403,12 +1404,13 @@ admin/views/hashtags.vue: | |||||||
|   hided-tags: "Hidden Tags" |   hided-tags: "Hidden Tags" | ||||||
|  |  | ||||||
| admin/views/federation.vue: | admin/views/federation.vue: | ||||||
|   federation: "連合" |   instance: "インスタンス" | ||||||
|   host: "ホスト" |   host: "ホスト" | ||||||
|   notes: "投稿" |   notes: "投稿" | ||||||
|   users: "ユーザー" |   users: "ユーザー" | ||||||
|   following: "フォロー中" |   following: "フォロー中" | ||||||
|   followers: "フォロワー" |   followers: "フォロワー" | ||||||
|  |   caught-at: "登録日時" | ||||||
|   status: "ステータス" |   status: "ステータス" | ||||||
|   latest-request-sent-at: "直近のリクエスト送信" |   latest-request-sent-at: "直近のリクエスト送信" | ||||||
|   latest-request-received-at: "直近のリクエスト受信" |   latest-request-received-at: "直近のリクエスト受信" | ||||||
| @@ -1417,7 +1419,7 @@ admin/views/federation.vue: | |||||||
|   block: "ブロック" |   block: "ブロック" | ||||||
|   marked-as-closed: "閉鎖されているとマーク" |   marked-as-closed: "閉鎖されているとマーク" | ||||||
|   lookup: "照会" |   lookup: "照会" | ||||||
|   instances: "インスタンス" |   instances: "連合" | ||||||
|   instance-not-registered: "そのインスタンスは登録されていません" |   instance-not-registered: "そのインスタンスは登録されていません" | ||||||
|   sort: "ソート" |   sort: "ソート" | ||||||
|   sorts: |   sorts: | ||||||
| @@ -1486,9 +1488,6 @@ desktop/views/pages/search.vue: | |||||||
|   not-available: "検索機能はインスタンスの設定で無効になっています。" |   not-available: "検索機能はインスタンスの設定で無効になっています。" | ||||||
|   not-found: "「{q}」に関する投稿は見つかりませんでした。" |   not-found: "「{q}」に関する投稿は見つかりませんでした。" | ||||||
|  |  | ||||||
| desktop/views/pages/share.vue: |  | ||||||
|   share-with: "{name}で共有" |  | ||||||
|  |  | ||||||
| desktop/views/pages/tag.vue: | desktop/views/pages/tag.vue: | ||||||
|   no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。" |   no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。" | ||||||
|  |  | ||||||
| @@ -1530,9 +1529,6 @@ desktop/views/pages/user/user.timeline.vue: | |||||||
|   with-media: "メディア" |   with-media: "メディア" | ||||||
|   my-posts: "私の投稿" |   my-posts: "私の投稿" | ||||||
|  |  | ||||||
| desktop/views/widgets/messaging.vue: |  | ||||||
|   title: "メッセージ" |  | ||||||
|  |  | ||||||
| desktop/views/widgets/notifications.vue: | desktop/views/widgets/notifications.vue: | ||||||
|   title: "通知" |   title: "通知" | ||||||
|  |  | ||||||
| @@ -1680,7 +1676,7 @@ mobile/views/pages/home.vue: | |||||||
|   hybrid: "ソーシャル" |   hybrid: "ソーシャル" | ||||||
|   global: "グローバル" |   global: "グローバル" | ||||||
|   mentions: "あなた宛て" |   mentions: "あなた宛て" | ||||||
|   messages: "メッセージ" |   messages: "ダイレクト投稿" | ||||||
|  |  | ||||||
| mobile/views/pages/tag.vue: | mobile/views/pages/tag.vue: | ||||||
|   no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。" |   no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。" | ||||||
|   | |||||||
| @@ -476,10 +476,12 @@ common/views/components/profile-editor.vue: | |||||||
|   email-verified: "このメールアドレスOKや!" |   email-verified: "このメールアドレスOKや!" | ||||||
|   email-not-verified: "メールアドレスが確認されとらん。メールボックスもっぺん見てくれへん?" |   email-not-verified: "メールアドレスが確認されとらん。メールボックスもっぺん見てくれへん?" | ||||||
|   export: "エクスポート" |   export: "エクスポート" | ||||||
|  |   import: "インポート" | ||||||
|   export-targets: |   export-targets: | ||||||
|     following-list: "フォロー" |     following-list: "フォロー" | ||||||
|     mute-list: "ミュート" |     mute-list: "ミュート" | ||||||
|     blocking-list: "ブロック" |     blocking-list: "ブロック" | ||||||
|  |     user-lists: "リスト" | ||||||
|   enter-password: "パスワードを入れてや" |   enter-password: "パスワードを入れてや" | ||||||
| common/views/components/user-list-editor.vue: | common/views/components/user-list-editor.vue: | ||||||
|   users: "ユーザー" |   users: "ユーザー" | ||||||
| @@ -858,8 +860,6 @@ admin/views/dashboard.vue: | |||||||
|   instances: "インスタンス" |   instances: "インスタンス" | ||||||
|   this-instance: "ワイのインスタンス" |   this-instance: "ワイのインスタンス" | ||||||
|   federated: "連合" |   federated: "連合" | ||||||
| admin/views/queue.vue: |  | ||||||
|   operation: "操作" |  | ||||||
| admin/views/abuse.vue: | admin/views/abuse.vue: | ||||||
|   details: "もっと" |   details: "もっと" | ||||||
|   remove-report: "削除" |   remove-report: "削除" | ||||||
| @@ -988,7 +988,7 @@ admin/views/announcements.vue: | |||||||
|   add: "増やす" |   add: "増やす" | ||||||
|   saved: "保存したで!" |   saved: "保存したで!" | ||||||
| admin/views/federation.vue: | admin/views/federation.vue: | ||||||
|   federation: "連合" |   instance: "インスタンス" | ||||||
|   host: "ホスト" |   host: "ホスト" | ||||||
|   notes: "投稿" |   notes: "投稿" | ||||||
|   users: "ユーザー" |   users: "ユーザー" | ||||||
| @@ -997,7 +997,7 @@ admin/views/federation.vue: | |||||||
|   status: "ステータス" |   status: "ステータス" | ||||||
|   block: "ブロック" |   block: "ブロック" | ||||||
|   lookup: "照会" |   lookup: "照会" | ||||||
|   instances: "インスタンス" |   instances: "連合" | ||||||
|   states: |   states: | ||||||
|     all: "すべて" |     all: "すべて" | ||||||
|     blocked: "ブロック" |     blocked: "ブロック" | ||||||
|   | |||||||
| @@ -314,6 +314,7 @@ common/views/pages/explore.vue: | |||||||
|   users-info: "현재 {users} 사용자가 등록되어 있습니다" |   users-info: "현재 {users} 사용자가 등록되어 있습니다" | ||||||
| common/views/components/url-preview.vue: | common/views/components/url-preview.vue: | ||||||
|   enable-player: "플레이어 열기" |   enable-player: "플레이어 열기" | ||||||
|  |   disable-player: "플레이어 닫기" | ||||||
| common/views/components/user-list.vue: | common/views/components/user-list.vue: | ||||||
|   no-users: "사용자가 없습니다" |   no-users: "사용자가 없습니다" | ||||||
| common/views/components/games/reversi/reversi.vue: | common/views/components/games/reversi/reversi.vue: | ||||||
| @@ -647,12 +648,16 @@ common/views/components/profile-editor.vue: | |||||||
|   email-verified: "매일 주소가 확인되었습니다" |   email-verified: "매일 주소가 확인되었습니다" | ||||||
|   email-not-verified: "메일 주소가 확인되지 않았습니다. 받은 편지함을 확인하여 주시기 바랍니다." |   email-not-verified: "메일 주소가 확인되지 않았습니다. 받은 편지함을 확인하여 주시기 바랍니다." | ||||||
|   export: "내보내기" |   export: "내보내기" | ||||||
|  |   import: "가져오기" | ||||||
|  |   export-and-import: "내보내기와 가져오기" | ||||||
|   export-targets: |   export-targets: | ||||||
|     all-notes: "모든 글 데이터" |     all-notes: "모든 글 데이터" | ||||||
|     following-list: "팔로잉" |     following-list: "팔로잉" | ||||||
|     mute-list: "뮤트" |     mute-list: "뮤트" | ||||||
|     blocking-list: "차단" |     blocking-list: "차단" | ||||||
|  |     user-lists: "리스트" | ||||||
|   export-requested: "내보내기를 요청하였습니다. 이 작업은 시간이 걸릴 수 있습니다. 내보내기가 완료되면 드라이브에 파일이 추가됩니다." |   export-requested: "내보내기를 요청하였습니다. 이 작업은 시간이 걸릴 수 있습니다. 내보내기가 완료되면 드라이브에 파일이 추가됩니다." | ||||||
|  |   import-requested: "가져오기를 요청하였습니다. 이 작업에는 시간이 걸릴 수 있습니다." | ||||||
|   enter-password: "비밀번호를 입력하여 주십시오" |   enter-password: "비밀번호를 입력하여 주십시오" | ||||||
|   danger-zone: "위험한 설정" |   danger-zone: "위험한 설정" | ||||||
|   delete-account: "계정 삭제" |   delete-account: "계정 삭제" | ||||||
| @@ -1052,7 +1057,7 @@ admin/views/dashboard.vue: | |||||||
|   this-instance: "이 인스턴스" |   this-instance: "이 인스턴스" | ||||||
|   federated: "연합" |   federated: "연합" | ||||||
| admin/views/queue.vue: | admin/views/queue.vue: | ||||||
|   operation: "동작" |   title: "큐" | ||||||
|   remove-all-jobs: "모든 작업 제거" |   remove-all-jobs: "모든 작업 제거" | ||||||
| admin/views/abuse.vue: | admin/views/abuse.vue: | ||||||
|   title: "스팸 신고" |   title: "스팸 신고" | ||||||
| @@ -1270,12 +1275,13 @@ admin/views/announcements.vue: | |||||||
| admin/views/hashtags.vue: | admin/views/hashtags.vue: | ||||||
|   hided-tags: "Hidden Tags" |   hided-tags: "Hidden Tags" | ||||||
| admin/views/federation.vue: | admin/views/federation.vue: | ||||||
|   federation: "연합" |   instance: "인스턴스" | ||||||
|   host: "호스트" |   host: "호스트" | ||||||
|   notes: "글" |   notes: "글" | ||||||
|   users: "사용자" |   users: "사용자" | ||||||
|   following: "팔로우 중" |   following: "팔로우 중" | ||||||
|   followers: "팔로워" |   followers: "팔로워" | ||||||
|  |   caught-at: "등록 날짜" | ||||||
|   status: "상태" |   status: "상태" | ||||||
|   latest-request-sent-at: "마지막으로 요청을 전송한 시간" |   latest-request-sent-at: "마지막으로 요청을 전송한 시간" | ||||||
|   latest-request-received-at: "마지막으로 요청을 받은 시간" |   latest-request-received-at: "마지막으로 요청을 받은 시간" | ||||||
| @@ -1284,7 +1290,7 @@ admin/views/federation.vue: | |||||||
|   block: "차단" |   block: "차단" | ||||||
|   marked-as-closed: "폐쇄된 것으로 표시" |   marked-as-closed: "폐쇄된 것으로 표시" | ||||||
|   lookup: "조회" |   lookup: "조회" | ||||||
|   instances: "인스턴스" |   instances: "연합" | ||||||
|   instance-not-registered: "해당 인스턴스가 등록되어 있지 않습니다" |   instance-not-registered: "해당 인스턴스가 등록되어 있지 않습니다" | ||||||
|   sort: "정렬" |   sort: "정렬" | ||||||
|   sorts: |   sorts: | ||||||
| @@ -1347,8 +1353,6 @@ desktop/views/pages/selectdrive.vue: | |||||||
| desktop/views/pages/search.vue: | desktop/views/pages/search.vue: | ||||||
|   not-available: "검색 기능은 인스턴스 설정에서 비활성화되어 있습니다." |   not-available: "검색 기능은 인스턴스 설정에서 비활성화되어 있습니다." | ||||||
|   not-found: "\"{q}\" 와 일치하는 글을 찾을 수 없습니다." |   not-found: "\"{q}\" 와 일치하는 글을 찾을 수 없습니다." | ||||||
| desktop/views/pages/share.vue: |  | ||||||
|   share-with: "{name}(으)로 공유" |  | ||||||
| desktop/views/pages/tag.vue: | desktop/views/pages/tag.vue: | ||||||
|   no-posts-found: "해시태그 \"{q}\"가 붙은 글을 찾을 수 없습니다." |   no-posts-found: "해시태그 \"{q}\"가 붙은 글을 찾을 수 없습니다." | ||||||
| desktop/views/pages/user-list.users.vue: | desktop/views/pages/user-list.users.vue: | ||||||
|   | |||||||
| @@ -196,6 +196,7 @@ common/views/components/profile-editor.vue: | |||||||
|   banner: "Omslagfoto" |   banner: "Omslagfoto" | ||||||
|   export-targets: |   export-targets: | ||||||
|     following-list: "Volgend" |     following-list: "Volgend" | ||||||
|  |     user-lists: "Lijsten" | ||||||
|   enter-password: "Voer het wachtwoord in" |   enter-password: "Voer het wachtwoord in" | ||||||
| common/views/components/user-list-editor.vue: | common/views/components/user-list-editor.vue: | ||||||
|   users: "Gebruiker" |   users: "Gebruiker" | ||||||
|   | |||||||
| @@ -187,6 +187,7 @@ common/views/components/profile-editor.vue: | |||||||
|   save: "Lagre" |   save: "Lagre" | ||||||
|   export-targets: |   export-targets: | ||||||
|     following-list: "Følger" |     following-list: "Følger" | ||||||
|  |     user-lists: "Lister" | ||||||
| common/views/components/user-list-editor.vue: | common/views/components/user-list-editor.vue: | ||||||
|   users: "Bruker" |   users: "Bruker" | ||||||
| common/views/widgets/broadcast.vue: | common/views/widgets/broadcast.vue: | ||||||
|   | |||||||
| @@ -121,8 +121,35 @@ common: | |||||||
|     other: "Inne" |     other: "Inne" | ||||||
|     appearance: "Wygląd" |     appearance: "Wygląd" | ||||||
|     behavior: "Zachowanie" |     behavior: "Zachowanie" | ||||||
|  |     fetch-on-scroll: "Automatycznie ładuj po przeciągnięciu w dół" | ||||||
|     note-visibility: "Widoczność wpisów" |     note-visibility: "Widoczność wpisów" | ||||||
|  |     web-search-engine: "Wyszukiwarka internetowa" | ||||||
|  |     line-width: "Szerokości linii" | ||||||
|  |     line-width-thin: "Cienka" | ||||||
|  |     line-width-normal: "Normalna" | ||||||
|  |     line-width-thick: "Gruba" | ||||||
|  |     font-size: "Rozmiar tekstu" | ||||||
|  |     font-size-x-small: "Małe" | ||||||
|  |     font-size-medium: "Normalna" | ||||||
|  |     font-size-large: "Trochę duży" | ||||||
|  |     font-size-x-large: "Duży" | ||||||
|  |     deck-column-align-center: "Po środku" | ||||||
|  |     deck-column-align-left: "Z lewej" | ||||||
|  |     deck-column-align-flexible: "Elastyczne" | ||||||
|  |     deck-column-width: "Szerokość kolumn w talii" | ||||||
|  |     deck-column-width-narrow: "Wąska" | ||||||
|  |     deck-column-width-narrower: "Trochę wąska" | ||||||
|  |     deck-column-width-normal: "Normalna" | ||||||
|  |     deck-column-width-wider: "Trochę szerokie" | ||||||
|  |     deck-column-width-wide: "Szeroka" | ||||||
|  |     wallpaper: "Tapeta" | ||||||
|  |     choose-wallpaper: "Wybierz tapetę" | ||||||
|     timeline: "Oś czasu" |     timeline: "Oś czasu" | ||||||
|  |     sound: "Dźwięk" | ||||||
|  |     test: "Test" | ||||||
|  |     update: "Aktualizacja Misskey" | ||||||
|  |     version: "Wersja:" | ||||||
|  |     navbar-position-left: "Z lewej" | ||||||
|   search: "Szukaj" |   search: "Szukaj" | ||||||
|   delete: "Usuń" |   delete: "Usuń" | ||||||
|   loading: "Ładowanie" |   loading: "Ładowanie" | ||||||
| @@ -474,10 +501,12 @@ common/views/components/profile-editor.vue: | |||||||
|   email-address: "Adres e-mail" |   email-address: "Adres e-mail" | ||||||
|   email-verified: "Twój adres e-mail został zweryfikowany." |   email-verified: "Twój adres e-mail został zweryfikowany." | ||||||
|   export: "Eksportuj" |   export: "Eksportuj" | ||||||
|  |   import: "Importuj" | ||||||
|   export-targets: |   export-targets: | ||||||
|     following-list: "Śledzeni" |     following-list: "Śledzeni" | ||||||
|     mute-list: "Wycisz" |     mute-list: "Wycisz" | ||||||
|     blocking-list: "Zablokuj" |     blocking-list: "Zablokuj" | ||||||
|  |     user-lists: "Listy" | ||||||
|   enter-password: "Wprowadź hasło" |   enter-password: "Wprowadź hasło" | ||||||
| common/views/components/user-list-editor.vue: | common/views/components/user-list-editor.vue: | ||||||
|   users: "Użytkownicy" |   users: "Użytkownicy" | ||||||
| @@ -918,13 +947,14 @@ admin/views/announcements.vue: | |||||||
|     are-you-sure: "Usunąć \"$1\"?" |     are-you-sure: "Usunąć \"$1\"?" | ||||||
|     removed: "Usunięto" |     removed: "Usunięto" | ||||||
| admin/views/federation.vue: | admin/views/federation.vue: | ||||||
|  |   instance: "Instancja" | ||||||
|   notes: "Wpis" |   notes: "Wpis" | ||||||
|   users: "Użytkownicy" |   users: "Użytkownicy" | ||||||
|   following: "Śledzisz" |   following: "Śledzisz" | ||||||
|   followers: "Śledzący" |   followers: "Śledzący" | ||||||
|  |   caught-at: "Utworzono" | ||||||
|   status: "Stan" |   status: "Stan" | ||||||
|   block: "Zablokuj" |   block: "Zablokuj" | ||||||
|   instances: "Instancja" |  | ||||||
|   sort: "Sortuj" |   sort: "Sortuj" | ||||||
|   states: |   states: | ||||||
|     all: "Wszyscy" |     all: "Wszyscy" | ||||||
|   | |||||||
| @@ -314,6 +314,7 @@ common/views/pages/explore.vue: | |||||||
|   users-info: "当前有{users}个注册用户" |   users-info: "当前有{users}个注册用户" | ||||||
| common/views/components/url-preview.vue: | common/views/components/url-preview.vue: | ||||||
|   enable-player: "打开播放器" |   enable-player: "打开播放器" | ||||||
|  |   disable-player: "关闭播放器" | ||||||
| common/views/components/user-list.vue: | common/views/components/user-list.vue: | ||||||
|   no-users: "无用户" |   no-users: "无用户" | ||||||
| common/views/components/games/reversi/reversi.vue: | common/views/components/games/reversi/reversi.vue: | ||||||
| @@ -647,12 +648,16 @@ common/views/components/profile-editor.vue: | |||||||
|   email-verified: "电子邮件地址已验证" |   email-verified: "电子邮件地址已验证" | ||||||
|   email-not-verified: "邮件地址尚未验证。 请检查您的邮箱。" |   email-not-verified: "邮件地址尚未验证。 请检查您的邮箱。" | ||||||
|   export: "导出" |   export: "导出" | ||||||
|  |   import: "导入" | ||||||
|  |   export-and-import: "导出/导入" | ||||||
|   export-targets: |   export-targets: | ||||||
|     all-notes: "所有发帖" |     all-notes: "所有发帖" | ||||||
|     following-list: "关注列表" |     following-list: "关注列表" | ||||||
|     mute-list: "屏蔽列表" |     mute-list: "屏蔽列表" | ||||||
|     blocking-list: "黑名单" |     blocking-list: "黑名单" | ||||||
|  |     user-lists: "列表" | ||||||
|   export-requested: "导出请求已提交。可能需要花一些时间。导出的文件将保存到网盘中。" |   export-requested: "导出请求已提交。可能需要花一些时间。导出的文件将保存到网盘中。" | ||||||
|  |   import-requested: "导入请求已提交。这可能需要花一点时间。" | ||||||
|   enter-password: "请输入您的密码" |   enter-password: "请输入您的密码" | ||||||
|   danger-zone: "危险选项" |   danger-zone: "危险选项" | ||||||
|   delete-account: "删除帐户" |   delete-account: "删除帐户" | ||||||
| @@ -1052,7 +1057,7 @@ admin/views/dashboard.vue: | |||||||
|   this-instance: "此实例" |   this-instance: "此实例" | ||||||
|   federated: "联合" |   federated: "联合" | ||||||
| admin/views/queue.vue: | admin/views/queue.vue: | ||||||
|   operation: "操作" |   title: "队列" | ||||||
|   remove-all-jobs: "清除所有作业" |   remove-all-jobs: "清除所有作业" | ||||||
| admin/views/abuse.vue: | admin/views/abuse.vue: | ||||||
|   title: "举报垃圾信息" |   title: "举报垃圾信息" | ||||||
| @@ -1270,12 +1275,13 @@ admin/views/announcements.vue: | |||||||
| admin/views/hashtags.vue: | admin/views/hashtags.vue: | ||||||
|   hided-tags: "隐藏标签" |   hided-tags: "隐藏标签" | ||||||
| admin/views/federation.vue: | admin/views/federation.vue: | ||||||
|   federation: "联合" |   instance: "例" | ||||||
|   host: "主机名" |   host: "主机名" | ||||||
|   notes: "帖子" |   notes: "帖子" | ||||||
|   users: "用户" |   users: "用户" | ||||||
|   following: "正在关注" |   following: "正在关注" | ||||||
|   followers: "关注者" |   followers: "关注者" | ||||||
|  |   caught-at: "注册日期" | ||||||
|   status: "状态" |   status: "状态" | ||||||
|   latest-request-sent-at: "上次发送的请求" |   latest-request-sent-at: "上次发送的请求" | ||||||
|   latest-request-received-at: "上次收到的请求" |   latest-request-received-at: "上次收到的请求" | ||||||
| @@ -1284,7 +1290,7 @@ admin/views/federation.vue: | |||||||
|   block: "拉黑" |   block: "拉黑" | ||||||
|   marked-as-closed: "标记为已关闭" |   marked-as-closed: "标记为已关闭" | ||||||
|   lookup: "查询" |   lookup: "查询" | ||||||
|   instances: "实例" |   instances: "联合" | ||||||
|   instance-not-registered: "实例未注册" |   instance-not-registered: "实例未注册" | ||||||
|   sort: "排序" |   sort: "排序" | ||||||
|   sorts: |   sorts: | ||||||
| @@ -1347,8 +1353,6 @@ desktop/views/pages/selectdrive.vue: | |||||||
| desktop/views/pages/search.vue: | desktop/views/pages/search.vue: | ||||||
|   not-available: "在此实例的设置中关闭搜索功能。" |   not-available: "在此实例的设置中关闭搜索功能。" | ||||||
|   not-found: "没有找到“{q}”的帖子" |   not-found: "没有找到“{q}”的帖子" | ||||||
| desktop/views/pages/share.vue: |  | ||||||
|   share-with: "共享{name}" |  | ||||||
| desktop/views/pages/tag.vue: | desktop/views/pages/tag.vue: | ||||||
|   no-posts-found: "没有找到带有主题标签“{q}”的帖子" |   no-posts-found: "没有找到带有主题标签“{q}”的帖子" | ||||||
| desktop/views/pages/user-list.users.vue: | desktop/views/pages/user-list.users.vue: | ||||||
|   | |||||||
							
								
								
									
										46
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,7 +1,7 @@ | |||||||
| { | { | ||||||
| 	"name": "misskey", | 	"name": "misskey", | ||||||
| 	"author": "syuilo <i@syuilo.com>", | 	"author": "syuilo <i@syuilo.com>", | ||||||
| 	"version": "10.92.3", | 	"version": "10.98.2", | ||||||
| 	"codename": "nighthike", | 	"codename": "nighthike", | ||||||
| 	"repository": { | 	"repository": { | ||||||
| 		"type": "git", | 		"type": "git", | ||||||
| @@ -46,7 +46,6 @@ | |||||||
| 		"@types/gulp-uglify": "3.0.6", | 		"@types/gulp-uglify": "3.0.6", | ||||||
| 		"@types/gulp-util": "3.0.34", | 		"@types/gulp-util": "3.0.34", | ||||||
| 		"@types/is-root": "1.0.0", | 		"@types/is-root": "1.0.0", | ||||||
| 		"@types/is-svg": "3.0.0", |  | ||||||
| 		"@types/is-url": "1.2.28", | 		"@types/is-url": "1.2.28", | ||||||
| 		"@types/js-yaml": "3.12.0", | 		"@types/js-yaml": "3.12.0", | ||||||
| 		"@types/jsdom": "12.2.3", | 		"@types/jsdom": "12.2.3", | ||||||
| @@ -78,6 +77,7 @@ | |||||||
| 		"@types/qrcode": "1.3.0", | 		"@types/qrcode": "1.3.0", | ||||||
| 		"@types/ratelimiter": "2.1.28", | 		"@types/ratelimiter": "2.1.28", | ||||||
| 		"@types/redis": "2.8.10", | 		"@types/redis": "2.8.10", | ||||||
|  | 		"@types/rename": "1.0.1", | ||||||
| 		"@types/request": "2.48.1", | 		"@types/request": "2.48.1", | ||||||
| 		"@types/request-promise-native": "1.0.15", | 		"@types/request-promise-native": "1.0.15", | ||||||
| 		"@types/request-stats": "3.0.0", | 		"@types/request-stats": "3.0.0", | ||||||
| @@ -96,7 +96,7 @@ | |||||||
| 		"@types/websocket": "0.0.40", | 		"@types/websocket": "0.0.40", | ||||||
| 		"@types/ws": "6.0.1", | 		"@types/ws": "6.0.1", | ||||||
| 		"animejs": "3.0.1", | 		"animejs": "3.0.1", | ||||||
| 		"apexcharts": "3.5.0", | 		"apexcharts": "3.6.2", | ||||||
| 		"autobind-decorator": "2.4.0", | 		"autobind-decorator": "2.4.0", | ||||||
| 		"autosize": "4.0.2", | 		"autosize": "4.0.2", | ||||||
| 		"autwh": "0.1.0", | 		"autwh": "0.1.0", | ||||||
| @@ -108,21 +108,22 @@ | |||||||
| 		"chai-http": "4.2.1", | 		"chai-http": "4.2.1", | ||||||
| 		"chalk": "2.4.2", | 		"chalk": "2.4.2", | ||||||
| 		"commander": "2.19.0", | 		"commander": "2.19.0", | ||||||
|  | 		"content-disposition": "0.5.3", | ||||||
| 		"crc-32": "1.2.0", | 		"crc-32": "1.2.0", | ||||||
| 		"css-loader": "2.1.0", | 		"css-loader": "2.1.1", | ||||||
| 		"cssnano": "4.1.10", | 		"cssnano": "4.1.10", | ||||||
| 		"dateformat": "3.0.3", | 		"dateformat": "3.0.3", | ||||||
| 		"deep-equal": "1.0.1", | 		"deep-equal": "1.0.1", | ||||||
| 		"deepcopy": "0.6.3", | 		"deepcopy": "0.6.3", | ||||||
| 		"diskusage": "1.0.0", | 		"diskusage": "1.0.0", | ||||||
| 		"double-ended-queue": "2.1.0-0", | 		"double-ended-queue": "2.1.0-0", | ||||||
| 		"elasticsearch": "15.3.1", | 		"elasticsearch": "15.4.1", | ||||||
| 		"emojilib": "2.4.0", | 		"emojilib": "2.4.0", | ||||||
| 		"escape-regexp": "0.0.1", | 		"escape-regexp": "0.0.1", | ||||||
| 		"eslint": "5.15.0", | 		"eslint": "5.15.1", | ||||||
| 		"eslint-plugin-vue": "5.2.2", | 		"eslint-plugin-vue": "5.2.2", | ||||||
| 		"eventemitter3": "3.1.0", | 		"eventemitter3": "3.1.0", | ||||||
| 		"feed": "2.0.2", | 		"feed": "2.0.4", | ||||||
| 		"file-type": "10.9.0", | 		"file-type": "10.9.0", | ||||||
| 		"fuckadblock": "3.2.1", | 		"fuckadblock": "3.2.1", | ||||||
| 		"gulp": "4.0.0", | 		"gulp": "4.0.0", | ||||||
| @@ -131,20 +132,20 @@ | |||||||
| 		"gulp-mocha": "6.0.0", | 		"gulp-mocha": "6.0.0", | ||||||
| 		"gulp-rename": "1.4.0", | 		"gulp-rename": "1.4.0", | ||||||
| 		"gulp-replace": "1.0.0", | 		"gulp-replace": "1.0.0", | ||||||
| 		"gulp-sourcemaps": "2.6.4", | 		"gulp-sourcemaps": "2.6.5", | ||||||
| 		"gulp-stylus": "2.7.0", | 		"gulp-stylus": "2.7.0", | ||||||
| 		"gulp-tslint": "8.1.3", | 		"gulp-tslint": "8.1.4", | ||||||
| 		"gulp-typescript": "5.0.0", | 		"gulp-typescript": "5.0.0", | ||||||
| 		"gulp-uglify": "3.0.1", | 		"gulp-uglify": "3.0.2", | ||||||
| 		"gulp-util": "3.0.8", | 		"gulp-util": "3.0.8", | ||||||
| 		"hard-source-webpack-plugin": "0.13.1", | 		"hard-source-webpack-plugin": "0.13.1", | ||||||
| 		"html-minifier": "3.5.21", | 		"html-minifier": "3.5.21", | ||||||
| 		"http-signature": "1.2.0", | 		"http-signature": "1.2.0", | ||||||
| 		"insert-text-at-cursor": "0.1.2", | 		"insert-text-at-cursor": "0.1.2", | ||||||
| 		"is-root": "2.0.0", | 		"is-root": "2.0.0", | ||||||
| 		"is-svg": "3.0.0", | 		"is-svg": "4.0.0", | ||||||
| 		"js-yaml": "3.12.1", | 		"js-yaml": "3.12.2", | ||||||
| 		"jsdom": "13.2.0", | 		"jsdom": "14.0.0", | ||||||
| 		"json5": "2.1.0", | 		"json5": "2.1.0", | ||||||
| 		"json5-loader": "1.0.1", | 		"json5-loader": "1.0.1", | ||||||
| 		"katex": "0.10.1", | 		"katex": "0.10.1", | ||||||
| @@ -189,13 +190,14 @@ | |||||||
| 		"pug": "2.0.3", | 		"pug": "2.0.3", | ||||||
| 		"punycode": "2.1.1", | 		"punycode": "2.1.1", | ||||||
| 		"qrcode": "1.3.3", | 		"qrcode": "1.3.3", | ||||||
| 		"randomcolor": "0.5.3", | 		"randomcolor": "0.5.4", | ||||||
| 		"ratelimiter": "3.2.0", | 		"ratelimiter": "3.3.0", | ||||||
| 		"recaptcha-promise": "0.1.3", | 		"recaptcha-promise": "0.1.3", | ||||||
| 		"reconnecting-websocket": "4.1.10", | 		"reconnecting-websocket": "4.1.10", | ||||||
| 		"redis": "2.8.0", | 		"redis": "2.8.0", | ||||||
|  | 		"rename": "1.0.4", | ||||||
| 		"request": "2.88.0", | 		"request": "2.88.0", | ||||||
| 		"request-promise-native": "1.0.5", | 		"request-promise-native": "1.0.7", | ||||||
| 		"request-stats": "3.0.0", | 		"request-stats": "3.0.0", | ||||||
| 		"rimraf": "2.6.3", | 		"rimraf": "2.6.3", | ||||||
| 		"rndstr": "1.0.0", | 		"rndstr": "1.0.0", | ||||||
| @@ -210,14 +212,14 @@ | |||||||
| 		"stylus": "0.54.5", | 		"stylus": "0.54.5", | ||||||
| 		"stylus-loader": "3.0.2", | 		"stylus-loader": "3.0.2", | ||||||
| 		"summaly": "2.2.0", | 		"summaly": "2.2.0", | ||||||
| 		"systeminformation": "4.0.14", | 		"systeminformation": "4.0.16", | ||||||
| 		"syuilo-password-strength": "0.0.1", | 		"syuilo-password-strength": "0.0.1", | ||||||
| 		"terser-webpack-plugin": "1.2.3", | 		"terser-webpack-plugin": "1.2.3", | ||||||
| 		"textarea-caret": "3.1.0", | 		"textarea-caret": "3.1.0", | ||||||
| 		"tinycolor2": "1.4.1", | 		"tinycolor2": "1.4.1", | ||||||
| 		"tmp": "0.0.33", | 		"tmp": "0.0.33", | ||||||
| 		"ts-loader": "5.3.3", | 		"ts-loader": "5.3.3", | ||||||
| 		"ts-node": "8.0.2", | 		"ts-node": "8.0.3", | ||||||
| 		"tslint": "5.13.1", | 		"tslint": "5.13.1", | ||||||
| 		"tslint-sonarts": "1.9.0", | 		"tslint-sonarts": "1.9.0", | ||||||
| 		"typescript": "3.3.3333", | 		"typescript": "3.3.3333", | ||||||
| @@ -232,7 +234,7 @@ | |||||||
| 		"vue-color": "2.7.0", | 		"vue-color": "2.7.0", | ||||||
| 		"vue-content-loading": "1.5.3", | 		"vue-content-loading": "1.5.3", | ||||||
| 		"vue-cropperjs": "3.0.0", | 		"vue-cropperjs": "3.0.0", | ||||||
| 		"vue-i18n": "8.8.2", | 		"vue-i18n": "8.9.0", | ||||||
| 		"vue-js-modal": "1.3.28", | 		"vue-js-modal": "1.3.28", | ||||||
| 		"vue-json-pretty": "1.4.1", | 		"vue-json-pretty": "1.4.1", | ||||||
| 		"vue-loader": "15.7.0", | 		"vue-loader": "15.7.0", | ||||||
| @@ -241,9 +243,9 @@ | |||||||
| 		"vue-router": "3.0.2", | 		"vue-router": "3.0.2", | ||||||
| 		"vue-sequential-entrance": "1.1.3", | 		"vue-sequential-entrance": "1.1.3", | ||||||
| 		"vue-style-loader": "4.1.2", | 		"vue-style-loader": "4.1.2", | ||||||
| 		"vue-svg-inline-loader": "1.2.12", | 		"vue-svg-inline-loader": "1.2.13", | ||||||
| 		"vue-template-compiler": "2.6.8", | 		"vue-template-compiler": "2.6.8", | ||||||
| 		"vuedraggable": "2.18.1", | 		"vuedraggable": "2.19.2", | ||||||
| 		"vuewordcloud": "18.7.11", | 		"vuewordcloud": "18.7.11", | ||||||
| 		"vuex": "3.1.0", | 		"vuex": "3.1.0", | ||||||
| 		"vuex-persistedstate": "2.5.4", | 		"vuex-persistedstate": "2.5.4", | ||||||
| @@ -252,7 +254,7 @@ | |||||||
| 		"webpack": "4.28.4", | 		"webpack": "4.28.4", | ||||||
| 		"webpack-cli": "3.2.3", | 		"webpack-cli": "3.2.3", | ||||||
| 		"websocket": "1.0.28", | 		"websocket": "1.0.28", | ||||||
| 		"ws": "6.1.4", | 		"ws": "6.2.0", | ||||||
| 		"xev": "2.0.1" | 		"xev": "2.0.1" | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								src/@types/koa-slow.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/@types/koa-slow.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -8,7 +8,7 @@ declare module 'koa-slow' { | |||||||
|  |  | ||||||
| 	function slow(options?: ISlowOptions): Middleware; | 	function slow(options?: ISlowOptions): Middleware; | ||||||
|  |  | ||||||
| 	namespace slow { } // Hack | 	namespace slow {} // Hack | ||||||
|  |  | ||||||
| 	export = slow; | 	export = slow; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -181,7 +181,12 @@ export default Vue.extend({ | |||||||
| 				}, | 				}, | ||||||
| 				grid: { | 				grid: { | ||||||
| 					clipMarkers: false, | 					clipMarkers: false, | ||||||
| 					borderColor: 'rgba(0, 0, 0, 0.1)' | 					borderColor: 'rgba(0, 0, 0, 0.1)', | ||||||
|  | 					xaxis: { | ||||||
|  | 						lines: { | ||||||
|  | 							show: true, | ||||||
|  | 						} | ||||||
|  | 					}, | ||||||
| 				}, | 				}, | ||||||
| 				stroke: { | 				stroke: { | ||||||
| 					curve: 'straight', | 					curve: 'straight', | ||||||
| @@ -240,6 +245,7 @@ export default Vue.extend({ | |||||||
| 		federationInstancesChart(total: boolean): any { | 		federationInstancesChart(total: boolean): any { | ||||||
| 			return { | 			return { | ||||||
| 				series: [{ | 				series: [{ | ||||||
|  | 					name: 'Instances', | ||||||
| 					data: this.format(total | 					data: this.format(total | ||||||
| 						? this.stats.federation.instance.total | 						? this.stats.federation.instance.total | ||||||
| 						: sum(this.stats.federation.instance.inc, negate(this.stats.federation.instance.dec)) | 						: sum(this.stats.federation.instance.inc, negate(this.stats.federation.instance.dec)) | ||||||
							
								
								
									
										196
									
								
								src/client/app/admin/views/dashboard.queue-charts.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								src/client/app/admin/views/dashboard.queue-charts.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,196 @@ | |||||||
|  | <template> | ||||||
|  | <div class="mzxlfysy"> | ||||||
|  | 	<div> | ||||||
|  | 		<header> | ||||||
|  | 			<span><fa :icon="faInbox"/> In</span> | ||||||
|  | 			<span v-if="latestStats">{{ latestStats.inbox.activeSincePrevTick | number }} / {{ latestStats.inbox.delayed | number }}</span> | ||||||
|  | 		</header> | ||||||
|  | 		<div ref="in"></div> | ||||||
|  | 	</div> | ||||||
|  | 	<div> | ||||||
|  | 		<header> | ||||||
|  | 			<span><fa :icon="faPaperPlane"/> Out</span> | ||||||
|  | 			<span v-if="latestStats">{{ latestStats.deliver.activeSincePrevTick | number }} / {{ latestStats.deliver.delayed | number }}</span> | ||||||
|  | 		</header> | ||||||
|  | 		<div ref="out"></div> | ||||||
|  | 	</div> | ||||||
|  | </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import Vue from 'vue'; | ||||||
|  | import { faInbox } from '@fortawesome/free-solid-svg-icons'; | ||||||
|  | import { faPaperPlane } from '@fortawesome/free-regular-svg-icons'; | ||||||
|  | import ApexCharts from 'apexcharts'; | ||||||
|  |  | ||||||
|  | const limit = 150; | ||||||
|  |  | ||||||
|  | export default Vue.extend({ | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			stats: [], | ||||||
|  | 			inChart: null, | ||||||
|  | 			outChart: null, | ||||||
|  | 			faInbox, faPaperPlane | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	computed: { | ||||||
|  | 		latestStats(): any { | ||||||
|  | 			return this.stats[this.stats.length - 1]; | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	watch: { | ||||||
|  | 		stats(stats) { | ||||||
|  | 			this.inChart.updateSeries([{ | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.inbox.activeSincePrevTick })) | ||||||
|  | 			}, { | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.inbox.active })) | ||||||
|  | 			}, { | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.inbox.waiting })) | ||||||
|  | 			}, { | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.inbox.delayed })) | ||||||
|  | 			}]); | ||||||
|  | 			this.outChart.updateSeries([{ | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.deliver.activeSincePrevTick })) | ||||||
|  | 			}, { | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.deliver.active })) | ||||||
|  | 			}, { | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.deliver.waiting })) | ||||||
|  | 			}, { | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.deliver.delayed })) | ||||||
|  | 			}]); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	mounted() { | ||||||
|  | 		const chartOpts = { | ||||||
|  | 			chart: { | ||||||
|  | 				type: 'area', | ||||||
|  | 				height: 200, | ||||||
|  | 				animations: { | ||||||
|  | 					dynamicAnimation: { | ||||||
|  | 						enabled: false | ||||||
|  | 					} | ||||||
|  | 				}, | ||||||
|  | 				toolbar: { | ||||||
|  | 					show: false | ||||||
|  | 				}, | ||||||
|  | 				zoom: { | ||||||
|  | 					enabled: false | ||||||
|  | 				} | ||||||
|  | 			}, | ||||||
|  | 			dataLabels: { | ||||||
|  | 				enabled: false | ||||||
|  | 			}, | ||||||
|  | 			grid: { | ||||||
|  | 				clipMarkers: false, | ||||||
|  | 				borderColor: 'rgba(0, 0, 0, 0.1)' | ||||||
|  | 			}, | ||||||
|  | 			stroke: { | ||||||
|  | 				curve: 'straight', | ||||||
|  | 				width: 2 | ||||||
|  | 			}, | ||||||
|  | 			tooltip: { | ||||||
|  | 				enabled: false | ||||||
|  | 			}, | ||||||
|  | 			legend: { | ||||||
|  | 				show: false | ||||||
|  | 			}, | ||||||
|  | 			colors: ['#00E396', '#00BCD4', '#FFB300', '#e53935'], | ||||||
|  | 			series: [{ data: [] }, { data: [] }, { data: [] }, { data: [] }] as any, | ||||||
|  | 			xaxis: { | ||||||
|  | 				type: 'numeric', | ||||||
|  | 				labels: { | ||||||
|  | 					show: false | ||||||
|  | 				}, | ||||||
|  | 				tooltip: { | ||||||
|  | 					enabled: false | ||||||
|  | 				} | ||||||
|  | 			}, | ||||||
|  | 			yaxis: { | ||||||
|  | 				show: false, | ||||||
|  | 				min: 0, | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		this.inChart = new ApexCharts(this.$refs.in, chartOpts); | ||||||
|  | 		this.outChart = new ApexCharts(this.$refs.out, chartOpts); | ||||||
|  |  | ||||||
|  | 		this.inChart.render(); | ||||||
|  | 		this.outChart.render(); | ||||||
|  |  | ||||||
|  | 		const connection = this.$root.stream.useSharedConnection('queueStats'); | ||||||
|  | 		connection.on('stats', this.onStats); | ||||||
|  | 		connection.on('statsLog', this.onStatsLog); | ||||||
|  | 		connection.send('requestLog', { | ||||||
|  | 			id: Math.random().toString().substr(2, 8), | ||||||
|  | 			length: limit | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		this.$once('hook:beforeDestroy', () => { | ||||||
|  | 			connection.dispose(); | ||||||
|  | 			this.inChart.destroy(); | ||||||
|  | 			this.outChart.destroy(); | ||||||
|  | 		}); | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	methods: { | ||||||
|  | 		onStats(stats) { | ||||||
|  | 			this.stats.push(stats); | ||||||
|  | 			if (this.stats.length > limit) this.stats.shift(); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		onStatsLog(statsLog) { | ||||||
|  | 			for (const stats of statsLog.reverse()) { | ||||||
|  | 				this.onStats(stats); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="stylus" scoped> | ||||||
|  | .mzxlfysy | ||||||
|  | 	display flex | ||||||
|  |  | ||||||
|  | 	> div | ||||||
|  | 		display block | ||||||
|  | 		flex 1 | ||||||
|  | 		padding 20px 12px 0 12px | ||||||
|  | 		box-shadow 0 2px 4px rgba(0, 0, 0, 0.1) | ||||||
|  | 		background var(--face) | ||||||
|  | 		border-radius 8px | ||||||
|  |  | ||||||
|  | 		&:first-child | ||||||
|  | 			margin-right 16px | ||||||
|  |  | ||||||
|  | 		> header | ||||||
|  | 			display flex | ||||||
|  | 			padding 0 8px | ||||||
|  | 			margin-bottom -16px | ||||||
|  | 			color var(--adminDashboardCardFg) | ||||||
|  | 			font-size 14px | ||||||
|  |  | ||||||
|  | 			> span | ||||||
|  | 				&:last-child | ||||||
|  | 					margin-left auto | ||||||
|  | 					opacity 0.7 | ||||||
|  |  | ||||||
|  | 				> span | ||||||
|  | 					opacity 0.7 | ||||||
|  |  | ||||||
|  | 		> div | ||||||
|  | 			margin-bottom -10px | ||||||
|  |  | ||||||
|  | 	@media (max-width 1000px) | ||||||
|  | 		display block | ||||||
|  | 		margin-bottom 26px | ||||||
|  |  | ||||||
|  | 		> div | ||||||
|  | 			&:first-child | ||||||
|  | 				margin-right 0 | ||||||
|  | 				margin-bottom 26px | ||||||
|  |  | ||||||
|  | </style> | ||||||
| @@ -73,6 +73,10 @@ | |||||||
| 		<x-charts ref="charts"/> | 		<x-charts ref="charts"/> | ||||||
| 	</div> | 	</div> | ||||||
|  |  | ||||||
|  | 	<div class="queue"> | ||||||
|  | 		<x-queue/> | ||||||
|  | 	</div> | ||||||
|  |  | ||||||
| 	<div class="cpu-memory"> | 	<div class="cpu-memory"> | ||||||
| 		<x-cpu-memory :connection="connection"/> | 		<x-cpu-memory :connection="connection"/> | ||||||
| 	</div> | 	</div> | ||||||
| @@ -86,9 +90,10 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import i18n from '../../i18n'; | import i18n from '../../i18n'; | ||||||
| import XCpuMemory from "./cpu-memory.vue"; | import XCpuMemory from "./dashboard.cpu-memory.vue"; | ||||||
| import XCharts from "./charts.vue"; | import XQueue from "./dashboard.queue-charts.vue"; | ||||||
| import XApLog from "./ap-log.vue"; | import XCharts from "./dashboard.charts.vue"; | ||||||
|  | import XApLog from "./dashboard.ap-log.vue"; | ||||||
| import { faDatabase } from '@fortawesome/free-solid-svg-icons'; | import { faDatabase } from '@fortawesome/free-solid-svg-icons'; | ||||||
| import MarqueeText from 'vue-marquee-text-component'; | import MarqueeText from 'vue-marquee-text-component'; | ||||||
| import randomColor from 'randomcolor'; | import randomColor from 'randomcolor'; | ||||||
| @@ -98,6 +103,7 @@ export default Vue.extend({ | |||||||
|  |  | ||||||
| 	components: { | 	components: { | ||||||
| 		XCpuMemory, | 		XCpuMemory, | ||||||
|  | 		XQueue, | ||||||
| 		XCharts, | 		XCharts, | ||||||
| 		XApLog, | 		XApLog, | ||||||
| 		MarqueeText | 		MarqueeText | ||||||
| @@ -274,6 +280,9 @@ export default Vue.extend({ | |||||||
| 	> .charts | 	> .charts | ||||||
| 		margin-bottom 16px | 		margin-bottom 16px | ||||||
|  |  | ||||||
|  | 	> .queue | ||||||
|  | 		margin-bottom 16px | ||||||
|  |  | ||||||
| 	> .cpu-memory | 	> .cpu-memory | ||||||
| 		margin-bottom 16px | 		margin-bottom 16px | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,43 +1,58 @@ | |||||||
| <template> | <template> | ||||||
| <div> | <div> | ||||||
| 	<ui-card> | 	<ui-card> | ||||||
| 		<template #title><fa :icon="faTerminal"/> {{ $t('federation') }}</template> | 		<template #title><fa :icon="faTerminal"/> {{ $t('instance') }}</template> | ||||||
| 		<section class="fit-top"> | 		<section class="fit-top"> | ||||||
| 			<ui-input class="target" v-model="target" type="text" @enter="showInstance()"> | 			<ui-input class="target" v-model="target" type="text" @enter="showInstance()"> | ||||||
| 				<span>{{ $t('host') }}</span> | 				<span>{{ $t('host') }}</span> | ||||||
|  | 				<template #prefix><fa :icon="faServer"/></template> | ||||||
| 			</ui-input> | 			</ui-input> | ||||||
| 			<ui-button @click="showInstance()"><fa :icon="faSearch"/> {{ $t('lookup') }}</ui-button> | 			<ui-button @click="showInstance()"><fa :icon="faSearch"/> {{ $t('lookup') }}</ui-button> | ||||||
|  |  | ||||||
| 			<div class="instance" v-if="instance"> | 			<div class="instance" v-if="instance"> | ||||||
|  | 				<ui-horizon-group inputs> | ||||||
| 					<ui-input :value="instance.host" type="text" readonly> | 					<ui-input :value="instance.host" type="text" readonly> | ||||||
| 						<span>{{ $t('host') }}</span> | 						<span>{{ $t('host') }}</span> | ||||||
|  | 						<template #prefix><fa :icon="faServer"/></template> | ||||||
| 					</ui-input> | 					</ui-input> | ||||||
|  | 					<ui-input :value="instance.caughtAt | date" type="text" readonly> | ||||||
|  | 						<span>{{ $t('caught-at') }}</span> | ||||||
|  | 						<template #prefix><fa :icon="faCrosshairs"/></template> | ||||||
|  | 					</ui-input> | ||||||
|  | 				</ui-horizon-group> | ||||||
| 				<ui-horizon-group inputs> | 				<ui-horizon-group inputs> | ||||||
| 					<ui-input :value="instance.notesCount | number" type="text" readonly> | 					<ui-input :value="instance.notesCount | number" type="text" readonly> | ||||||
| 						<span>{{ $t('notes') }}</span> | 						<span>{{ $t('notes') }}</span> | ||||||
|  | 						<template #prefix><fa :icon="faEnvelopeOpenText"/></template> | ||||||
| 					</ui-input> | 					</ui-input> | ||||||
| 					<ui-input :value="instance.usersCount | number" type="text" readonly> | 					<ui-input :value="instance.usersCount | number" type="text" readonly> | ||||||
| 						<span>{{ $t('users') }}</span> | 						<span>{{ $t('users') }}</span> | ||||||
|  | 						<template #prefix><fa :icon="faUsers"/></template> | ||||||
| 					</ui-input> | 					</ui-input> | ||||||
| 				</ui-horizon-group> | 				</ui-horizon-group> | ||||||
| 				<ui-horizon-group inputs> | 				<ui-horizon-group inputs> | ||||||
| 					<ui-input :value="instance.followingCount | number" type="text" readonly> | 					<ui-input :value="instance.followingCount | number" type="text" readonly> | ||||||
| 						<span>{{ $t('following') }}</span> | 						<span>{{ $t('following') }}</span> | ||||||
|  | 						<template #prefix><fa :icon="faCaretDown"/></template> | ||||||
| 					</ui-input> | 					</ui-input> | ||||||
| 					<ui-input :value="instance.followersCount | number" type="text" readonly> | 					<ui-input :value="instance.followersCount | number" type="text" readonly> | ||||||
| 						<span>{{ $t('followers') }}</span> | 						<span>{{ $t('followers') }}</span> | ||||||
|  | 						<template #prefix><fa :icon="faCaretUp"/></template> | ||||||
| 					</ui-input> | 					</ui-input> | ||||||
| 				</ui-horizon-group> | 				</ui-horizon-group> | ||||||
| 				<ui-horizon-group inputs> | 				<ui-horizon-group inputs> | ||||||
| 					<ui-input :value="instance.latestRequestSentAt" type="text" readonly> | 					<ui-input :value="instance.latestRequestSentAt | date" type="text" readonly> | ||||||
| 						<span>{{ $t('latest-request-sent-at') }}</span> | 						<span>{{ $t('latest-request-sent-at') }}</span> | ||||||
|  | 						<template #prefix><fa :icon="faPaperPlane"/></template> | ||||||
| 					</ui-input> | 					</ui-input> | ||||||
| 					<ui-input :value="instance.latestStatus" type="text" readonly> | 					<ui-input :value="instance.latestStatus" type="text" readonly> | ||||||
| 						<span>{{ $t('status') }}</span> | 						<span>{{ $t('status') }}</span> | ||||||
|  | 						<template #prefix><fa :icon="faTrafficLight"/></template> | ||||||
| 					</ui-input> | 					</ui-input> | ||||||
| 				</ui-horizon-group> | 				</ui-horizon-group> | ||||||
| 				<ui-input :value="instance.latestRequestReceivedAt" type="text" readonly> | 				<ui-input :value="instance.latestRequestReceivedAt | date" type="text" readonly> | ||||||
| 					<span>{{ $t('latest-request-received-at') }}</span> | 					<span>{{ $t('latest-request-received-at') }}</span> | ||||||
|  | 					<template #prefix><fa :icon="faInbox"/></template> | ||||||
| 				</ui-input> | 				</ui-input> | ||||||
| 				<ui-switch v-model="instance.isBlocked" @change="updateInstance()">{{ $t('block') }}</ui-switch> | 				<ui-switch v-model="instance.isBlocked" @change="updateInstance()">{{ $t('block') }}</ui-switch> | ||||||
| 				<ui-switch v-model="instance.isMarkedAsClosed" @change="updateInstance()">{{ $t('marked-as-closed') }}</ui-switch> | 				<ui-switch v-model="instance.isMarkedAsClosed" @change="updateInstance()">{{ $t('marked-as-closed') }}</ui-switch> | ||||||
| @@ -133,7 +148,8 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import i18n from '../../i18n'; | import i18n from '../../i18n'; | ||||||
| import { faGlobe, faTerminal, faSearch, faMinusCircle, faServer } from '@fortawesome/free-solid-svg-icons'; | import { faPaperPlane } from '@fortawesome/free-regular-svg-icons'; | ||||||
|  | import { faGlobe, faTerminal, faSearch, faMinusCircle, faServer, faCrosshairs, faEnvelopeOpenText, faUsers, faCaretDown, faCaretUp, faTrafficLight, faInbox } from '@fortawesome/free-solid-svg-icons'; | ||||||
| import ApexCharts from 'apexcharts'; | import ApexCharts from 'apexcharts'; | ||||||
| import * as tinycolor from 'tinycolor2'; | import * as tinycolor from 'tinycolor2'; | ||||||
|  |  | ||||||
| @@ -144,19 +160,23 @@ const negate = arr => arr.map(x => -x); | |||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('admin/views/federation.vue'), | 	i18n: i18n('admin/views/federation.vue'), | ||||||
|  |  | ||||||
|  | 	filters: { | ||||||
|  | 		date: v => v ? new Date(v).toLocaleString() : 'N/A' | ||||||
|  | 	}, | ||||||
|  |  | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			instance: null, | 			instance: null, | ||||||
| 			target: null, | 			target: null, | ||||||
| 			sort: '+lastCommunicatedAt', | 			sort: '+lastCommunicatedAt', | ||||||
| 			state: 'all', | 			state: 'all', | ||||||
| 			limit: 50, | 			limit: 100, | ||||||
| 			instances: [], | 			instances: [], | ||||||
| 			chart: null, | 			chart: null, | ||||||
| 			chartSrc: 'requests', | 			chartSrc: 'requests', | ||||||
| 			chartSpan: 'hour', | 			chartSpan: 'hour', | ||||||
| 			chartInstance: null, | 			chartInstance: null, | ||||||
| 			faGlobe, faTerminal, faSearch, faMinusCircle, faServer | 			faGlobe, faTerminal, faSearch, faMinusCircle, faServer, faCrosshairs, faEnvelopeOpenText, faUsers, faCaretDown, faCaretUp, faPaperPlane, faTrafficLight, faInbox | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ | |||||||
| 			<ui-input :value="host" readonly>{{ $t('host') }}</ui-input> | 			<ui-input :value="host" readonly>{{ $t('host') }}</ui-input> | ||||||
| 			<ui-input v-model="name">{{ $t('instance-name') }}</ui-input> | 			<ui-input v-model="name">{{ $t('instance-name') }}</ui-input> | ||||||
| 			<ui-textarea v-model="description">{{ $t('instance-description') }}</ui-textarea> | 			<ui-textarea v-model="description">{{ $t('instance-description') }}</ui-textarea> | ||||||
|  | 			<ui-input v-model="iconUrl"><template #icon><fa icon="link"/></template>{{ $t('icon-url') }}</ui-input> | ||||||
| 			<ui-input v-model="mascotImageUrl"><template #icon><fa icon="link"/></template>{{ $t('logo-url') }}</ui-input> | 			<ui-input v-model="mascotImageUrl"><template #icon><fa icon="link"/></template>{{ $t('logo-url') }}</ui-input> | ||||||
| 			<ui-input v-model="bannerUrl"><template #icon><fa icon="link"/></template>{{ $t('banner-url') }}</ui-input> | 			<ui-input v-model="bannerUrl"><template #icon><fa icon="link"/></template>{{ $t('banner-url') }}</ui-input> | ||||||
| 			<ui-input v-model="errorImageUrl"><template #icon><fa icon="link"/></template>{{ $t('error-image-url') }}</ui-input> | 			<ui-input v-model="errorImageUrl"><template #icon><fa icon="link"/></template>{{ $t('error-image-url') }}</ui-input> | ||||||
| @@ -24,6 +25,8 @@ | |||||||
| 			<ui-switch v-model="disableLocalTimeline">{{ $t('disable-local-timeline') }}</ui-switch> | 			<ui-switch v-model="disableLocalTimeline">{{ $t('disable-local-timeline') }}</ui-switch> | ||||||
| 			<ui-switch v-model="disableGlobalTimeline">{{ $t('disable-global-timeline') }}</ui-switch> | 			<ui-switch v-model="disableGlobalTimeline">{{ $t('disable-global-timeline') }}</ui-switch> | ||||||
| 			<ui-info>{{ $t('disabling-timelines-info') }}</ui-info> | 			<ui-info>{{ $t('disabling-timelines-info') }}</ui-info> | ||||||
|  | 			<ui-switch v-model="enableEmojiReaction">{{ $t('enable-emoji-reaction') }}</ui-switch> | ||||||
|  | 			<ui-switch v-model="useStarForReactionFallback">{{ $t('use-star-for-reaction-fallback') }}</ui-switch> | ||||||
| 		</section> | 		</section> | ||||||
| 		<section class="fit-bottom"> | 		<section class="fit-bottom"> | ||||||
| 			<header><fa icon="cloud"/> {{ $t('drive-config') }}</header> | 			<header><fa icon="cloud"/> {{ $t('drive-config') }}</header> | ||||||
| @@ -154,9 +157,12 @@ export default Vue.extend({ | |||||||
| 			disableRegistration: false, | 			disableRegistration: false, | ||||||
| 			disableLocalTimeline: false, | 			disableLocalTimeline: false, | ||||||
| 			disableGlobalTimeline: false, | 			disableGlobalTimeline: false, | ||||||
|  | 			enableEmojiReaction: true, | ||||||
|  | 			useStarForReactionFallback: false, | ||||||
| 			mascotImageUrl: null, | 			mascotImageUrl: null, | ||||||
| 			bannerUrl: null, | 			bannerUrl: null, | ||||||
| 			errorImageUrl: null, | 			errorImageUrl: null, | ||||||
|  | 			iconUrl: null, | ||||||
| 			name: null, | 			name: null, | ||||||
| 			description: null, | 			description: null, | ||||||
| 			languages: null, | 			languages: null, | ||||||
| @@ -204,9 +210,12 @@ export default Vue.extend({ | |||||||
| 			this.disableRegistration = meta.disableRegistration; | 			this.disableRegistration = meta.disableRegistration; | ||||||
| 			this.disableLocalTimeline = meta.disableLocalTimeline; | 			this.disableLocalTimeline = meta.disableLocalTimeline; | ||||||
| 			this.disableGlobalTimeline = meta.disableGlobalTimeline; | 			this.disableGlobalTimeline = meta.disableGlobalTimeline; | ||||||
|  | 			this.enableEmojiReaction = meta.enableEmojiReaction; | ||||||
|  | 			this.useStarForReactionFallback = meta.useStarForReactionFallback; | ||||||
| 			this.mascotImageUrl = meta.mascotImageUrl; | 			this.mascotImageUrl = meta.mascotImageUrl; | ||||||
| 			this.bannerUrl = meta.bannerUrl; | 			this.bannerUrl = meta.bannerUrl; | ||||||
| 			this.errorImageUrl = meta.errorImageUrl; | 			this.errorImageUrl = meta.errorImageUrl; | ||||||
|  | 			this.iconUrl = meta.iconUrl; | ||||||
| 			this.name = meta.name; | 			this.name = meta.name; | ||||||
| 			this.description = meta.description; | 			this.description = meta.description; | ||||||
| 			this.languages = meta.langs.join(' '); | 			this.languages = meta.langs.join(' '); | ||||||
| @@ -264,9 +273,12 @@ export default Vue.extend({ | |||||||
| 				disableRegistration: this.disableRegistration, | 				disableRegistration: this.disableRegistration, | ||||||
| 				disableLocalTimeline: this.disableLocalTimeline, | 				disableLocalTimeline: this.disableLocalTimeline, | ||||||
| 				disableGlobalTimeline: this.disableGlobalTimeline, | 				disableGlobalTimeline: this.disableGlobalTimeline, | ||||||
|  | 				enableEmojiReaction: this.enableEmojiReaction, | ||||||
|  | 				useStarForReactionFallback: this.useStarForReactionFallback, | ||||||
| 				mascotImageUrl: this.mascotImageUrl, | 				mascotImageUrl: this.mascotImageUrl, | ||||||
| 				bannerUrl: this.bannerUrl, | 				bannerUrl: this.bannerUrl, | ||||||
| 				errorImageUrl: this.errorImageUrl, | 				errorImageUrl: this.errorImageUrl, | ||||||
|  | 				iconUrl: this.iconUrl, | ||||||
| 				name: this.name, | 				name: this.name, | ||||||
| 				description: this.description, | 				description: this.description, | ||||||
| 				langs: this.languages.split(' '), | 				langs: this.languages.split(' '), | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ | |||||||
| 		<template #title><fa :icon="faStream"/> {{ $t('logs') }}</template> | 		<template #title><fa :icon="faStream"/> {{ $t('logs') }}</template> | ||||||
| 		<section class="fit-top"> | 		<section class="fit-top"> | ||||||
| 			<ui-horizon-group inputs> | 			<ui-horizon-group inputs> | ||||||
| 				<ui-input v-model="domain" debounce> | 				<ui-input v-model="domain" :debounce="true"> | ||||||
| 					<span>{{ $t('domain') }}</span> | 					<span>{{ $t('domain') }}</span> | ||||||
| 				</ui-input> | 				</ui-input> | ||||||
| 				<ui-select v-model="level"> | 				<ui-select v-model="level"> | ||||||
|   | |||||||
| @@ -1,68 +1,258 @@ | |||||||
| <template> | <template> | ||||||
| <div> | <div> | ||||||
| 	<ui-card> | 	<ui-card> | ||||||
| 		<template #title>{{ $t('operation') }}</template> | 		<template #title><fa :icon="faChartBar"/> {{ $t('title') }}</template> | ||||||
| 		<section> | 		<section class="wptihjuy"> | ||||||
| 			<header>Deliver</header> | 			<header><fa :icon="faPaperPlane"/> Deliver</header> | ||||||
| 			<ui-horizon-group inputs v-if="stats"> | 			<ui-info warn v-if="latestStats && latestStats.deliver.waiting > 0">The queue is jammed.</ui-info> | ||||||
| 				<ui-input :value="stats.deliver.waiting | number" type="text" readonly> | 			<ui-horizon-group inputs v-if="latestStats" class="fit-bottom"> | ||||||
| 					<span>Waiting</span> | 				<ui-input :value="latestStats.deliver.activeSincePrevTick | number" type="text" readonly> | ||||||
|  | 					<span>Process</span> | ||||||
|  | 					<template #prefix><fa :icon="fasPlayCircle"/></template> | ||||||
|  | 					<template #suffix>jobs/tick</template> | ||||||
| 				</ui-input> | 				</ui-input> | ||||||
| 				<ui-input :value="stats.deliver.delayed | number" type="text" readonly> | 				<ui-input :value="latestStats.deliver.active | number" type="text" readonly> | ||||||
| 					<span>Delayed</span> |  | ||||||
| 				</ui-input> |  | ||||||
| 				<ui-input :value="stats.deliver.active | number" type="text" readonly> |  | ||||||
| 					<span>Active</span> | 					<span>Active</span> | ||||||
|  | 					<template #prefix><fa :icon="farPlayCircle"/></template> | ||||||
|  | 					<template #suffix>jobs</template> | ||||||
|  | 				</ui-input> | ||||||
|  | 				<ui-input :value="latestStats.deliver.waiting | number" type="text" readonly> | ||||||
|  | 					<span>Waiting</span> | ||||||
|  | 					<template #prefix><fa :icon="faStopCircle"/></template> | ||||||
|  | 					<template #suffix>jobs</template> | ||||||
|  | 				</ui-input> | ||||||
|  | 				<ui-input :value="latestStats.deliver.delayed | number" type="text" readonly> | ||||||
|  | 					<span>Delayed</span> | ||||||
|  | 					<template #prefix><fa :icon="faStopwatch"/></template> | ||||||
|  | 					<template #suffix>jobs</template> | ||||||
| 				</ui-input> | 				</ui-input> | ||||||
| 			</ui-horizon-group> | 			</ui-horizon-group> | ||||||
|  | 			<div ref="deliverChart" class="chart"></div> | ||||||
| 		</section> | 		</section> | ||||||
| 		<section> | 		<section class="wptihjuy"> | ||||||
| 			<header>Inbox</header> | 			<header><fa :icon="faInbox"/> Inbox</header> | ||||||
| 			<ui-horizon-group inputs v-if="stats"> | 			<ui-info warn v-if="latestStats && latestStats.inbox.waiting > 0">The queue is jammed.</ui-info> | ||||||
| 				<ui-input :value="stats.inbox.waiting | number" type="text" readonly> | 			<ui-horizon-group inputs v-if="latestStats" class="fit-bottom"> | ||||||
| 					<span>Waiting</span> | 				<ui-input :value="latestStats.inbox.activeSincePrevTick | number" type="text" readonly> | ||||||
|  | 					<span>Process</span> | ||||||
|  | 					<template #prefix><fa :icon="fasPlayCircle"/></template> | ||||||
|  | 					<template #suffix>jobs/tick</template> | ||||||
| 				</ui-input> | 				</ui-input> | ||||||
| 				<ui-input :value="stats.inbox.delayed | number" type="text" readonly> | 				<ui-input :value="latestStats.inbox.active | number" type="text" readonly> | ||||||
| 					<span>Delayed</span> |  | ||||||
| 				</ui-input> |  | ||||||
| 				<ui-input :value="stats.inbox.active | number" type="text" readonly> |  | ||||||
| 					<span>Active</span> | 					<span>Active</span> | ||||||
|  | 					<template #prefix><fa :icon="farPlayCircle"/></template> | ||||||
|  | 					<template #suffix>jobs</template> | ||||||
|  | 				</ui-input> | ||||||
|  | 				<ui-input :value="latestStats.inbox.waiting | number" type="text" readonly> | ||||||
|  | 					<span>Waiting</span> | ||||||
|  | 					<template #prefix><fa :icon="faStopCircle"/></template> | ||||||
|  | 					<template #suffix>jobs</template> | ||||||
|  | 				</ui-input> | ||||||
|  | 				<ui-input :value="latestStats.inbox.delayed | number" type="text" readonly> | ||||||
|  | 					<span>Delayed</span> | ||||||
|  | 					<template #prefix><fa :icon="faStopwatch"/></template> | ||||||
|  | 					<template #suffix>jobs</template> | ||||||
| 				</ui-input> | 				</ui-input> | ||||||
| 			</ui-horizon-group> | 			</ui-horizon-group> | ||||||
|  | 			<div ref="inboxChart" class="chart"></div> | ||||||
| 		</section> | 		</section> | ||||||
| 		<section> | 		<section> | ||||||
| 			<ui-button @click="removeAllJobs">{{ $t('remove-all-jobs') }}</ui-button> | 			<ui-button @click="removeAllJobs">{{ $t('remove-all-jobs') }}</ui-button> | ||||||
| 		</section> | 		</section> | ||||||
| 	</ui-card> | 	</ui-card> | ||||||
|  |  | ||||||
|  | 	<ui-card> | ||||||
|  | 		<template #title><fa :icon="faTasks"/> {{ $t('jobs') }}</template> | ||||||
|  | 		<section class="fit-top"> | ||||||
|  | 			<ui-horizon-group inputs> | ||||||
|  | 				<ui-select v-model="domain"> | ||||||
|  | 					<template #label>{{ $t('queue') }}</template> | ||||||
|  | 					<option value="deliver">{{ $t('domains.deliver') }}</option> | ||||||
|  | 					<option value="inbox">{{ $t('domains.inbox') }}</option> | ||||||
|  | 				</ui-select> | ||||||
|  | 				<ui-select v-model="state"> | ||||||
|  | 					<template #label>{{ $t('state') }}</template> | ||||||
|  | 					<option value="delayed">{{ $t('states.delayed') }}</option> | ||||||
|  | 				</ui-select> | ||||||
|  | 			</ui-horizon-group> | ||||||
|  | 			<sequential-entrance animation="entranceFromTop" delay="25"> | ||||||
|  | 				<div class="xvvuvgsv" v-for="job in jobs"> | ||||||
|  | 					<b>{{ job.id }}</b> | ||||||
|  | 					<template v-if="domain === 'deliver'"> | ||||||
|  | 						<span>{{ job.data.to }}</span> | ||||||
|  | 					</template> | ||||||
|  | 					<template v-if="domain === 'inbox'"> | ||||||
|  | 						<span>{{ job.activity.id }}</span> | ||||||
|  | 					</template> | ||||||
|  | 				</div> | ||||||
|  | 			</sequential-entrance> | ||||||
|  | 			<ui-info v-if="jobs.length == jobsLimit">{{ $t('result-is-truncated', { n: jobsLimit }) }}</ui-info> | ||||||
|  | 		</section> | ||||||
|  | 	</ui-card> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import i18n from '../../i18n'; | import i18n from '../../i18n'; | ||||||
|  | import ApexCharts from 'apexcharts'; | ||||||
|  | import * as tinycolor from 'tinycolor2'; | ||||||
|  | import { faTasks, faInbox, faStopwatch, faPlayCircle as fasPlayCircle } from '@fortawesome/free-solid-svg-icons'; | ||||||
|  | import { faPaperPlane, faStopCircle, faPlayCircle as farPlayCircle, faChartBar } from '@fortawesome/free-regular-svg-icons'; | ||||||
|  |  | ||||||
|  | const limit = 200; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('admin/views/queue.vue'), | 	i18n: i18n('admin/views/queue.vue'), | ||||||
|  |  | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			stats: null | 			stats: [], | ||||||
|  | 			deliverChart: null, | ||||||
|  | 			inboxChart: null, | ||||||
|  | 			jobs: [], | ||||||
|  | 			jobsLimit: 50, | ||||||
|  | 			domain: 'deliver', | ||||||
|  | 			state: 'delayed', | ||||||
|  | 			faTasks, faPaperPlane, faInbox, faStopwatch, faStopCircle, farPlayCircle, fasPlayCircle, faChartBar | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	created() { | 	computed: { | ||||||
| 		const fetchStats = () => { | 		latestStats(): any { | ||||||
| 			this.$root.api('admin/queue/stats', {}, true).then(stats => { | 			return this.stats[this.stats.length - 1]; | ||||||
| 				this.stats = stats; | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	watch: { | ||||||
|  | 		stats(stats) { | ||||||
|  | 			this.inboxChart.updateSeries([{ | ||||||
|  | 				name: 'Process', | ||||||
|  | 				type: 'area', | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.inbox.activeSincePrevTick })) | ||||||
|  | 			}, { | ||||||
|  | 				name: 'Active', | ||||||
|  | 				type: 'area', | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.inbox.active })) | ||||||
|  | 			}, { | ||||||
|  | 				name: 'Waiting', | ||||||
|  | 				type: 'line', | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.inbox.waiting })) | ||||||
|  | 			}, { | ||||||
|  | 				name: 'Delayed', | ||||||
|  | 				type: 'line', | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.inbox.delayed })) | ||||||
|  | 			}]); | ||||||
|  | 			this.deliverChart.updateSeries([{ | ||||||
|  | 				name: 'Process', | ||||||
|  | 				type: 'area', | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.deliver.activeSincePrevTick })) | ||||||
|  | 			}, { | ||||||
|  | 				name: 'Active', | ||||||
|  | 				type: 'area', | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.deliver.active })) | ||||||
|  | 			}, { | ||||||
|  | 				name: 'Waiting', | ||||||
|  | 				type: 'line', | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.deliver.waiting })) | ||||||
|  | 			}, { | ||||||
|  | 				name: 'Delayed', | ||||||
|  | 				type: 'line', | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.deliver.delayed })) | ||||||
|  | 			}]); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		domain() { | ||||||
|  | 			this.jobs = []; | ||||||
|  | 			this.fetchJobs(); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		state() { | ||||||
|  | 			this.jobs = []; | ||||||
|  | 			this.fetchJobs(); | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	mounted() { | ||||||
|  | 		this.fetchJobs(); | ||||||
|  |  | ||||||
|  | 		const chartOpts = id => ({ | ||||||
|  | 			chart: { | ||||||
|  | 				id, | ||||||
|  | 				group: 'queue', | ||||||
|  | 				type: 'area', | ||||||
|  | 				height: 200, | ||||||
|  | 				animations: { | ||||||
|  | 					dynamicAnimation: { | ||||||
|  | 						enabled: false | ||||||
|  | 					} | ||||||
|  | 				}, | ||||||
|  | 				toolbar: { | ||||||
|  | 					show: false | ||||||
|  | 				}, | ||||||
|  | 				zoom: { | ||||||
|  | 					enabled: false | ||||||
|  | 				} | ||||||
|  | 			}, | ||||||
|  | 			dataLabels: { | ||||||
|  | 				enabled: false | ||||||
|  | 			}, | ||||||
|  | 			grid: { | ||||||
|  | 				clipMarkers: false, | ||||||
|  | 				borderColor: 'rgba(0, 0, 0, 0.1)', | ||||||
|  | 				xaxis: { | ||||||
|  | 					lines: { | ||||||
|  | 						show: true, | ||||||
|  | 					} | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			stroke: { | ||||||
|  | 				curve: 'straight', | ||||||
|  | 				width: 2 | ||||||
|  | 			}, | ||||||
|  | 			tooltip: { | ||||||
|  | 				enabled: false | ||||||
|  | 			}, | ||||||
|  | 			legend: { | ||||||
|  | 				labels: { | ||||||
|  | 					colors: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString() | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			series: [] as any, | ||||||
|  | 			colors: ['#00E396', '#00BCD4', '#FFB300', '#e53935'], | ||||||
|  | 			xaxis: { | ||||||
|  | 				type: 'numeric', | ||||||
|  | 				labels: { | ||||||
|  | 					show: false | ||||||
|  | 				}, | ||||||
|  | 				tooltip: { | ||||||
|  | 					enabled: false | ||||||
|  | 				} | ||||||
|  | 			}, | ||||||
|  | 			yaxis: { | ||||||
|  | 				show: false, | ||||||
|  | 				min: 0, | ||||||
|  | 			} | ||||||
| 		}); | 		}); | ||||||
| 		}; |  | ||||||
|  |  | ||||||
| 		fetchStats(); | 		this.inboxChart = new ApexCharts(this.$refs.inboxChart, chartOpts('a')); | ||||||
|  | 		this.deliverChart = new ApexCharts(this.$refs.deliverChart, chartOpts('b')); | ||||||
|  |  | ||||||
| 		const clock = setInterval(fetchStats, 1000); | 		this.inboxChart.render(); | ||||||
|  | 		this.deliverChart.render(); | ||||||
|  |  | ||||||
|  | 		const connection = this.$root.stream.useSharedConnection('queueStats'); | ||||||
|  | 		connection.on('stats', this.onStats); | ||||||
|  | 		connection.on('statsLog', this.onStatsLog); | ||||||
|  | 		connection.send('requestLog', { | ||||||
|  | 			id: Math.random().toString().substr(2, 8), | ||||||
|  | 			length: limit | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		this.$once('hook:beforeDestroy', () => { | 		this.$once('hook:beforeDestroy', () => { | ||||||
| 			clearInterval(clock); | 			connection.dispose(); | ||||||
|  | 			this.inboxChart.destroy(); | ||||||
|  | 			this.deliverChart.destroy(); | ||||||
| 		}); | 		}); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -83,6 +273,39 @@ export default Vue.extend({ | |||||||
| 				}); | 				}); | ||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | 		onStats(stats) { | ||||||
|  | 			this.stats.push(stats); | ||||||
|  | 			if (this.stats.length > limit) this.stats.shift(); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		onStatsLog(statsLog) { | ||||||
|  | 			for (const stats of statsLog.reverse()) { | ||||||
|  | 				this.onStats(stats); | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		fetchJobs() { | ||||||
|  | 			this.$root.api('admin/queue/jobs', { | ||||||
|  | 				domain: this.domain, | ||||||
|  | 				state: this.state, | ||||||
|  | 				limit: this.jobsLimit | ||||||
|  | 			}).then(jobs => { | ||||||
|  | 				this.jobs = jobs; | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | <style lang="stylus" scoped> | ||||||
|  | .wptihjuy | ||||||
|  | 	> .chart | ||||||
|  | 		min-height 200px !important | ||||||
|  | 		margin 0 -8px | ||||||
|  |  | ||||||
|  | .xvvuvgsv | ||||||
|  | 	> b | ||||||
|  | 		margin-right 16px | ||||||
|  |  | ||||||
|  | </style> | ||||||
|   | |||||||
| @@ -69,7 +69,7 @@ export default Vue.extend({ | |||||||
| 				}, | 				}, | ||||||
| 				plotOptions: { | 				plotOptions: { | ||||||
| 					bar: { | 					bar: { | ||||||
| 						columnWidth: '90%' | 						columnWidth: '80%' | ||||||
| 					} | 					} | ||||||
| 				}, | 				}, | ||||||
| 				grid: { | 				grid: { | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ | |||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import * as emojilib from 'emojilib'; | import * as emojilib from 'emojilib'; | ||||||
| import contains from '../../../common/scripts/contains'; | import contains from '../../../common/scripts/contains'; | ||||||
|  | import { twemojiBase } from '../../../../../misc/twemoji-base'; | ||||||
|  |  | ||||||
| type EmojiDef = { | type EmojiDef = { | ||||||
| 	emoji: string; | 	emoji: string; | ||||||
| @@ -54,7 +55,7 @@ const emjdb: EmojiDef[] = lib.map((x: any) => ({ | |||||||
| 	emoji: x[1].char, | 	emoji: x[1].char, | ||||||
| 	name: x[0], | 	name: x[0], | ||||||
| 	aliasOf: null, | 	aliasOf: null, | ||||||
| 	url: `https://twemoji.maxcdn.com/2/svg/${char2file(x[1].char)}.svg` | 	url: `${twemojiBase}/2/svg/${char2file(x[1].char)}.svg` | ||||||
| })); | })); | ||||||
|  |  | ||||||
| for (const x of lib as any) { | for (const x of lib as any) { | ||||||
| @@ -64,7 +65,7 @@ for (const x of lib as any) { | |||||||
| 				emoji: x[1].char, | 				emoji: x[1].char, | ||||||
| 				name: k, | 				name: k, | ||||||
| 				aliasOf: x[0], | 				aliasOf: x[0], | ||||||
| 				url: `https://twemoji.maxcdn.com/2/svg/${char2file(x[1].char)}.svg` | 				url: `${twemojiBase}/2/svg/${char2file(x[1].char)}.svg` | ||||||
| 			}); | 			}); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|   | |||||||
							
								
								
									
										177
									
								
								src/client/app/common/views/components/drive-file-thumbnail.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								src/client/app/common/views/components/drive-file-thumbnail.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,177 @@ | |||||||
|  | <template> | ||||||
|  | <div class="zdjebgpv" :class="{ detail }" ref="thumbnail" :style="`background-color: ${ background }`"> | ||||||
|  | 	<img | ||||||
|  | 		:src="file.url" | ||||||
|  | 		:alt="file.name" | ||||||
|  | 		:title="file.name" | ||||||
|  | 		v-if="detail && is === 'image'"/> | ||||||
|  | 	<video | ||||||
|  | 		:src="file.url" | ||||||
|  | 		ref="volumectrl" | ||||||
|  | 		preload="metadata" | ||||||
|  | 		controls | ||||||
|  | 		v-else-if="detail && is === 'video'"/> | ||||||
|  | 	<img :src="file.thumbnailUrl" alt="" @load="onThumbnailLoaded" :style="`object-fit: ${ fit }`" v-else-if="isThumbnailAvailable"/> | ||||||
|  | 	<fa :icon="faFileImage" class="icon" v-else-if="is === 'image'"/> | ||||||
|  | 	<fa :icon="faFileVideo" class="icon" v-else-if="is === 'video'"/> | ||||||
|  |  | ||||||
|  | 	<audio | ||||||
|  | 		:src="file.url" | ||||||
|  | 		ref="volumectrl" | ||||||
|  | 		preload="metadata" | ||||||
|  | 		controls | ||||||
|  | 		v-else-if="detail && is === 'audio'"/> | ||||||
|  | 	<fa :icon="faMusic" class="icon" v-else-if="is === 'audio' || is === 'midi'"/> | ||||||
|  |  | ||||||
|  | 	<fa :icon="faFileCsv" class="icon" v-else-if="is === 'csv'"/> | ||||||
|  | 	<fa :icon="faFilePdf" class="icon" v-else-if="is === 'pdf'"/> | ||||||
|  | 	<fa :icon="faFileAlt" class="icon" v-else-if="is === 'textfile'"/> | ||||||
|  | 	<fa :icon="faFileArchive" class="icon" v-else-if="is === 'archive'"/> | ||||||
|  | 	<fa :icon="faFile" class="icon" v-else/> | ||||||
|  |  | ||||||
|  | 	<fa :icon="faFilm" class="icon-sub" v-if="!detail && isThumbnailAvailable && is === 'video'"/> | ||||||
|  | </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import Vue from 'vue'; | ||||||
|  | import anime from 'animejs'; | ||||||
|  | import { | ||||||
|  | 	faFile, | ||||||
|  | 	faFileAlt, | ||||||
|  | 	faFileImage, | ||||||
|  | 	faMusic, | ||||||
|  | 	faFileVideo, | ||||||
|  | 	faFileCsv, | ||||||
|  | 	faFilePdf, | ||||||
|  | 	faFileArchive, | ||||||
|  | 	faFilm | ||||||
|  | 	} from '@fortawesome/free-solid-svg-icons'; | ||||||
|  |  | ||||||
|  | export default Vue.extend({ | ||||||
|  | 	props: { | ||||||
|  | 		file: { | ||||||
|  | 			type: Object, | ||||||
|  | 			required: true | ||||||
|  | 		}, | ||||||
|  | 		fit: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: true | ||||||
|  | 		}, | ||||||
|  | 		detail: { | ||||||
|  | 			type: Boolean, | ||||||
|  | 			required: false, | ||||||
|  | 			default: false | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			isContextmenuShowing: false, | ||||||
|  | 			isDragging: false, | ||||||
|  |  | ||||||
|  | 			faFile, | ||||||
|  | 			faFileAlt, | ||||||
|  | 			faFileImage, | ||||||
|  | 			faMusic, | ||||||
|  | 			faFileVideo, | ||||||
|  | 			faFileCsv, | ||||||
|  | 			faFilePdf, | ||||||
|  | 			faFileArchive, | ||||||
|  | 			faFilm | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
|  | 	computed: { | ||||||
|  | 		is(): 'image' | 'video' | 'midi' | 'audio' | 'csv' | 'pdf' | 'textfile' | 'archive' | 'unknown' { | ||||||
|  | 			if (this.file.type.startsWith('image/')) return 'image'; | ||||||
|  | 			if (this.file.type.startsWith('video/')) return 'video'; | ||||||
|  | 			if (this.file.type === 'audio/midi') return 'midi'; | ||||||
|  | 			if (this.file.type.startsWith('audio/')) return 'audio'; | ||||||
|  | 			if (this.file.type.endsWith('/csv')) return 'csv'; | ||||||
|  | 			if (this.file.type.endsWith('/pdf')) return 'pdf'; | ||||||
|  | 			if (this.file.type.startsWith('text/')) return 'textfile'; | ||||||
|  | 			if ([ | ||||||
|  | 					"application/zip", | ||||||
|  | 					"application/x-cpio", | ||||||
|  | 					"application/x-bzip", | ||||||
|  | 					"application/x-bzip2", | ||||||
|  | 					"application/java-archive", | ||||||
|  | 					"application/x-rar-compressed", | ||||||
|  | 					"application/x-tar", | ||||||
|  | 					"application/gzip", | ||||||
|  | 					"application/x-7z-compressed" | ||||||
|  | 				].some(e => e === this.file.type)) return 'archive'; | ||||||
|  | 			return 'unknown'; | ||||||
|  | 		}, | ||||||
|  | 		isThumbnailAvailable(): boolean { | ||||||
|  | 			return this.file.thumbnailUrl | ||||||
|  | 				? this.file.thumbnailUrl.endsWith('?thumbnail') | ||||||
|  | 					? (this.is === 'image' || this.is === 'video') | ||||||
|  | 					: true | ||||||
|  | 				: false; | ||||||
|  | 		}, | ||||||
|  | 		background(): string { | ||||||
|  | 			return this.file.properties.avgColor && this.file.properties.avgColor.length == 3 | ||||||
|  | 				? `rgb(${this.file.properties.avgColor.join(',')})` | ||||||
|  | 				: 'transparent'; | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	mounted() { | ||||||
|  | 		const audioTag = this.$refs.volumectrl as HTMLAudioElement; | ||||||
|  | 		if (audioTag) audioTag.volume = this.$store.state.device.mediaVolume; | ||||||
|  | 	}, | ||||||
|  | 	methods: { | ||||||
|  | 		onThumbnailLoaded() { | ||||||
|  | 			if (this.file.properties.avgColor && this.file.properties.avgColor.length == 3) { | ||||||
|  | 				anime({ | ||||||
|  | 					targets: this.$refs.thumbnail, | ||||||
|  | 					backgroundColor: `rgba(${this.file.properties.avgColor.join(',')}, 0)`, | ||||||
|  | 					duration: 100, | ||||||
|  | 					easing: 'linear' | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		volumechange() { | ||||||
|  | 			const audioTag = this.$refs.volumectrl as HTMLAudioElement; | ||||||
|  | 			this.$store.commit('device/set', { key: 'mediaVolume', value: audioTag.volume }); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="stylus" scoped> | ||||||
|  | .zdjebgpv | ||||||
|  | 	display flex | ||||||
|  |  | ||||||
|  | 	> img, | ||||||
|  | 	> .icon | ||||||
|  | 		pointer-events none | ||||||
|  |  | ||||||
|  | 	> img | ||||||
|  | 		height 100% | ||||||
|  | 		width 100% | ||||||
|  | 		margin auto | ||||||
|  | 		object-fit cover | ||||||
|  |  | ||||||
|  | 	> .icon | ||||||
|  | 		height 65% | ||||||
|  | 		width 65% | ||||||
|  | 		margin auto | ||||||
|  |  | ||||||
|  | 	> video, | ||||||
|  | 	> audio | ||||||
|  | 		width 100% | ||||||
|  |  | ||||||
|  | 	> .icon-sub | ||||||
|  | 		position absolute | ||||||
|  | 		width 30% | ||||||
|  | 		height auto | ||||||
|  | 		margin 0 | ||||||
|  | 		right 4% | ||||||
|  | 		bottom 4% | ||||||
|  |  | ||||||
|  | 	&.detail | ||||||
|  | 		> .icon | ||||||
|  | 			height 100px | ||||||
|  | 			margin 16px auto | ||||||
|  |  | ||||||
|  | </style> | ||||||
| @@ -10,6 +10,7 @@ import Vue from 'vue'; | |||||||
| // スクリプトサイズがデカい | // スクリプトサイズがデカい | ||||||
| //import { lib } from 'emojilib'; | //import { lib } from 'emojilib'; | ||||||
| import { getStaticImageUrl } from '../../../common/scripts/get-static-image-url'; | import { getStaticImageUrl } from '../../../common/scripts/get-static-image-url'; | ||||||
|  | import { twemojiBase } from '../../../../../misc/twemoji-base'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	props: { | 	props: { | ||||||
| @@ -29,7 +30,11 @@ export default Vue.extend({ | |||||||
| 		customEmojis: { | 		customEmojis: { | ||||||
| 			required: false, | 			required: false, | ||||||
| 			default: () => [] | 			default: () => [] | ||||||
| 		} | 		}, | ||||||
|  | 		isReaction: { | ||||||
|  | 			type: Boolean, | ||||||
|  | 			default: false | ||||||
|  | 		}, | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	data() { | 	data() { | ||||||
| @@ -46,7 +51,7 @@ export default Vue.extend({ | |||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		useOsDefaultEmojis(): boolean { | 		useOsDefaultEmojis(): boolean { | ||||||
| 			return this.$store.state.device.useOsDefaultEmojis; | 			return this.$store.state.device.useOsDefaultEmojis && !this.isReaction; | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -73,7 +78,7 @@ export default Vue.extend({ | |||||||
| 			if (!codes.includes('200d')) codes = codes.filter(x => x != 'fe0f'); | 			if (!codes.includes('200d')) codes = codes.filter(x => x != 'fe0f'); | ||||||
| 			codes = codes.filter(x => x && x.length); | 			codes = codes.filter(x => x && x.length); | ||||||
|  |  | ||||||
| 			this.url = `https://twemoji.maxcdn.com/2/svg/${codes.join('-')}.svg`; | 			this.url = `${twemojiBase}/2/svg/${codes.join('-')}.svg`; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,19 +1,5 @@ | |||||||
| <template> | <template> | ||||||
| <span class="mk-reaction-icon"> | <mk-emoji :emoji="str.startsWith(':') ? null : str" :name="str.startsWith(':') ? str.substr(1, str.length - 2) : null" :is-reaction="true" :custom-emojis="customEmojis" :normal="true"/> | ||||||
| 	<img v-if="reaction == 'like'" src="https://twemoji.maxcdn.com/2/svg/1f44d.svg" :alt="$t('@.reactions.like')"> |  | ||||||
| 	<img v-if="reaction == 'love'" src="https://twemoji.maxcdn.com/2/svg/2764.svg" :alt="$t('@.reactions.love')"> |  | ||||||
| 	<img v-if="reaction == 'laugh'" src="https://twemoji.maxcdn.com/2/svg/1f606.svg" :alt="$t('@.reactions.laugh')"> |  | ||||||
| 	<img v-if="reaction == 'hmm'" src="https://twemoji.maxcdn.com/2/svg/1f914.svg" :alt="$t('@.reactions.hmm')"> |  | ||||||
| 	<img v-if="reaction == 'surprise'" src="https://twemoji.maxcdn.com/2/svg/1f62e.svg" :alt="$t('@.reactions.surprise')"> |  | ||||||
| 	<img v-if="reaction == 'congrats'" src="https://twemoji.maxcdn.com/2/svg/1f389.svg" :alt="$t('@.reactions.congrats')"> |  | ||||||
| 	<img v-if="reaction == 'angry'" src="https://twemoji.maxcdn.com/2/svg/1f4a2.svg" :alt="$t('@.reactions.angry')"> |  | ||||||
| 	<img v-if="reaction == 'confused'" src="https://twemoji.maxcdn.com/2/svg/1f625.svg" :alt="$t('@.reactions.confused')"> |  | ||||||
| 	<img v-if="reaction == 'rip'" src="https://twemoji.maxcdn.com/2/svg/1f607.svg" :alt="$t('@.reactions.rip')"> |  | ||||||
| 	<template v-if="reaction == 'pudding'"> |  | ||||||
| 		<img v-if="$store.getters.isSignedIn && $store.state.settings.iLikeSushi" src="https://twemoji.maxcdn.com/2/svg/1f363.svg" :alt="$t('@.reactions.pudding')"> |  | ||||||
| 		<img v-else src="https://twemoji.maxcdn.com/2/svg/1f36e.svg" :alt="$t('@.reactions.pudding')"> |  | ||||||
| 	</template> |  | ||||||
| </span> |  | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| @@ -21,7 +7,35 @@ import Vue from 'vue'; | |||||||
| import i18n from '../../../i18n'; | import i18n from '../../../i18n'; | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n(), | 	i18n: i18n(), | ||||||
| 	props: ['reaction'] | 	props: { | ||||||
|  | 		reaction: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: true | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			customEmojis: (this.$root.getMetaSync() || { emojis: [] }).emojis || [] | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
|  | 	computed: { | ||||||
|  | 		str(): any { | ||||||
|  | 			switch (this.reaction) { | ||||||
|  | 				case 'like': return '👍'; | ||||||
|  | 				case 'love': return '❤'; | ||||||
|  | 				case 'laugh': return '😆'; | ||||||
|  | 				case 'hmm': return '🤔'; | ||||||
|  | 				case 'surprise': return '😮'; | ||||||
|  | 				case 'congrats': return '🎉'; | ||||||
|  | 				case 'angry': return '💢'; | ||||||
|  | 				case 'confused': return '😥'; | ||||||
|  | 				case 'rip': return '😇'; | ||||||
|  | 				case 'pudding': return (this.$store.getters.isSignedIn && this.$store.state.settings.iLikeSushi) ? '🍣' : '🍮'; | ||||||
|  | 				case 'star': return '⭐'; | ||||||
|  | 				default: return this.reaction; | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| 	<div class="backdrop" ref="backdrop" @click="close"></div> | 	<div class="backdrop" ref="backdrop" @click="close"></div> | ||||||
| 	<div class="popover" :class="{ isMobile: $root.isMobile }" ref="popover"> | 	<div class="popover" :class="{ isMobile: $root.isMobile }" ref="popover"> | ||||||
| 		<p v-if="!$root.isMobile">{{ title }}</p> | 		<p v-if="!$root.isMobile">{{ title }}</p> | ||||||
| 		<div ref="buttons" :class="{ showFocus }"> | 		<div class="buttons" ref="buttons" :class="{ showFocus }"> | ||||||
| 			<button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" :title="$t('@.reactions.like')" v-particle><mk-reaction-icon reaction="like"/></button> | 			<button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" :title="$t('@.reactions.like')" v-particle><mk-reaction-icon reaction="like"/></button> | ||||||
| 			<button @click="react('love')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="2" :title="$t('@.reactions.love')" v-particle><mk-reaction-icon reaction="love"/></button> | 			<button @click="react('love')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="2" :title="$t('@.reactions.love')" v-particle><mk-reaction-icon reaction="love"/></button> | ||||||
| 			<button @click="react('laugh')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="3" :title="$t('@.reactions.laugh')" v-particle><mk-reaction-icon reaction="laugh"/></button> | 			<button @click="react('laugh')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="3" :title="$t('@.reactions.laugh')" v-particle><mk-reaction-icon reaction="laugh"/></button> | ||||||
| @@ -15,6 +15,9 @@ | |||||||
| 			<button @click="react('rip')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="9" :title="$t('@.reactions.rip')" v-particle><mk-reaction-icon reaction="rip"/></button> | 			<button @click="react('rip')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="9" :title="$t('@.reactions.rip')" v-particle><mk-reaction-icon reaction="rip"/></button> | ||||||
| 			<button @click="react('pudding')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="10" :title="$t('@.reactions.pudding')" v-particle><mk-reaction-icon reaction="pudding"/></button> | 			<button @click="react('pudding')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="10" :title="$t('@.reactions.pudding')" v-particle><mk-reaction-icon reaction="pudding"/></button> | ||||||
| 		</div> | 		</div> | ||||||
|  | 		<div v-if="enableEmojiReaction" class="text"> | ||||||
|  | 			<input v-model="text" placeholder="または絵文字を入力" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }"> | ||||||
|  | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
| @@ -23,6 +26,7 @@ | |||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import i18n from '../../../i18n'; | import i18n from '../../../i18n'; | ||||||
| import anime from 'animejs'; | import anime from 'animejs'; | ||||||
|  | import { emojiRegex } from '../../../../../misc/emoji-regex'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('common/views/components/reaction-picker.vue'), | 	i18n: i18n('common/views/components/reaction-picker.vue'), | ||||||
| @@ -56,6 +60,8 @@ export default Vue.extend({ | |||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			title: this.$t('choose-reaction'), | 			title: this.$t('choose-reaction'), | ||||||
|  | 			text: null, | ||||||
|  | 			enableEmojiReaction: false, | ||||||
| 			focus: null | 			focus: null | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| @@ -94,6 +100,10 @@ export default Vue.extend({ | |||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	mounted() { | 	mounted() { | ||||||
|  | 		this.$root.getMeta().then(meta => { | ||||||
|  | 			this.enableEmojiReaction = meta.enableEmojiReaction; | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		this.$nextTick(() => { | 		this.$nextTick(() => { | ||||||
| 			this.focus = 0; | 			this.focus = 0; | ||||||
|  |  | ||||||
| @@ -143,6 +153,17 @@ export default Vue.extend({ | |||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | 		reactText() { | ||||||
|  | 			if (!this.text) return; | ||||||
|  | 			this.react(this.text); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		tryReactText() { | ||||||
|  | 			if (!this.text) return; | ||||||
|  | 			if (!this.text.match(emojiRegex)) return; | ||||||
|  | 			this.reactText(); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
| 		onMouseover(e) { | 		onMouseover(e) { | ||||||
| 			this.title = e.target.title; | 			this.title = e.target.title; | ||||||
| 		}, | 		}, | ||||||
| @@ -256,9 +277,9 @@ export default Vue.extend({ | |||||||
| 			color var(--popupFg) | 			color var(--popupFg) | ||||||
| 			border-bottom solid var(--lineWidth) var(--faceDivider) | 			border-bottom solid var(--lineWidth) var(--faceDivider) | ||||||
|  |  | ||||||
| 		> div | 		> .buttons | ||||||
| 			padding 4px | 			padding 4px 4px 8px 4px | ||||||
| 			width 240px | 			width 216px | ||||||
| 			text-align center | 			text-align center | ||||||
|  |  | ||||||
| 			&.showFocus | 			&.showFocus | ||||||
| @@ -283,6 +304,9 @@ export default Vue.extend({ | |||||||
| 				font-size 24px | 				font-size 24px | ||||||
| 				border-radius 2px | 				border-radius 2px | ||||||
|  |  | ||||||
|  | 				> * | ||||||
|  | 					height 1em | ||||||
|  |  | ||||||
| 				&:hover | 				&:hover | ||||||
| 					background var(--reactionPickerButtonHoverBg) | 					background var(--reactionPickerButtonHoverBg) | ||||||
|  |  | ||||||
| @@ -290,4 +314,29 @@ export default Vue.extend({ | |||||||
| 					background var(--primary) | 					background var(--primary) | ||||||
| 					box-shadow inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15) | 					box-shadow inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15) | ||||||
|  |  | ||||||
|  | 		> .text | ||||||
|  | 			width 216px | ||||||
|  | 			padding 0 8px 8px 8px | ||||||
|  |  | ||||||
|  | 			> input | ||||||
|  | 				width 100% | ||||||
|  | 				padding 10px | ||||||
|  | 				margin 0 | ||||||
|  | 				text-align center | ||||||
|  | 				font-size 16px | ||||||
|  | 				color var(--desktopPostFormTextareaFg) | ||||||
|  | 				background var(--desktopPostFormTextareaBg) | ||||||
|  | 				outline none | ||||||
|  | 				border solid 1px var(--primaryAlpha01) | ||||||
|  | 				border-radius 4px | ||||||
|  | 				transition border-color .2s ease | ||||||
|  |  | ||||||
|  | 				&:hover | ||||||
|  | 					border-color var(--primaryAlpha02) | ||||||
|  | 					transition border-color .1s ease | ||||||
|  |  | ||||||
|  | 				&:focus | ||||||
|  | 					border-color var(--primaryAlpha05) | ||||||
|  | 					transition border-color 0s ease | ||||||
|  |  | ||||||
| </style> | </style> | ||||||
|   | |||||||
| @@ -136,12 +136,8 @@ export default Vue.extend({ | |||||||
| 		&:hover | 		&:hover | ||||||
| 			background var(--reactionViewerButtonHoverBg) | 			background var(--reactionViewerButtonHoverBg) | ||||||
|  |  | ||||||
| 	> .mk-reaction-icon |  | ||||||
| 		font-size 1.4em |  | ||||||
|  |  | ||||||
| 	> span | 	> span | ||||||
| 		font-size 1.1em | 		font-size 1.1em | ||||||
| 		line-height 32px | 		line-height 32px | ||||||
| 		vertical-align middle |  | ||||||
| 		color var(--text) | 		color var(--text) | ||||||
| </style> | </style> | ||||||
|   | |||||||
| @@ -93,12 +93,17 @@ export default Vue.extend({ | |||||||
| 					}, | 					}, | ||||||
| 					plotOptions: { | 					plotOptions: { | ||||||
| 						bar: { | 						bar: { | ||||||
| 							columnWidth: '90%' | 							columnWidth: '80%' | ||||||
| 						} | 						} | ||||||
| 					}, | 					}, | ||||||
| 					grid: { | 					grid: { | ||||||
| 						clipMarkers: false, | 						clipMarkers: false, | ||||||
| 						borderColor: 'rgba(0, 0, 0, 0.1)' | 						borderColor: 'rgba(0, 0, 0, 0.1)', | ||||||
|  | 						xaxis: { | ||||||
|  | 							lines: { | ||||||
|  | 								show: true, | ||||||
|  | 							} | ||||||
|  | 						}, | ||||||
| 					}, | 					}, | ||||||
| 					tooltip: { | 					tooltip: { | ||||||
| 						shared: true, | 						shared: true, | ||||||
|   | |||||||
| @@ -51,12 +51,12 @@ | |||||||
| 				<template #desc v-if="bannerUploading">{{ $t('uploading') }}<mk-ellipsis/></template> | 				<template #desc v-if="bannerUploading">{{ $t('uploading') }}<mk-ellipsis/></template> | ||||||
| 			</ui-input> | 			</ui-input> | ||||||
|  |  | ||||||
| 			<ui-button @click="save(true)">{{ $t('save') }}</ui-button> | 			<ui-button @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</ui-button> | ||||||
| 		</ui-form> | 		</ui-form> | ||||||
| 	</section> | 	</section> | ||||||
|  |  | ||||||
| 	<section> | 	<section> | ||||||
| 		<header>{{ $t('advanced') }}</header> | 		<header><fa :icon="faCogs"/> {{ $t('advanced') }}</header> | ||||||
|  |  | ||||||
| 		<div> | 		<div> | ||||||
| 			<ui-switch v-model="isCat" @change="save(false)">{{ $t('is-cat') }}</ui-switch> | 			<ui-switch v-model="isCat" @change="save(false)">{{ $t('is-cat') }}</ui-switch> | ||||||
| @@ -66,7 +66,7 @@ | |||||||
| 	</section> | 	</section> | ||||||
|  |  | ||||||
| 	<section> | 	<section> | ||||||
| 		<header>{{ $t('privacy') }}</header> | 		<header><fa :icon="faUnlockAlt"/> {{ $t('privacy') }}</header> | ||||||
|  |  | ||||||
| 		<div> | 		<div> | ||||||
| 			<ui-switch v-model="isLocked" @change="save(false)">{{ $t('is-locked') }}</ui-switch> | 			<ui-switch v-model="isLocked" @change="save(false)">{{ $t('is-locked') }}</ui-switch> | ||||||
| @@ -76,7 +76,7 @@ | |||||||
| 	</section> | 	</section> | ||||||
|  |  | ||||||
| 	<section v-if="enableEmail"> | 	<section v-if="enableEmail"> | ||||||
| 		<header>{{ $t('email') }}</header> | 		<header><fa :icon="faEnvelope"/> {{ $t('email') }}</header> | ||||||
|  |  | ||||||
| 		<div> | 		<div> | ||||||
| 			<template v-if="$store.state.i.email != null"> | 			<template v-if="$store.state.i.email != null"> | ||||||
| @@ -84,12 +84,12 @@ | |||||||
| 				<ui-info v-else warn>{{ $t('email-not-verified') }}</ui-info> | 				<ui-info v-else warn>{{ $t('email-not-verified') }}</ui-info> | ||||||
| 			</template> | 			</template> | ||||||
| 			<ui-input v-model="email" type="email"><span>{{ $t('email-address') }}</span></ui-input> | 			<ui-input v-model="email" type="email"><span>{{ $t('email-address') }}</span></ui-input> | ||||||
| 			<ui-button @click="updateEmail()">{{ $t('save') }}</ui-button> | 			<ui-button @click="updateEmail()"><fa :icon="faSave"/> {{ $t('save') }}</ui-button> | ||||||
| 		</div> | 		</div> | ||||||
| 	</section> | 	</section> | ||||||
|  |  | ||||||
| 	<section> | 	<section> | ||||||
| 		<header>{{ $t('export') }}</header> | 		<header><fa :icon="faBoxes"/> {{ $t('export-and-import') }}</header> | ||||||
|  |  | ||||||
| 		<div> | 		<div> | ||||||
| 			<ui-select v-model="exportTarget"> | 			<ui-select v-model="exportTarget"> | ||||||
| @@ -97,8 +97,12 @@ | |||||||
| 				<option value="following">{{ $t('export-targets.following-list') }}</option> | 				<option value="following">{{ $t('export-targets.following-list') }}</option> | ||||||
| 				<option value="mute">{{ $t('export-targets.mute-list') }}</option> | 				<option value="mute">{{ $t('export-targets.mute-list') }}</option> | ||||||
| 				<option value="blocking">{{ $t('export-targets.blocking-list') }}</option> | 				<option value="blocking">{{ $t('export-targets.blocking-list') }}</option> | ||||||
|  | 				<option value="user-lists">{{ $t('export-targets.user-lists') }}</option> | ||||||
| 			</ui-select> | 			</ui-select> | ||||||
|  | 			<ui-horizon-group class="fit-bottom"> | ||||||
| 				<ui-button @click="doExport()"><fa :icon="faDownload"/> {{ $t('export') }}</ui-button> | 				<ui-button @click="doExport()"><fa :icon="faDownload"/> {{ $t('export') }}</ui-button> | ||||||
|  | 				<ui-button @click="doImport()" :disabled="!['following', 'user-lists'].includes(exportTarget)"><fa :icon="faUpload"/> {{ $t('import') }}</ui-button> | ||||||
|  | 			</ui-horizon-group> | ||||||
| 		</div> | 		</div> | ||||||
| 	</section> | 	</section> | ||||||
|  |  | ||||||
| @@ -118,7 +122,8 @@ import { apiUrl, host } from '../../../../config'; | |||||||
| import { toUnicode } from 'punycode'; | import { toUnicode } from 'punycode'; | ||||||
| import langmap from 'langmap'; | import langmap from 'langmap'; | ||||||
| import { unique } from '../../../../../../prelude/array'; | import { unique } from '../../../../../../prelude/array'; | ||||||
| import { faDownload } from '@fortawesome/free-solid-svg-icons'; | import { faDownload, faUpload, faUnlockAlt, faBoxes, faCogs } from '@fortawesome/free-solid-svg-icons'; | ||||||
|  | import { faSave, faEnvelope } from '@fortawesome/free-regular-svg-icons'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('common/views/components/profile-editor.vue'), | 	i18n: i18n('common/views/components/profile-editor.vue'), | ||||||
| @@ -147,7 +152,7 @@ export default Vue.extend({ | |||||||
| 			avatarUploading: false, | 			avatarUploading: false, | ||||||
| 			bannerUploading: false, | 			bannerUploading: false, | ||||||
| 			exportTarget: 'notes', | 			exportTarget: 'notes', | ||||||
| 			faDownload | 			faDownload, faUpload, faSave, faEnvelope, faUnlockAlt, faBoxes, faCogs | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -284,6 +289,7 @@ export default Vue.extend({ | |||||||
| 				this.exportTarget == 'following' ? 'i/export-following' : | 				this.exportTarget == 'following' ? 'i/export-following' : | ||||||
| 				this.exportTarget == 'mute' ? 'i/export-mute' : | 				this.exportTarget == 'mute' ? 'i/export-mute' : | ||||||
| 				this.exportTarget == 'blocking' ? 'i/export-blocking' : | 				this.exportTarget == 'blocking' ? 'i/export-blocking' : | ||||||
|  | 				this.exportTarget == 'user-lists' ? 'i/export-user-lists' : | ||||||
| 				null, {}); | 				null, {}); | ||||||
|  |  | ||||||
| 			this.$root.dialog({ | 			this.$root.dialog({ | ||||||
| @@ -292,6 +298,22 @@ export default Vue.extend({ | |||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | 		doImport() { | ||||||
|  | 			this.$chooseDriveFile().then(file => { | ||||||
|  | 				this.$root.api( | ||||||
|  | 					this.exportTarget == 'following' ? 'i/import-following' : | ||||||
|  | 					this.exportTarget == 'user-lists' ? 'i/import-user-lists' : | ||||||
|  | 					null, { | ||||||
|  | 						fileId: file.id | ||||||
|  | 					}); | ||||||
|  |  | ||||||
|  | 				this.$root.dialog({ | ||||||
|  | 					type: 'info', | ||||||
|  | 					text: this.$t('import-requested') | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
| 		async deleteAccount() { | 		async deleteAccount() { | ||||||
| 			const { canceled: canceled, result: password } = await this.$root.dialog({ | 			const { canceled: canceled, result: password } = await this.$root.dialog({ | ||||||
| 				title: this.$t('enter-password'), | 				title: this.$t('enter-password'), | ||||||
|   | |||||||
| @@ -159,7 +159,7 @@ | |||||||
| 	</template> | 	</template> | ||||||
|  |  | ||||||
| 	<template v-if="page == null || page == 'notification'"> | 	<template v-if="page == null || page == 'notification'"> | ||||||
| 		<x-notification v-show="page == 'notification'"/> | 		<x-notification/> | ||||||
| 	</template> | 	</template> | ||||||
|  |  | ||||||
| 	<template v-if="page == null || page == 'drive'"> | 	<template v-if="page == null || page == 'drive'"> | ||||||
|   | |||||||
| @@ -366,9 +366,6 @@ root(fill) | |||||||
| 			&[type='file'] | 			&[type='file'] | ||||||
| 				display none | 				display none | ||||||
|  |  | ||||||
| 			&[type='number'] |  | ||||||
| 				text-align right |  | ||||||
|  |  | ||||||
| 		> .prefix | 		> .prefix | ||||||
| 		> .suffix | 		> .suffix | ||||||
| 			display block | 			display block | ||||||
|   | |||||||
| @@ -3,12 +3,14 @@ | |||||||
| 	<ol v-if="uploads.length > 0"> | 	<ol v-if="uploads.length > 0"> | ||||||
| 		<li v-for="ctx in uploads" :key="ctx.id"> | 		<li v-for="ctx in uploads" :key="ctx.id"> | ||||||
| 			<div class="img" :style="{ backgroundImage: `url(${ ctx.img })` }"></div> | 			<div class="img" :style="{ backgroundImage: `url(${ ctx.img })` }"></div> | ||||||
|  | 			<div class="top"> | ||||||
| 				<p class="name"><fa icon="spinner" pulse/>{{ ctx.name }}</p> | 				<p class="name"><fa icon="spinner" pulse/>{{ ctx.name }}</p> | ||||||
| 				<p class="status"> | 				<p class="status"> | ||||||
| 					<span class="initing" v-if="ctx.progress == undefined">{{ $t('waiting') }}<mk-ellipsis/></span> | 					<span class="initing" v-if="ctx.progress == undefined">{{ $t('waiting') }}<mk-ellipsis/></span> | ||||||
| 					<span class="kb" v-if="ctx.progress != undefined">{{ String(Math.floor(ctx.progress.value / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i> / {{ String(Math.floor(ctx.progress.max / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i></span> | 					<span class="kb" v-if="ctx.progress != undefined">{{ String(Math.floor(ctx.progress.value / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i> / {{ String(Math.floor(ctx.progress.max / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i></span> | ||||||
| 					<span class="percentage" v-if="ctx.progress != undefined">{{ Math.floor((ctx.progress.value / ctx.progress.max) * 100) }}</span> | 					<span class="percentage" v-if="ctx.progress != undefined">{{ Math.floor((ctx.progress.value / ctx.progress.max) * 100) }}</span> | ||||||
| 				</p> | 				</p> | ||||||
|  | 			</div> | ||||||
| 			<progress v-if="ctx.progress != undefined && ctx.progress.value != ctx.progress.max" :value="ctx.progress.value" :max="ctx.progress.max"></progress> | 			<progress v-if="ctx.progress != undefined && ctx.progress.value != ctx.progress.max" :value="ctx.progress.value" :max="ctx.progress.max"></progress> | ||||||
| 			<div class="progress initing" v-if="ctx.progress == undefined"></div> | 			<div class="progress initing" v-if="ctx.progress == undefined"></div> | ||||||
| 			<div class="progress waiting" v-if="ctx.progress != undefined && ctx.progress.value == ctx.progress.max"></div> | 			<div class="progress waiting" v-if="ctx.progress != undefined && ctx.progress.value == ctx.progress.max"></div> | ||||||
| @@ -116,12 +118,17 @@ export default Vue.extend({ | |||||||
| 		list-style none | 		list-style none | ||||||
|  |  | ||||||
| 		> li | 		> li | ||||||
| 			display block | 			display grid | ||||||
| 			margin 8px 0 0 0 | 			margin 8px 0 0 0 | ||||||
| 			padding 0 | 			padding 0 | ||||||
| 			height 36px | 			height 36px | ||||||
|  | 			width: 100% | ||||||
| 			box-shadow 0 -1px 0 var(--primaryAlpha01) | 			box-shadow 0 -1px 0 var(--primaryAlpha01) | ||||||
| 			border-top solid 8px transparent | 			border-top solid 8px transparent | ||||||
|  | 			grid-template-columns 36px calc(100% - 44px) | ||||||
|  | 			grid-template-rows 1fr 8px | ||||||
|  | 			column-gap 8px | ||||||
|  | 			box-sizing content-box | ||||||
|  |  | ||||||
| 			&:first-child | 			&:first-child | ||||||
| 				margin 0 | 				margin 0 | ||||||
| @@ -130,39 +137,36 @@ export default Vue.extend({ | |||||||
|  |  | ||||||
| 			> .img | 			> .img | ||||||
| 				display block | 				display block | ||||||
| 				position absolute |  | ||||||
| 				top 0 |  | ||||||
| 				left 0 |  | ||||||
| 				width 36px |  | ||||||
| 				height 36px |  | ||||||
| 				background-size cover | 				background-size cover | ||||||
| 				background-position center center | 				background-position center center | ||||||
|  | 				grid-column 1 / 2 | ||||||
|  | 				grid-row 1 / 3 | ||||||
|  |  | ||||||
|  | 			> .top | ||||||
|  | 				display flex | ||||||
|  | 				grid-column 2 / 3 | ||||||
|  | 				grid-row 1 / 2 | ||||||
|  |  | ||||||
| 				> .name | 				> .name | ||||||
| 					display block | 					display block | ||||||
| 				position absolute | 					padding 0 8px 0 0 | ||||||
| 				top 0 |  | ||||||
| 				left 44px |  | ||||||
| 					margin 0 | 					margin 0 | ||||||
| 				padding 0 |  | ||||||
| 				max-width 256px |  | ||||||
| 					font-size 0.8em | 					font-size 0.8em | ||||||
| 					color var(--primaryAlpha07) | 					color var(--primaryAlpha07) | ||||||
| 					white-space nowrap | 					white-space nowrap | ||||||
| 					text-overflow ellipsis | 					text-overflow ellipsis | ||||||
| 					overflow hidden | 					overflow hidden | ||||||
|  | 					flex-shrink 1 | ||||||
|  |  | ||||||
| 					> [data-icon] | 					> [data-icon] | ||||||
| 						margin-right 4px | 						margin-right 4px | ||||||
|  |  | ||||||
| 				> .status | 				> .status | ||||||
| 					display block | 					display block | ||||||
| 				position absolute | 					margin 0 0 0 auto | ||||||
| 				top 0 |  | ||||||
| 				right 0 |  | ||||||
| 				margin 0 |  | ||||||
| 					padding 0 | 					padding 0 | ||||||
| 					font-size 0.8em | 					font-size 0.8em | ||||||
|  | 					flex-shrink 0 | ||||||
|  |  | ||||||
| 					> .initing | 					> .initing | ||||||
| 						color var(--primaryAlpha05) | 						color var(--primaryAlpha05) | ||||||
| @@ -182,16 +186,13 @@ export default Vue.extend({ | |||||||
|  |  | ||||||
| 			> progress | 			> progress | ||||||
| 				display block | 				display block | ||||||
| 				position absolute |  | ||||||
| 				bottom 0 |  | ||||||
| 				right 0 |  | ||||||
| 				margin 0 |  | ||||||
| 				width calc(100% - 44px) |  | ||||||
| 				height 8px |  | ||||||
| 				background transparent | 				background transparent | ||||||
| 				border none | 				border none | ||||||
| 				border-radius 4px | 				border-radius 4px | ||||||
| 				overflow hidden | 				overflow hidden | ||||||
|  | 				grid-column 2 / 3 | ||||||
|  | 				grid-row 2 / 3 | ||||||
|  | 				z-index 2 | ||||||
|  |  | ||||||
| 				&::-webkit-progress-value | 				&::-webkit-progress-value | ||||||
| 					background var(--primary) | 					background var(--primary) | ||||||
| @@ -201,12 +202,6 @@ export default Vue.extend({ | |||||||
|  |  | ||||||
| 			> .progress | 			> .progress | ||||||
| 				display block | 				display block | ||||||
| 				position absolute |  | ||||||
| 				bottom 0 |  | ||||||
| 				right 0 |  | ||||||
| 				margin 0 |  | ||||||
| 				width calc(100% - 44px) |  | ||||||
| 				height 8px |  | ||||||
| 				border none | 				border none | ||||||
| 				border-radius 4px | 				border-radius 4px | ||||||
| 				background linear-gradient( | 				background linear-gradient( | ||||||
| @@ -221,6 +216,9 @@ export default Vue.extend({ | |||||||
| 				) | 				) | ||||||
| 				background-size 32px 32px | 				background-size 32px 32px | ||||||
| 				animation bg 1.5s linear infinite | 				animation bg 1.5s linear infinite | ||||||
|  | 				grid-column 2 / 3 | ||||||
|  | 				grid-row 2 / 3 | ||||||
|  | 				z-index 1 | ||||||
|  |  | ||||||
| 				&.initing | 				&.initing | ||||||
| 					opacity 0.3 | 					opacity 0.3 | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| <template> | <template> | ||||||
| <div v-if="playerEnabled" class="player" :style="`padding: ${(player.height || 0) / (player.width || 1) * 100}% 0 0`"> | <div v-if="playerEnabled" class="player" :style="`padding: ${(player.height || 0) / (player.width || 1) * 100}% 0 0`"> | ||||||
|  | 	<button class="disablePlayer" @click="playerEnabled = false" :title="$t('disable-player')"><fa icon="times"/></button> | ||||||
| 	<iframe :src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')" :width="player.width || '100%'" :heigth="player.height || 250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen /> | 	<iframe :src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')" :width="player.width || '100%'" :heigth="player.height || 250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen /> | ||||||
| </div> | </div> | ||||||
| <div v-else-if="tweetUrl && detail" class="twitter"> | <div v-else-if="tweetUrl && detail" class="twitter"> | ||||||
| @@ -126,6 +127,22 @@ export default Vue.extend({ | |||||||
| 	position relative | 	position relative | ||||||
| 	width 100% | 	width 100% | ||||||
|  |  | ||||||
|  | 	> button | ||||||
|  | 		position absolute | ||||||
|  | 		top -1.5em | ||||||
|  | 		right 0 | ||||||
|  | 		font-size 1em | ||||||
|  | 		width 1.5em | ||||||
|  | 		height 1.5em | ||||||
|  | 		padding 0 | ||||||
|  | 		margin 0 | ||||||
|  | 		color var(--text) | ||||||
|  | 		background rgba(128, 128, 128, 0.2) | ||||||
|  | 		opacity 0.7 | ||||||
|  |  | ||||||
|  | 		&:hover | ||||||
|  | 			opacity 0.9 | ||||||
|  |  | ||||||
| 	> iframe | 	> iframe | ||||||
| 		height 100% | 		height 100% | ||||||
| 		left 0 | 		left 0 | ||||||
|   | |||||||
| @@ -172,7 +172,7 @@ export default Vue.extend({ | |||||||
| 					}, | 					}, | ||||||
| 					plotOptions: { | 					plotOptions: { | ||||||
| 						bar: { | 						bar: { | ||||||
| 							columnWidth: '90%' | 							columnWidth: '80%' | ||||||
| 						} | 						} | ||||||
| 					}, | 					}, | ||||||
| 					grid: { | 					grid: { | ||||||
|   | |||||||
| @@ -26,6 +26,7 @@ | |||||||
| 					<option value="hashtags">{{ $t('@.widgets.hashtags') }}</option> | 					<option value="hashtags">{{ $t('@.widgets.hashtags') }}</option> | ||||||
| 					<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option> | 					<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option> | ||||||
| 					<option value="server">{{ $t('@.widgets.server') }}</option> | 					<option value="server">{{ $t('@.widgets.server') }}</option> | ||||||
|  | 					<option value="queue">{{ $t('@.widgets.queue') }}</option> | ||||||
| 					<option value="nav">{{ $t('@.widgets.nav') }}</option> | 					<option value="nav">{{ $t('@.widgets.nav') }}</option> | ||||||
| 					<option value="tips">{{ $t('@.widgets.tips') }}</option> | 					<option value="tips">{{ $t('@.widgets.tips') }}</option> | ||||||
| 				</select> | 				</select> | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| 	<h1>{{ $t('share-with', { name }) }}</h1> | 	<h1>{{ $t('share-with', { name }) }}</h1> | ||||||
| 	<div> | 	<div> | ||||||
| 		<mk-signin v-if="!$store.getters.isSignedIn"/> | 		<mk-signin v-if="!$store.getters.isSignedIn"/> | ||||||
| 		<mk-post-form v-else-if="!posted" :initial-text="text" :instant="true" @posted="posted = true"/> | 		<mk-post-form v-else-if="!posted" :initial-text="template" :instant="true" @posted="posted = true"/> | ||||||
| 		<p v-if="posted" class="posted"><fa icon="check"/></p> | 		<p v-if="posted" class="posted"><fa icon="check"/></p> | ||||||
| 	</div> | 	</div> | ||||||
| 	<ui-button class="close" v-if="posted" @click="close">{{ $t('@.close') }}</ui-button> | 	<ui-button class="close" v-if="posted" @click="close">{{ $t('@.close') }}</ui-button> | ||||||
| @@ -20,9 +20,21 @@ export default Vue.extend({ | |||||||
| 		return { | 		return { | ||||||
| 			name: null, | 			name: null, | ||||||
| 			posted: false, | 			posted: false, | ||||||
| 			text: new URLSearchParams(location.search).get('text') | 			text: new URLSearchParams(location.search).get('text'), | ||||||
|  | 			url: new URLSearchParams(location.search).get('url'), | ||||||
|  | 			title: new URLSearchParams(location.search).get('title'), | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
|  | 	computed: { | ||||||
|  | 		template(): string { | ||||||
|  | 			let t = ''; | ||||||
|  | 			if (this.title && this.url) t += `【[${this.title}](${this.url})】\n`; | ||||||
|  | 			if (this.title && !this.url) t += `【${this.title}】\n`; | ||||||
|  | 			if (this.text) t += `${this.text}\n`; | ||||||
|  | 			if (!this.title && this.url) t += `${this.url}`; | ||||||
|  | 			return t.trim(); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
| 	methods: { | 	methods: { | ||||||
| 		close() { | 		close() { | ||||||
| 			window.close(); | 			window.close(); | ||||||
| @@ -31,3 +31,4 @@ Vue.component('mkw-version', wVersion); | |||||||
| Vue.component('mkw-hashtags', wHashtags); | Vue.component('mkw-hashtags', wHashtags); | ||||||
| Vue.component('mkw-instance', wInstance); | Vue.component('mkw-instance', wInstance); | ||||||
| Vue.component('mkw-post-form', wPostForm); | Vue.component('mkw-post-form', wPostForm); | ||||||
|  | Vue.component('mkw-queue', () => import('./queue.vue').then(m => m.default)); | ||||||
|   | |||||||
							
								
								
									
										170
									
								
								src/client/app/common/views/widgets/queue.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								src/client/app/common/views/widgets/queue.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,170 @@ | |||||||
|  | <template> | ||||||
|  | <div> | ||||||
|  | 	<ui-container :show-header="!props.compact"> | ||||||
|  | 		<template #header><fa :icon="faTasks"/>Queue</template> | ||||||
|  |  | ||||||
|  | 		<div class="mntrproz"> | ||||||
|  | 			<div> | ||||||
|  | 				<b>In</b> | ||||||
|  | 				<span v-if="latestStats">{{ latestStats.inbox.activeSincePrevTick | number }} / {{ latestStats.inbox.delayed | number }}</span> | ||||||
|  | 				<div ref="in"></div> | ||||||
|  | 			</div> | ||||||
|  | 			<div> | ||||||
|  | 				<b>Out</b> | ||||||
|  | 				<span v-if="latestStats">{{ latestStats.deliver.activeSincePrevTick | number }} / {{ latestStats.deliver.delayed | number }}</span> | ||||||
|  | 				<div ref="out"></div> | ||||||
|  | 			</div> | ||||||
|  | 		</div> | ||||||
|  | 	</ui-container> | ||||||
|  | </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import define from '../../define-widget'; | ||||||
|  | import { faTasks } from '@fortawesome/free-solid-svg-icons'; | ||||||
|  | import ApexCharts from 'apexcharts'; | ||||||
|  |  | ||||||
|  | export default define({ | ||||||
|  | 	name: 'queue', | ||||||
|  | 	props: () => ({ | ||||||
|  | 		compact: false | ||||||
|  | 	}) | ||||||
|  | }).extend({ | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			stats: [], | ||||||
|  | 			inChart: null, | ||||||
|  | 			outChart: null, | ||||||
|  | 			faTasks | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	watch: { | ||||||
|  | 		stats(stats) { | ||||||
|  | 			this.inChart.updateSeries([{ | ||||||
|  | 				type: 'area', | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.inbox.activeSincePrevTick })) | ||||||
|  | 			}, { | ||||||
|  | 				type: 'area', | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.inbox.active })) | ||||||
|  | 			}, { | ||||||
|  | 				type: 'line', | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.inbox.waiting })) | ||||||
|  | 			}, { | ||||||
|  | 				type: 'line', | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.inbox.delayed })) | ||||||
|  | 			}]); | ||||||
|  | 			this.outChart.updateSeries([{ | ||||||
|  | 				type: 'area', | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.deliver.activeSincePrevTick })) | ||||||
|  | 			}, { | ||||||
|  | 				type: 'area', | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.deliver.active })) | ||||||
|  | 			}, { | ||||||
|  | 				type: 'line', | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.deliver.waiting })) | ||||||
|  | 			}, { | ||||||
|  | 				type: 'line', | ||||||
|  | 				data: stats.map((x, i) => ({ x: i, y: x.deliver.delayed })) | ||||||
|  | 			}]); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	computed: { | ||||||
|  | 		latestStats(): any { | ||||||
|  | 			return this.stats[this.stats.length - 1]; | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	mounted() { | ||||||
|  | 		const chartOpts = { | ||||||
|  | 			chart: { | ||||||
|  | 				type: 'area', | ||||||
|  | 				height: 70, | ||||||
|  | 				animations: { | ||||||
|  | 					dynamicAnimation: { | ||||||
|  | 						enabled: false | ||||||
|  | 					} | ||||||
|  | 				}, | ||||||
|  | 				sparkline: { | ||||||
|  | 					enabled: true, | ||||||
|  | 				} | ||||||
|  | 			}, | ||||||
|  | 			tooltip: { | ||||||
|  | 				enabled: false | ||||||
|  | 			}, | ||||||
|  | 			stroke: { | ||||||
|  | 				curve: 'straight', | ||||||
|  | 				width: 1 | ||||||
|  | 			}, | ||||||
|  | 			colors: ['#00E396', '#00BCD4', '#FFB300', '#e53935'], | ||||||
|  | 			series: [{ data: [] }, { data: [] }, { data: [] }, { data: [] }] as any, | ||||||
|  | 			yaxis: { | ||||||
|  | 				min: 0, | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		this.inChart = new ApexCharts(this.$refs.in, chartOpts); | ||||||
|  | 		this.outChart = new ApexCharts(this.$refs.out, chartOpts); | ||||||
|  |  | ||||||
|  | 		this.inChart.render(); | ||||||
|  | 		this.outChart.render(); | ||||||
|  |  | ||||||
|  | 		const connection = this.$root.stream.useSharedConnection('queueStats'); | ||||||
|  | 		connection.on('stats', this.onStats); | ||||||
|  | 		connection.on('statsLog', this.onStatsLog); | ||||||
|  | 		connection.send('requestLog', { | ||||||
|  | 			id: Math.random().toString().substr(2, 8), | ||||||
|  | 			length: 50 | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		this.$once('hook:beforeDestroy', () => { | ||||||
|  | 			connection.dispose(); | ||||||
|  | 			this.inChart.destroy(); | ||||||
|  | 			this.outChart.destroy(); | ||||||
|  | 		}); | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	methods: { | ||||||
|  | 		func() { | ||||||
|  | 			this.props.compact = !this.props.compact; | ||||||
|  | 			this.save(); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		onStats(stats) { | ||||||
|  | 			this.stats.push(stats); | ||||||
|  | 			if (this.stats.length > 50) this.stats.shift(); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		onStatsLog(statsLog) { | ||||||
|  | 			for (const stats of statsLog.reverse()) { | ||||||
|  | 				this.onStats(stats); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="stylus" scoped> | ||||||
|  | .mntrproz | ||||||
|  | 	display flex | ||||||
|  | 	padding 4px | ||||||
|  |  | ||||||
|  | 	> div | ||||||
|  | 		width 50% | ||||||
|  | 		padding 4px | ||||||
|  |  | ||||||
|  | 		> b | ||||||
|  | 			display block | ||||||
|  | 			font-size 12px | ||||||
|  | 			color var(--text) | ||||||
|  |  | ||||||
|  | 		> span | ||||||
|  | 			position absolute | ||||||
|  | 			top 4px | ||||||
|  | 			right 4px | ||||||
|  | 			opacity 0.7 | ||||||
|  | 			font-size 12px | ||||||
|  | 			color var(--text) | ||||||
|  |  | ||||||
|  | </style> | ||||||
| @@ -18,7 +18,7 @@ import MkSelectDrive from './views/pages/selectdrive.vue'; | |||||||
| import MkDrive from './views/pages/drive.vue'; | import MkDrive from './views/pages/drive.vue'; | ||||||
| import MkMessagingRoom from './views/pages/messaging-room.vue'; | import MkMessagingRoom from './views/pages/messaging-room.vue'; | ||||||
| import MkReversi from './views/pages/games/reversi.vue'; | import MkReversi from './views/pages/games/reversi.vue'; | ||||||
| import MkShare from './views/pages/share.vue'; | import MkShare from '../common/views/pages/share.vue'; | ||||||
| import MkFollow from '../common/views/pages/follow.vue'; | import MkFollow from '../common/views/pages/follow.vue'; | ||||||
| import MkNotFound from '../common/views/pages/not-found.vue'; | import MkNotFound from '../common/views/pages/not-found.vue'; | ||||||
| import MkSettings from './views/pages/settings.vue'; | import MkSettings from './views/pages/settings.vue'; | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ | |||||||
| 				<p @click="click(item)"><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}</p> | 				<p @click="click(item)"><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}</p> | ||||||
| 			</template> | 			</template> | ||||||
| 			<template v-else-if="item.type == 'link'"> | 			<template v-else-if="item.type == 'link'"> | ||||||
| 				<a :href="item.href" :target="item.target" @click="click(item)"><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}</a> | 				<a :href="item.href" :target="item.target" @click="click(item)" :download="item.download"><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}</a> | ||||||
| 			</template> | 			</template> | ||||||
| 			<template v-else-if="item.type == 'nest'"> | 			<template v-else-if="item.type == 'nest'"> | ||||||
| 				<p><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}...<span class="caret"><fa icon="caret-right"/></span></p> | 				<p><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}...<span class="caret"><fa icon="caret-right"/></span></p> | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| 		<template #header><fa icon="crop"/>{{ title }}</template> | 		<template #header><fa icon="crop"/>{{ title }}</template> | ||||||
| 		<div class="body"> | 		<div class="body"> | ||||||
| 			<vue-cropper ref="cropper" | 			<vue-cropper ref="cropper" | ||||||
| 				:src="image.url" | 				:src="imageUrl" | ||||||
| 				:view-mode="1" | 				:view-mode="1" | ||||||
| 				:aspect-ratio="aspectRatio" | 				:aspect-ratio="aspectRatio" | ||||||
| 				:container-style="{ width: '100%', 'max-height': '400px' }" | 				:container-style="{ width: '100%', 'max-height': '400px' }" | ||||||
| @@ -21,6 +21,7 @@ | |||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import i18n from '../../../i18n'; | import i18n from '../../../i18n'; | ||||||
| import VueCropper from 'vue-cropperjs'; | import VueCropper from 'vue-cropperjs'; | ||||||
|  | import * as url from '../../../../../prelude/url'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('desktop/views/components/crop-window.vue'), | 	i18n: i18n('desktop/views/components/crop-window.vue'), | ||||||
| @@ -41,6 +42,13 @@ export default Vue.extend({ | |||||||
| 			required: true | 			required: true | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  | 	computed: { | ||||||
|  | 		imageUrl() { | ||||||
|  | 			return `/proxy/?${url.query({ | ||||||
|  | 				url: this.image.url | ||||||
|  | 			})}`; | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
| 	methods: { | 	methods: { | ||||||
| 		ok() { | 		ok() { | ||||||
| 			(this.$refs.cropper as any).getCroppedCanvas().toBlob(blob => { | 			(this.$refs.cropper as any).getCroppedCanvas().toBlob(blob => { | ||||||
|   | |||||||
| @@ -21,9 +21,9 @@ | |||||||
| 		<img src="/assets/label-red.svg"/> | 		<img src="/assets/label-red.svg"/> | ||||||
| 		<p>{{ $t('nsfw') }}</p> | 		<p>{{ $t('nsfw') }}</p> | ||||||
| 	</div> | 	</div> | ||||||
| 	<div class="thumbnail" ref="thumbnail" :style="`background-color: ${ background }`"> |  | ||||||
| 		<img :src="file.thumbnailUrl" alt="" @load="onThumbnailLoaded"/> | 	<x-file-thumbnail class="thumbnail" :file="file" fit="contain"/> | ||||||
| 	</div> |  | ||||||
| 	<p class="name"> | 	<p class="name"> | ||||||
| 		<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span> | 		<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span> | ||||||
| 		<span class="ext" v-if="file.name.lastIndexOf('.') != -1">{{ file.name.substr(file.name.lastIndexOf('.')) }}</span> | 		<span class="ext" v-if="file.name.lastIndexOf('.') != -1">{{ file.name.substr(file.name.lastIndexOf('.')) }}</span> | ||||||
| @@ -34,14 +34,18 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import i18n from '../../../i18n'; | import i18n from '../../../i18n'; | ||||||
| import anime from 'animejs'; |  | ||||||
| import copyToClipboard from '../../../common/scripts/copy-to-clipboard'; | import copyToClipboard from '../../../common/scripts/copy-to-clipboard'; | ||||||
| import updateAvatar from '../../api/update-avatar'; | import updateAvatar from '../../api/update-avatar'; | ||||||
| import updateBanner from '../../api/update-banner'; | import updateBanner from '../../api/update-banner'; | ||||||
|  | import { appendQuery } from '../../../../../prelude/url'; | ||||||
|  | import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('desktop/views/components/drive.file.vue'), | 	i18n: i18n('desktop/views/components/drive.file.vue'), | ||||||
| 	props: ['file'], | 	props: ['file'], | ||||||
|  | 	components: { | ||||||
|  | 		XFileThumbnail | ||||||
|  | 	}, | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			isContextmenuShowing: false, | 			isContextmenuShowing: false, | ||||||
| @@ -57,11 +61,6 @@ export default Vue.extend({ | |||||||
| 		}, | 		}, | ||||||
| 		title(): string { | 		title(): string { | ||||||
| 			return `${this.file.name}\n${this.file.type} ${Vue.filter('bytes')(this.file.datasize)}`; | 			return `${this.file.name}\n${this.file.type} ${Vue.filter('bytes')(this.file.datasize)}`; | ||||||
| 		}, |  | ||||||
| 		background(): string { |  | ||||||
| 			return this.file.properties.avgColor && this.file.properties.avgColor.length == 3 |  | ||||||
| 				? `rgb(${this.file.properties.avgColor.join(',')})` |  | ||||||
| 				: 'transparent'; |  | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 	methods: { | 	methods: { | ||||||
| @@ -88,9 +87,10 @@ export default Vue.extend({ | |||||||
| 				action: this.copyUrl | 				action: this.copyUrl | ||||||
| 			}, { | 			}, { | ||||||
| 				type: 'link', | 				type: 'link', | ||||||
| 				href: `${this.file.url}?download`, | 				href: appendQuery(this.file.url, 'download'), | ||||||
| 				text: this.$t('contextmenu.download'), | 				text: this.$t('contextmenu.download'), | ||||||
| 				icon: 'download', | 				icon: 'download', | ||||||
|  | 				download: this.file.name | ||||||
| 			}, null, { | 			}, null, { | ||||||
| 				type: 'item', | 				type: 'item', | ||||||
| 				text: this.$t('@.delete'), | 				text: this.$t('@.delete'), | ||||||
| @@ -205,7 +205,7 @@ export default Vue.extend({ | |||||||
| <style lang="stylus" scoped> | <style lang="stylus" scoped> | ||||||
| .gvfdktuvdgwhmztnuekzkswkjygptfcv | .gvfdktuvdgwhmztnuekzkswkjygptfcv | ||||||
| 	padding 8px 0 0 0 | 	padding 8px 0 0 0 | ||||||
| 	height 180px | 	min-height 180px | ||||||
| 	border-radius 4px | 	border-radius 4px | ||||||
|  |  | ||||||
| 	&, * | 	&, * | ||||||
| @@ -254,6 +254,9 @@ export default Vue.extend({ | |||||||
| 		> .name | 		> .name | ||||||
| 			color var(--primaryForeground) | 			color var(--primaryForeground) | ||||||
|  |  | ||||||
|  | 		> .thumbnail | ||||||
|  | 			color var(--primaryForeground) | ||||||
|  |  | ||||||
| 	&[data-is-contextmenu-showing] | 	&[data-is-contextmenu-showing] | ||||||
| 		&:after | 		&:after | ||||||
| 			content "" | 			content "" | ||||||
| @@ -319,18 +322,7 @@ export default Vue.extend({ | |||||||
| 		width 128px | 		width 128px | ||||||
| 		height 128px | 		height 128px | ||||||
| 		margin auto | 		margin auto | ||||||
|  | 		color var(--driveFileIcon) | ||||||
| 		> img |  | ||||||
| 			display block |  | ||||||
| 			position absolute |  | ||||||
| 			top 0 |  | ||||||
| 			left 0 |  | ||||||
| 			right 0 |  | ||||||
| 			bottom 0 |  | ||||||
| 			margin auto |  | ||||||
| 			max-width 128px |  | ||||||
| 			max-height 128px |  | ||||||
| 			pointer-events none |  | ||||||
|  |  | ||||||
| 	> .name | 	> .name | ||||||
| 		display block | 		display block | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <template> | <template> | ||||||
| <mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="destroyDom"> | <mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="destroyDom"> | ||||||
| 	<template #header><fa icon="comments"/> {{ $t('title') }} <mk-user-name :user="user"/></template> | 	<template #header><fa icon="comments"/> {{ $t('@.messaging') }}: <mk-user-name :user="user"/></template> | ||||||
| 	<x-messaging-room :user="user" :class="$style.content"/> | 	<x-messaging-room :user="user" :class="$style.content"/> | ||||||
| </mk-window> | </mk-window> | ||||||
| </template> | </template> | ||||||
| @@ -12,7 +12,7 @@ import { url } from '../../../config'; | |||||||
| import getAcct from '../../../../../misc/acct/render'; | import getAcct from '../../../../../misc/acct/render'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('desktop/views/components/messaging-room-window.vue'), | 	i18n: i18n(), | ||||||
| 	components: { | 	components: { | ||||||
| 		XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default) | 		XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default) | ||||||
| 	}, | 	}, | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <template> | <template> | ||||||
| <mk-window ref="window" width="500px" height="560px" @closed="destroyDom"> | <mk-window ref="window" width="500px" height="560px" @closed="destroyDom"> | ||||||
| 	<template #header :class="$style.header"><fa icon="comments"/>{{ $t('title') }}</template> | 	<template #header :class="$style.header"><fa icon="comments"/>{{ $t('@.messaging') }}</template> | ||||||
| 	<x-messaging :class="$style.content" @navigate="navigate"/> | 	<x-messaging :class="$style.content" @navigate="navigate"/> | ||||||
| </mk-window> | </mk-window> | ||||||
| </template> | </template> | ||||||
| @@ -11,7 +11,7 @@ import i18n from '../../../i18n'; | |||||||
| import MkMessagingRoomWindow from './messaging-room-window.vue'; | import MkMessagingRoomWindow from './messaging-room-window.vue'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('desktop/views/components/messaging-window.vue'), | 	i18n: i18n(), | ||||||
| 	components: { | 	components: { | ||||||
| 		XMessaging: () => import('../../../common/views/components/messaging.vue').then(m => m.default) | 		XMessaging: () => import('../../../common/views/components/messaging.vue').then(m => m.default) | ||||||
| 	}, | 	}, | ||||||
|   | |||||||
| @@ -480,7 +480,7 @@ export default Vue.extend({ | |||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
| 			if (this.text && this.text != '') { | 			if (this.text && this.text != '') { | ||||||
| 				const hashtags = parse(this.text).filter(x => x.type == 'hashtag').map(x => x.hashtag); | 				const hashtags = parse(this.text).filter(x => x.node.type === 'hashtag').map(x => x.node.props.hashtag); | ||||||
| 				const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; | 				const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; | ||||||
| 				localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); | 				localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -27,6 +27,7 @@ | |||||||
| 						<option value="hashtags">{{ $t('@.widgets.hashtags') }}</option> | 						<option value="hashtags">{{ $t('@.widgets.hashtags') }}</option> | ||||||
| 						<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option> | 						<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option> | ||||||
| 						<option value="server">{{ $t('@.widgets.server') }}</option> | 						<option value="server">{{ $t('@.widgets.server') }}</option> | ||||||
|  | 						<option value="queue">{{ $t('@.widgets.queue') }}</option> | ||||||
| 						<option value="nav">{{ $t('@.widgets.nav') }}</option> | 						<option value="nav">{{ $t('@.widgets.nav') }}</option> | ||||||
| 						<option value="tips">{{ $t('@.widgets.tips') }}</option> | 						<option value="tips">{{ $t('@.widgets.tips') }}</option> | ||||||
| 					</select> | 					</select> | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ import parseAcct from '../../../../../misc/acct/parse'; | |||||||
| import getUserName from '../../../../../misc/get-user-name'; | import getUserName from '../../../../../misc/get-user-name'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('.vue'), | 	i18n: i18n(), | ||||||
| 	components: { | 	components: { | ||||||
| 		XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default) | 		XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default) | ||||||
| 	}, | 	}, | ||||||
| @@ -51,7 +51,7 @@ export default Vue.extend({ | |||||||
| 				this.user = user; | 				this.user = user; | ||||||
| 				this.fetching = false; | 				this.fetching = false; | ||||||
|  |  | ||||||
| 				document.title = `メッセージ: ${getUserName(this.user)}`; | 				document.title = this.$t('@.messaging') + ': ' + getUserName(this.user); | ||||||
|  |  | ||||||
| 				Progress.done(); | 				Progress.done(); | ||||||
| 			}); | 			}); | ||||||
|   | |||||||
| @@ -1,66 +0,0 @@ | |||||||
| <template> |  | ||||||
| <div class="pptjhabgjtt7kwskbfv4y3uml6fpuhmr"> |  | ||||||
| 	<h1>{{ this.$t('share-with', { name }) }}</h1> |  | ||||||
| 	<div> |  | ||||||
| 		<mk-signin v-if="!$store.getters.isSignedIn"/> |  | ||||||
| 		<mk-post-form v-else-if="!posted" :initial-text="text" :instant="true" @posted="posted = true"/> |  | ||||||
| 		<p v-if="posted" class="posted"><fa icon="check"/></p> |  | ||||||
| 	</div> |  | ||||||
| 	<button v-if="posted" class="ui button" @click="close">{{ $t('@.close') }}</button> |  | ||||||
| </div> |  | ||||||
| </template> |  | ||||||
|  |  | ||||||
| <script lang="ts"> |  | ||||||
| import Vue from 'vue'; |  | ||||||
| import i18n from '../../../i18n'; |  | ||||||
|  |  | ||||||
| export default Vue.extend({ |  | ||||||
| 	i18n: i18n('desktop/views/pages/share.vue'), |  | ||||||
| 	data() { |  | ||||||
| 		return { |  | ||||||
| 			name: null, |  | ||||||
| 			posted: false, |  | ||||||
| 			text: new URLSearchParams(location.search).get('text') |  | ||||||
| 		}; |  | ||||||
| 	}, |  | ||||||
| 	methods: { |  | ||||||
| 		close() { |  | ||||||
| 			window.close(); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 	mounted() { |  | ||||||
| 		this.$root.getMeta().then(meta => { |  | ||||||
| 			this.name = meta.name; |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
| </script> |  | ||||||
|  |  | ||||||
| <style lang="stylus" scoped> |  | ||||||
| .pptjhabgjtt7kwskbfv4y3uml6fpuhmr |  | ||||||
| 	padding 16px |  | ||||||
|  |  | ||||||
| 	> h1 |  | ||||||
| 		margin 0 0 8px 0 |  | ||||||
| 		color #555 |  | ||||||
| 		font-size 20px |  | ||||||
| 		text-align center |  | ||||||
|  |  | ||||||
| 	> div |  | ||||||
| 		max-width 500px |  | ||||||
| 		margin 0 auto |  | ||||||
| 		background #fff |  | ||||||
| 		border solid 1px rgba(#000, 0.1) |  | ||||||
| 		border-radius 6px |  | ||||||
| 		overflow hidden |  | ||||||
|  |  | ||||||
| 		> .posted |  | ||||||
| 			display block |  | ||||||
| 			margin 0 |  | ||||||
| 			padding 64px |  | ||||||
| 			text-align center |  | ||||||
|  |  | ||||||
| 	> button |  | ||||||
| 		display block |  | ||||||
| 		margin 16px auto |  | ||||||
| </style> |  | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| <template> | <template> | ||||||
| <div class="mkw-messaging"> | <div class="mkw-messaging"> | ||||||
| 	<ui-container :show-header="props.design == 0"> | 	<ui-container :show-header="props.design == 0"> | ||||||
| 		<template #header><fa icon="comments"/>{{ $t('title') }}</template> | 		<template #header><fa icon="comments"/>{{ $t('@.messaging') }}</template> | ||||||
| 		<template #func><button @click="add"><fa icon="plus"/></button></template> | 		<template #func><button @click="add"><fa icon="plus"/></button></template> | ||||||
|  |  | ||||||
| 		<x-messaging ref="index" compact @navigate="navigate"/> | 		<x-messaging ref="index" compact @navigate="navigate"/> | ||||||
| @@ -21,7 +21,7 @@ export default define({ | |||||||
| 		design: 0 | 		design: 0 | ||||||
| 	}) | 	}) | ||||||
| }).extend({ | }).extend({ | ||||||
| 	i18n: i18n('desktop/views/widgets/messaging.vue'), | 	i18n: i18n(''), | ||||||
| 	components: { | 	components: { | ||||||
| 		XMessaging: () => import('../../../common/views/components/messaging.vue').then(m => m.default) | 		XMessaging: () => import('../../../common/views/components/messaging.vue').then(m => m.default) | ||||||
| 	}, | 	}, | ||||||
|   | |||||||
| @@ -16,11 +16,11 @@ import App from './app.vue'; | |||||||
| import checkForUpdate from './common/scripts/check-for-update'; | import checkForUpdate from './common/scripts/check-for-update'; | ||||||
| import MiOS from './mios'; | import MiOS from './mios'; | ||||||
| import { version, codename, lang, locale } from './config'; | import { version, codename, lang, locale } from './config'; | ||||||
| import { builtinThemes, applyTheme, darkTheme } from './theme'; | import { builtinThemes, applyTheme, futureTheme } from './theme'; | ||||||
| import Dialog from './common/views/components/dialog.vue'; | import Dialog from './common/views/components/dialog.vue'; | ||||||
|  |  | ||||||
| if (localStorage.getItem('theme') == null) { | if (localStorage.getItem('theme') == null) { | ||||||
| 	applyTheme(darkTheme); | 	applyTheme(futureTheme); | ||||||
| } | } | ||||||
|  |  | ||||||
| //#region FontAwesome | //#region FontAwesome | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ import MkUserLists from './views/pages/user-lists.vue'; | |||||||
| import MkUserList from './views/pages/user-list.vue'; | import MkUserList from './views/pages/user-list.vue'; | ||||||
| import MkReversi from './views/pages/games/reversi.vue'; | import MkReversi from './views/pages/games/reversi.vue'; | ||||||
| import MkTag from './views/pages/tag.vue'; | import MkTag from './views/pages/tag.vue'; | ||||||
| import MkShare from './views/pages/share.vue'; | import MkShare from '../common/views/pages/share.vue'; | ||||||
| import MkFollow from '../common/views/pages/follow.vue'; | import MkFollow from '../common/views/pages/follow.vue'; | ||||||
| import MkNotFound from '../common/views/pages/not-found.vue'; | import MkNotFound from '../common/views/pages/not-found.vue'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,11 +1,7 @@ | |||||||
| <template> | <template> | ||||||
| <div class="pyvicwrksnfyhpfgkjwqknuururpaztw"> | <div class="pyvicwrksnfyhpfgkjwqknuururpaztw"> | ||||||
| 	<div class="preview"> | 	<div class="preview"> | ||||||
| 		<img v-if="kind == 'image'" ref="img" | 		<x-file-thumbnail class="preview" :file="file" fit="cover" :detail="true"/> | ||||||
| 			:src="file.url" |  | ||||||
| 			:alt="file.name" |  | ||||||
| 			:title="file.name" |  | ||||||
| 			:style="style"> |  | ||||||
| 		<template v-if="kind != 'image'"><fa icon="file"/></template> | 		<template v-if="kind != 'image'"><fa icon="file"/></template> | ||||||
| 		<footer v-if="kind == 'image' && file.properties && file.properties.width && file.properties.height"> | 		<footer v-if="kind == 'image' && file.properties && file.properties.width && file.properties.height"> | ||||||
| 			<span class="size"> | 			<span class="size"> | ||||||
| @@ -38,7 +34,7 @@ | |||||||
| 	<div class="menu"> | 	<div class="menu"> | ||||||
| 		<div> | 		<div> | ||||||
| 			<ui-input readonly :value="file.url">URL</ui-input> | 			<ui-input readonly :value="file.url">URL</ui-input> | ||||||
| 			<ui-button link :href="`${file.url}?download`" :download="file.name"><fa icon="download"/> {{ $t('download') }}</ui-button> | 			<ui-button link :href="dlUrl" :download="file.name"><fa icon="download"/> {{ $t('download') }}</ui-button> | ||||||
| 			<ui-button @click="rename"><fa icon="pencil-alt"/> {{ $t('rename') }}</ui-button> | 			<ui-button @click="rename"><fa icon="pencil-alt"/> {{ $t('rename') }}</ui-button> | ||||||
| 			<ui-button @click="move"><fa :icon="['far', 'folder-open']"/> {{ $t('move') }}</ui-button> | 			<ui-button @click="move"><fa :icon="['far', 'folder-open']"/> {{ $t('move') }}</ui-button> | ||||||
| 			<ui-button @click="toggleSensitive" v-if="file.isSensitive"><fa :icon="['far', 'eye']"/> {{ $t('unmark-as-sensitive') }}</ui-button> | 			<ui-button @click="toggleSensitive" v-if="file.isSensitive"><fa :icon="['far', 'eye']"/> {{ $t('unmark-as-sensitive') }}</ui-button> | ||||||
| @@ -61,11 +57,17 @@ | |||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import i18n from '../../../i18n'; | import i18n from '../../../i18n'; | ||||||
| import { gcd } from '../../../../../prelude/math'; | import { gcd } from '../../../../../prelude/math'; | ||||||
|  | import { appendQuery } from '../../../../../prelude/url'; | ||||||
|  | import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('mobile/views/components/drive.file-detail.vue'), | 	i18n: i18n('mobile/views/components/drive.file-detail.vue'), | ||||||
| 	props: ['file'], | 	props: ['file'], | ||||||
|  |  | ||||||
|  | 	components: { | ||||||
|  | 		XFileThumbnail | ||||||
|  | 	}, | ||||||
|  |  | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			gcd, | 			gcd, | ||||||
| @@ -86,6 +88,10 @@ export default Vue.extend({ | |||||||
| 			return this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? { | 			return this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? { | ||||||
| 				'background-color': `rgb(${ this.file.properties.avgColor.join(',') })` | 				'background-color': `rgb(${ this.file.properties.avgColor.join(',') })` | ||||||
| 			} : {}; | 			} : {}; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		dlUrl(): string { | ||||||
|  | 			return appendQuery(this.file.url, 'download'); | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -142,8 +148,7 @@ export default Vue.extend({ | |||||||
| 		padding 8px | 		padding 8px | ||||||
| 		background var(--bg) | 		background var(--bg) | ||||||
|  |  | ||||||
| 		> img | 		> .preview | ||||||
| 			display block |  | ||||||
| 			max-width 100% | 			max-width 100% | ||||||
| 			max-height 300px | 			max-height 300px | ||||||
| 			margin 0 auto | 			margin 0 auto | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| <template> | <template> | ||||||
| <a class="vupkuhvjnjyqaqhsiogfbywvjxynrgsm" @click.prevent="onClick" :href="`/i/drive/file/${ file.id }`" :data-is-selected="isSelected"> | <a class="vupkuhvjnjyqaqhsiogfbywvjxynrgsm" @click.prevent="onClick" :href="`/i/drive/file/${ file.id }`" :data-is-selected="isSelected"> | ||||||
| 	<div class="container"> | 	<div class="container"> | ||||||
| 		<div class="thumbnail" :style="thumbnail"></div> | 		<x-file-thumbnail class="thumbnail" :file="file" fit="cover"/> | ||||||
| 		<div class="body"> | 		<div class="body"> | ||||||
| 			<p class="name"> | 			<p class="name"> | ||||||
| 				<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span> | 				<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span> | ||||||
| @@ -26,9 +26,14 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import i18n from '../../../i18n'; | import i18n from '../../../i18n'; | ||||||
|  | import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('mobile/views/components/drive.file.vue'), | 	i18n: i18n('mobile/views/components/drive.file.vue'), | ||||||
| 	props: ['file'], | 	props: ['file'], | ||||||
|  | 	components: { | ||||||
|  | 		XFileThumbnail | ||||||
|  | 	}, | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			isSelected: false | 			isSelected: false | ||||||
| @@ -37,12 +42,6 @@ export default Vue.extend({ | |||||||
| 	computed: { | 	computed: { | ||||||
| 		browser(): any { | 		browser(): any { | ||||||
| 			return this.$parent; | 			return this.$parent; | ||||||
| 		}, |  | ||||||
| 		thumbnail(): any { |  | ||||||
| 			return { |  | ||||||
| 				'background-color': this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? `rgb(${this.file.properties.avgColor.join(',')})` : 'transparent', |  | ||||||
| 				'background-image': `url(${this.file.thumbnailUrl})` |  | ||||||
| 			}; |  | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 	created() { | 	created() { | ||||||
| @@ -74,9 +73,12 @@ export default Vue.extend({ | |||||||
| 		pointer-events none | 		pointer-events none | ||||||
|  |  | ||||||
| 	> .container | 	> .container | ||||||
|  | 		display grid | ||||||
| 		max-width 500px | 		max-width 500px | ||||||
| 		margin 0 auto | 		margin 0 auto | ||||||
| 		padding 16px | 		padding 16px | ||||||
|  | 		grid-template-columns 64px 1fr | ||||||
|  | 		grid-column-gap 10px | ||||||
|  |  | ||||||
| 		&:after | 		&:after | ||||||
| 			content "" | 			content "" | ||||||
| @@ -84,18 +86,13 @@ export default Vue.extend({ | |||||||
| 			clear both | 			clear both | ||||||
|  |  | ||||||
| 		> .thumbnail | 		> .thumbnail | ||||||
| 			display block |  | ||||||
| 			float left |  | ||||||
| 			width 64px | 			width 64px | ||||||
| 			height 64px | 			height 64px | ||||||
| 			background-size cover | 			color var(--driveFileIcon) | ||||||
| 			background-position center center |  | ||||||
|  |  | ||||||
| 		> .body | 		> .body | ||||||
| 			display block | 			display block | ||||||
| 			float left | 			word-break break-all | ||||||
| 			width calc(100% - 74px) |  | ||||||
| 			margin-left 10px |  | ||||||
|  |  | ||||||
| 			> .name | 			> .name | ||||||
| 				display block | 				display block | ||||||
| @@ -104,8 +101,7 @@ export default Vue.extend({ | |||||||
| 				font-size 0.9em | 				font-size 0.9em | ||||||
| 				font-weight bold | 				font-weight bold | ||||||
| 				color var(--text) | 				color var(--text) | ||||||
| 				text-overflow ellipsis | 				word-break break-word | ||||||
| 				overflow-wrap break-word |  | ||||||
|  |  | ||||||
| 				> .ext | 				> .ext | ||||||
| 					opacity 0.5 | 					opacity 0.5 | ||||||
| @@ -154,6 +150,6 @@ export default Vue.extend({ | |||||||
| 		background var(--primary) | 		background var(--primary) | ||||||
|  |  | ||||||
| 		&, * | 		&, * | ||||||
| 			color #fff !important | 			color var(--primaryForeground) !important | ||||||
|  |  | ||||||
| </style> | </style> | ||||||
|   | |||||||
| @@ -367,7 +367,7 @@ export default Vue.extend({ | |||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
| 			if (this.text && this.text != '') { | 			if (this.text && this.text != '') { | ||||||
| 				const hashtags = parse(this.text).filter(x => x.type == 'hashtag').map(x => x.hashtag); | 				const hashtags = parse(this.text).filter(x => x.node.type === 'hashtag').map(x => x.node.props.hashtag); | ||||||
| 				const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; | 				const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; | ||||||
| 				localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); | 				localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ | |||||||
| 					<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option> | 					<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option> | ||||||
| 					<option value="version">{{ $t('@.widgets.version') }}</option> | 					<option value="version">{{ $t('@.widgets.version') }}</option> | ||||||
| 					<option value="server">{{ $t('@.widgets.server') }}</option> | 					<option value="server">{{ $t('@.widgets.server') }}</option> | ||||||
|  | 					<option value="queue">{{ $t('@.widgets.queue') }}</option> | ||||||
| 					<option value="memo">{{ $t('@.widgets.memo') }}</option> | 					<option value="memo">{{ $t('@.widgets.memo') }}</option> | ||||||
| 					<option value="nav">{{ $t('@.widgets.nav') }}</option> | 					<option value="nav">{{ $t('@.widgets.nav') }}</option> | ||||||
| 					<option value="tips">{{ $t('@.widgets.tips') }}</option> | 					<option value="tips">{{ $t('@.widgets.tips') }}</option> | ||||||
|   | |||||||
| @@ -49,7 +49,7 @@ const defaultDeviceSettings = { | |||||||
| 	roundedCorners: true, | 	roundedCorners: true, | ||||||
| 	reduceMotion: false, | 	reduceMotion: false, | ||||||
| 	darkmode: true, | 	darkmode: true, | ||||||
| 	darkTheme: 'dark', | 	darkTheme: 'bb5a8287-a072-4b0a-8ae5-ea2a0d33f4f2', | ||||||
| 	lightTheme: 'light', | 	lightTheme: 'light', | ||||||
| 	lineWidth: 1, | 	lineWidth: 1, | ||||||
| 	fontSize: 0, | 	fontSize: 0, | ||||||
|   | |||||||
| @@ -10,26 +10,26 @@ export type Theme = { | |||||||
| 	props: { [key: string]: string }; | 	props: { [key: string]: string }; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const lightTheme: Theme = require('../theme/light.json5'); | export const lightTheme: Theme = require('../themes/light.json5'); | ||||||
| export const darkTheme: Theme = require('../theme/dark.json5'); | export const darkTheme: Theme = require('../themes/dark.json5'); | ||||||
| export const pinkTheme: Theme = require('../theme/pink.json5'); | export const lavenderTheme: Theme = require('../themes/lavender.json5'); | ||||||
| export const blackTheme: Theme = require('../theme/black.json5'); | export const futureTheme: Theme = require('../themes/future.json5'); | ||||||
| export const halloweenTheme: Theme = require('../theme/halloween.json5'); | export const halloweenTheme: Theme = require('../themes/halloween.json5'); | ||||||
| export const cafeTheme: Theme = require('../theme/cafe.json5'); | export const cafeTheme: Theme = require('../themes/cafe.json5'); | ||||||
| export const japaneseSushiSetTheme: Theme = require('../theme/japanese-sushi-set.json5'); | export const japaneseSushiSetTheme: Theme = require('../themes/japanese-sushi-set.json5'); | ||||||
| export const gruvboxDarkTheme: Theme = require('../theme/gruvbox-dark.json5'); | export const gruvboxDarkTheme: Theme = require('../themes/gruvbox-dark.json5'); | ||||||
| export const monokaiTheme: Theme = require('../theme/monokai.json5'); | export const monokaiTheme: Theme = require('../themes/monokai.json5'); | ||||||
| export const colorfulTheme: Theme = require('../theme/colorful.json5'); | export const colorfulTheme: Theme = require('../themes/colorful.json5'); | ||||||
| export const rainyTheme: Theme = require('../theme/rainy.json5'); | export const rainyTheme: Theme = require('../themes/rainy.json5'); | ||||||
| export const mauveTheme: Theme = require('../theme/mauve.json5'); | export const mauveTheme: Theme = require('../themes/mauve.json5'); | ||||||
| export const grayTheme: Theme = require('../theme/gray.json5'); | export const grayTheme: Theme = require('../themes/gray.json5'); | ||||||
| export const tweetDeckTheme: Theme = require('../theme/tweet-deck.json5'); | export const tweetDeckTheme: Theme = require('../themes/tweet-deck.json5'); | ||||||
|  |  | ||||||
| export const builtinThemes = [ | export const builtinThemes = [ | ||||||
| 	lightTheme, | 	lightTheme, | ||||||
| 	darkTheme, | 	darkTheme, | ||||||
| 	pinkTheme, | 	lavenderTheme, | ||||||
| 	blackTheme, | 	futureTheme, | ||||||
| 	halloweenTheme, | 	halloweenTheme, | ||||||
| 	cafeTheme, | 	cafeTheme, | ||||||
| 	japaneseSushiSetTheme, | 	japaneseSushiSetTheme, | ||||||
|   | |||||||
| @@ -43,6 +43,11 @@ | |||||||
| 		} | 		} | ||||||
| 	], | 	], | ||||||
| 	"share_target": { | 	"share_target": { | ||||||
| 		"url_template": "share?text=【{title}】%0A{text}%0A{url}" | 		"action": "/share/", | ||||||
|  | 		"params": { | ||||||
|  | 			"title": "title", | ||||||
|  | 			"text": "text", | ||||||
|  | 			"url": "url" | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,20 +0,0 @@ | |||||||
| { |  | ||||||
| 	id: 'bb5a8287-a072-4b0a-8ae5-ea2a0d33f4f2', |  | ||||||
|  |  | ||||||
| 	name: 'Future', |  | ||||||
| 	author: 'syuilo', |  | ||||||
|  |  | ||||||
| 	base: 'dark', |  | ||||||
|  |  | ||||||
| 	vars: { |  | ||||||
| 		primary: 'rgb(94, 158, 185)', |  | ||||||
| 		secondary: 'rgb(22, 24, 30)', |  | ||||||
| 		text: 'rgb(214, 218, 224)', |  | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	props: { |  | ||||||
| 		renoteGradient: '#0a2d3c', |  | ||||||
| 		renoteText: '$primary', |  | ||||||
| 		quoteBorder: '$primary', |  | ||||||
| 	}, |  | ||||||
| } |  | ||||||
| @@ -153,6 +153,8 @@ | |||||||
| 		messagingRoomMessageBg: '$secondary', | 		messagingRoomMessageBg: '$secondary', | ||||||
| 		messagingRoomMessageFg: '#fff', | 		messagingRoomMessageFg: '#fff', | ||||||
| 
 | 
 | ||||||
|  | 		driveFileIcon: '$text', | ||||||
|  | 
 | ||||||
| 		formButtonBorder: 'rgba(255, 255, 255, 0.1)', | 		formButtonBorder: 'rgba(255, 255, 255, 0.1)', | ||||||
| 		formButtonHoverBg: ':alpha<0.2<$primary', | 		formButtonHoverBg: ':alpha<0.2<$primary', | ||||||
| 		formButtonHoverBorder: ':alpha<0.5<$primary', | 		formButtonHoverBorder: ':alpha<0.5<$primary', | ||||||
							
								
								
									
										39
									
								
								src/client/themes/future.json5
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/client/themes/future.json5
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | { | ||||||
|  | 	id: 'bb5a8287-a072-4b0a-8ae5-ea2a0d33f4f2', | ||||||
|  |  | ||||||
|  | 	name: 'Future', | ||||||
|  | 	author: 'syuilo', | ||||||
|  | 	desc: 'Sci-fi flavored', | ||||||
|  |  | ||||||
|  | 	base: 'dark', | ||||||
|  |  | ||||||
|  | 	vars: { | ||||||
|  | 		c0: '#0e0e0e', | ||||||
|  | 		c1: 'rgb(255, 105, 78)', | ||||||
|  | 		c2: 'rgb(99, 197, 210)', | ||||||
|  | 		c4: 'rgb(253, 254, 214)', | ||||||
|  | 		c3: 'rgb(204, 254, 253)', | ||||||
|  | 		primary: '$c1', | ||||||
|  | 		secondary: '#191919', | ||||||
|  | 		text: '$c3', | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	props: { | ||||||
|  | 		bg: '$c0', | ||||||
|  | 		noteText: '$c4', | ||||||
|  | 		noteHeaderAcct: ':alpha<0.65<$c4', | ||||||
|  | 		noteHeaderInfo: ':alpha<0.5<$c4', | ||||||
|  | 		subNoteText: ':alpha<0.7<$c4', | ||||||
|  | 		renoteGradient: 'rgba(0, 0, 0, 0)', | ||||||
|  | 		renoteText: '$c2', | ||||||
|  | 		quoteBorder: '$c2', | ||||||
|  | 		mfmHashtag: '$c1', | ||||||
|  | 		mfmUrl: '$c2', | ||||||
|  | 		mfmLink: '$c2', | ||||||
|  | 		mfmMention: '$c1', | ||||||
|  | 		mfmMentionForeground: '#fff', | ||||||
|  | 		notificationIndicator: '$c2', | ||||||
|  | 		link: '$c2', | ||||||
|  | 		desktopHeaderBg: '$secondary', | ||||||
|  | 	}, | ||||||
|  | } | ||||||
| @@ -153,6 +153,8 @@ | |||||||
| 		messagingRoomMessageBg: '#eee', | 		messagingRoomMessageBg: '#eee', | ||||||
| 		messagingRoomMessageFg: '#333', | 		messagingRoomMessageFg: '#333', | ||||||
| 
 | 
 | ||||||
|  | 		driveFileIcon: '$text', | ||||||
|  | 
 | ||||||
| 		formButtonBorder: 'rgba(0, 0, 0, 0.1)', | 		formButtonBorder: 'rgba(0, 0, 0, 0.1)', | ||||||
| 		formButtonHoverBg: ':alpha<0.12<$primary', | 		formButtonHoverBg: ':alpha<0.12<$primary', | ||||||
| 		formButtonHoverBorder: ':alpha<0.3<$primary', | 		formButtonHoverBorder: ':alpha<0.3<$primary', | ||||||
| @@ -179,7 +181,7 @@ | |||||||
| 		desktopTimelineSrcHover: ':darken<7<$text', | 		desktopTimelineSrcHover: ':darken<7<$text', | ||||||
| 		desktopWindowTitle: '$text', | 		desktopWindowTitle: '$text', | ||||||
| 		desktopWindowShadow: 'rgba(0, 0, 0, 0.2)', | 		desktopWindowShadow: 'rgba(0, 0, 0, 0.2)', | ||||||
| 		desktopDriveBg: '#fff', | 		desktopDriveBg: '@bg', | ||||||
| 		desktopDriveFolderBg: ':lighten<31<$primary', | 		desktopDriveFolderBg: ':lighten<31<$primary', | ||||||
| 		desktopDriveFolderHoverBg: ':lighten<27<$primary', | 		desktopDriveFolderHoverBg: ':lighten<27<$primary', | ||||||
| 		desktopDriveFolderActiveBg: ':lighten<25<$primary', | 		desktopDriveFolderActiveBg: ':lighten<25<$primary', | ||||||
| @@ -19,6 +19,8 @@ export type Source = { | |||||||
| 		host: string; | 		host: string; | ||||||
| 		port: number; | 		port: number; | ||||||
| 		pass: string; | 		pass: string; | ||||||
|  | 		db?: number; | ||||||
|  | 		prefix?: string; | ||||||
| 	}; | 	}; | ||||||
| 	elasticsearch: { | 	elasticsearch: { | ||||||
| 		host: string; | 		host: string; | ||||||
|   | |||||||
							
								
								
									
										61
									
								
								src/daemons/queue-stats.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/daemons/queue-stats.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | |||||||
|  | import * as Deque from 'double-ended-queue'; | ||||||
|  | import Xev from 'xev'; | ||||||
|  | import { deliverQueue, inboxQueue } from '../queue'; | ||||||
|  |  | ||||||
|  | const ev = new Xev(); | ||||||
|  |  | ||||||
|  | const interval = 3000; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Report queue stats regularly | ||||||
|  |  */ | ||||||
|  | export default function() { | ||||||
|  | 	const log = new Deque<any>(); | ||||||
|  |  | ||||||
|  | 	ev.on('requestQueueStatsLog', x => { | ||||||
|  | 		ev.emit(`queueStatsLog:${x.id}`, log.toArray().slice(0, x.length || 50)); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	let activeDeliverJobs = 0; | ||||||
|  | 	let activeInboxJobs = 0; | ||||||
|  |  | ||||||
|  | 	deliverQueue.on('global:active', () => { | ||||||
|  | 		activeDeliverJobs++; | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	inboxQueue.on('global:active', () => { | ||||||
|  | 		activeInboxJobs++; | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	async function tick() { | ||||||
|  | 		const deliverJobCounts = await deliverQueue.getJobCounts(); | ||||||
|  | 		const inboxJobCounts = await inboxQueue.getJobCounts(); | ||||||
|  |  | ||||||
|  | 		const stats = { | ||||||
|  | 			deliver: { | ||||||
|  | 				activeSincePrevTick: activeDeliverJobs, | ||||||
|  | 				active: deliverJobCounts.active, | ||||||
|  | 				waiting: deliverJobCounts.waiting, | ||||||
|  | 				delayed: deliverJobCounts.delayed | ||||||
|  | 			}, | ||||||
|  | 			inbox: { | ||||||
|  | 				activeSincePrevTick: activeInboxJobs, | ||||||
|  | 				active: inboxJobCounts.active, | ||||||
|  | 				waiting: inboxJobCounts.waiting, | ||||||
|  | 				delayed: inboxJobCounts.delayed | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		ev.emit('queueStats', stats); | ||||||
|  |  | ||||||
|  | 		log.unshift(stats); | ||||||
|  | 		if (log.length > 200) log.pop(); | ||||||
|  |  | ||||||
|  | 		activeDeliverJobs = 0; | ||||||
|  | 		activeInboxJobs = 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	tick(); | ||||||
|  |  | ||||||
|  | 	setInterval(tick, interval); | ||||||
|  | } | ||||||
| @@ -5,6 +5,8 @@ export default config.redis ? redis.createClient( | |||||||
| 	config.redis.port, | 	config.redis.port, | ||||||
| 	config.redis.host, | 	config.redis.host, | ||||||
| 	{ | 	{ | ||||||
| 		auth_pass: config.redis.pass | 		auth_pass: config.redis.pass, | ||||||
|  | 		prefix: config.redis.prefix, | ||||||
|  | 		db: config.redis.db || 0 | ||||||
| 	} | 	} | ||||||
| ) : null; | ) : null; | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ import Xev from 'xev'; | |||||||
| import Logger from './services/logger'; | import Logger from './services/logger'; | ||||||
| import serverStats from './daemons/server-stats'; | import serverStats from './daemons/server-stats'; | ||||||
| import notesStats from './daemons/notes-stats'; | import notesStats from './daemons/notes-stats'; | ||||||
|  | import queueStats from './daemons/queue-stats'; | ||||||
| import loadConfig from './config/load'; | import loadConfig from './config/load'; | ||||||
| import { Config } from './config/types'; | import { Config } from './config/types'; | ||||||
| import { lessThan } from './prelude/array'; | import { lessThan } from './prelude/array'; | ||||||
| @@ -50,6 +51,7 @@ function main() { | |||||||
| 		if (program.daemons) { | 		if (program.daemons) { | ||||||
| 			serverStats(); | 			serverStats(); | ||||||
| 			notesStats(); | 			notesStats(); | ||||||
|  | 			queueStats(); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import { parseFragment, DefaultTreeDocumentFragment } from 'parse5'; | import { parseFragment, DefaultTreeDocumentFragment } from 'parse5'; | ||||||
| import { URL } from 'url'; | import { URL } from 'url'; | ||||||
|  | import { urlRegex } from './prelude'; | ||||||
|  |  | ||||||
| export function fromHtml(html: string): string { | export function fromHtml(html: string): string { | ||||||
| 	if (html == null) return null; | 	if (html == null) return null; | ||||||
| @@ -14,7 +15,7 @@ export function fromHtml(html: string): string { | |||||||
|  |  | ||||||
| 	return text.trim(); | 	return text.trim(); | ||||||
|  |  | ||||||
| 	function getText(node: any) { | 	function getText(node: any): string { | ||||||
| 		if (node.nodeName == '#text') return node.value; | 		if (node.nodeName == '#text') return node.value; | ||||||
|  |  | ||||||
| 		if (node.childNodes) { | 		if (node.childNodes) { | ||||||
| @@ -38,10 +39,11 @@ export function fromHtml(html: string): string { | |||||||
| 				const txt = getText(node); | 				const txt = getText(node); | ||||||
| 				const rel = node.attrs.find((x: any) => x.name == 'rel'); | 				const rel = node.attrs.find((x: any) => x.name == 'rel'); | ||||||
| 				const href = node.attrs.find((x: any) => x.name == 'href'); | 				const href = node.attrs.find((x: any) => x.name == 'href'); | ||||||
|  | 				const isHashtag = rel && rel.value.match('tag') !== null; | ||||||
|  |  | ||||||
| 				// ハッシュタグ / hrefがない / txtがURL | 				// ハッシュタグ / hrefがない / txtがURL | ||||||
| 				if ((rel && rel.value.match('tag') !== null) || !href || href.value == txt) { | 				if (isHashtag || !href || href.value == txt) { | ||||||
| 					text += txt; | 					text += isHashtag || txt.match(urlRegex) ? txt : `<${txt}>`; | ||||||
| 				// メンション | 				// メンション | ||||||
| 				} else if (txt.startsWith('@') && !(rel && rel.value.match(/^me /))) { | 				} else if (txt.startsWith('@') && !(rel && rel.value.match(/^me /))) { | ||||||
| 					const part = txt.split('@'); | 					const part = txt.split('@'); | ||||||
|   | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| import * as A from '../prelude/array'; | import * as A from '../prelude/array'; | ||||||
| import * as S from '../prelude/string'; | import * as S from '../prelude/string'; | ||||||
| import { MfmForest, MfmTree } from './types'; | import { MfmForest, MfmTree } from './prelude'; | ||||||
| import { createTree, createLeaf } from '../prelude/tree'; | import { createTree, createLeaf } from '../prelude/tree'; | ||||||
|  |  | ||||||
| function isEmptyTextTree(t: MfmTree): boolean { | function isEmptyTextTree(t: MfmTree): boolean { | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { mfmLanguage } from './language'; | import { mfmLanguage } from './language'; | ||||||
| import { MfmForest } from './types'; | import { MfmForest } from './prelude'; | ||||||
| import { normalize } from './normalize'; | import { normalize } from './normalize'; | ||||||
|  |  | ||||||
| export function parse(source: string): MfmForest { | export function parse(source: string): MfmForest { | ||||||
|   | |||||||
| @@ -35,3 +35,5 @@ export function createLeaf(type: string, props: any): MfmTree { | |||||||
| export function createTree(type: string, children: MfmForest, props: any): MfmTree { | export function createTree(type: string, children: MfmForest, props: any): MfmTree { | ||||||
| 	return T.createTree({ type, props }, children); | 	return T.createTree({ type, props }, children); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export const urlRegex = /^https?:\/\/[\w\/:%#@\$&\?!\(\)\[\]~\.,=\+\-]+/; | ||||||
| @@ -2,7 +2,7 @@ import { JSDOM } from 'jsdom'; | |||||||
| import config from '../config'; | import config from '../config'; | ||||||
| import { INote } from '../models/note'; | import { INote } from '../models/note'; | ||||||
| import { intersperse } from '../prelude/array'; | import { intersperse } from '../prelude/array'; | ||||||
| import { MfmForest, MfmTree } from './types'; | import { MfmForest, MfmTree } from './prelude'; | ||||||
|  |  | ||||||
| export function toHtml(tokens: MfmForest, mentionedRemoteUsers: INote['mentionedRemoteUsers'] = []) { | export function toHtml(tokens: MfmForest, mentionedRemoteUsers: INote['mentionedRemoteUsers'] = []) { | ||||||
| 	if (tokens == null) { | 	if (tokens == null) { | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import * as fs from 'fs'; | import * as fs from 'fs'; | ||||||
| import * as isSvg from 'is-svg'; | import isSvg from 'is-svg'; | ||||||
|  |  | ||||||
| export default function(path: string) { | export default function(path: string) { | ||||||
| 	try { | 	try { | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								src/misc/content-disposition.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/misc/content-disposition.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | const cd = require('content-disposition'); | ||||||
|  |  | ||||||
|  | export function contentDisposition(type: 'inline' | 'attachment', filename: string): string { | ||||||
|  | 	const fallback = filename.replace(/[^\w.-]/g, '_'); | ||||||
|  | 	return cd(filename, { type, fallback }); | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								src/misc/convert-host.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/misc/convert-host.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | import config from '../config'; | ||||||
|  | import { toUnicode, toASCII } from 'punycode'; | ||||||
|  | import { URL } from 'url'; | ||||||
|  |  | ||||||
|  | export function getFullApAccount(username: string, host: string) { | ||||||
|  | 	return host ? `${username}@${toApHost(host)}` : `${username}@${toApHost(config.host)}`; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function isSelfHost(host: string) { | ||||||
|  | 	if (host == null) return true; | ||||||
|  | 	return toApHost(config.host) === toApHost(host); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function extractDbHost(uri: string) { | ||||||
|  | 	const url = new URL(uri); | ||||||
|  | 	return toDbHost(url.hostname); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function toDbHost(host: string) { | ||||||
|  | 	if (host == null) return null; | ||||||
|  | 	return toUnicode(host.toLowerCase()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function toApHost(host: string) { | ||||||
|  | 	if (host == null) return null; | ||||||
|  | 	return toASCII(host.toLowerCase()); | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								src/misc/create-temp.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/misc/create-temp.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | import * as tmp from 'tmp'; | ||||||
|  |  | ||||||
|  | export function createTemp(): Promise<[string, any]> { | ||||||
|  | 	return new Promise<[string, any]>((res, rej) => { | ||||||
|  | 		tmp.file((e, path, fd, cleanup) => { | ||||||
|  | 			if (e) return rej(e); | ||||||
|  | 			res([path, cleanup]); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user