Compare commits
	
		
			150 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 0aa0a9d24b | ||
|   | a9a93db2b4 | ||
|   | f187df3933 | ||
|   | 8abe8042d7 | ||
|   | 58fd46ff6f | ||
|   | fef8b662c1 | ||
|   | 8de2f4ce76 | ||
|   | e5e344e1cd | ||
|   | e70d7edf41 | ||
|   | 71d4d51fb2 | ||
|   | aaf38f1cbe | ||
|   | 0e0d6692c0 | ||
|   | 29f927fe72 | ||
|   | ee39d9594e | ||
|   | cefd2a4c54 | ||
|   | a08c20d9af | ||
|   | dc11f1afbf | ||
|   | b0f2b209a2 | ||
|   | a25fdfd519 | ||
|   | c1aa58596d | ||
|   | b6a3eb2445 | ||
|   | 310f4d2edb | ||
|   | 701fee3139 | ||
|   | 593c2b9517 | ||
|   | 96b2267cb8 | ||
|   | 84730a071a | ||
|   | d0b0cf8dfb | ||
|   | 749200d22b | ||
|   | a47baad943 | ||
|   | 50abb51ece | ||
|   | 1f890c5bed | ||
|   | 97f23af86d | ||
|   | d77aa1f26a | ||
|   | 0b075ad4e9 | ||
|   | 423f776ed0 | ||
|   | 084fd8152b | ||
|   | 89d35c2e63 | ||
|   | be33581642 | ||
|   | 2d6d9f30e1 | ||
|   | 85721065fd | ||
|   | 9d65768d4d | ||
|   | 13f69e4291 | ||
|   | 6a0affcec1 | ||
|   | ab6a84cd45 | ||
|   | ba93bf7478 | ||
|   | 1c4e1af7c3 | ||
|   | a85f4c4fc4 | ||
|   | 9d6c8806af | ||
|   | ff52ea2a7c | ||
|   | f247ee9dd3 | ||
|   | f4cec53ba1 | ||
|   | ad70b50fee | ||
|   | ea7b2b3141 | ||
|   | c2f932e28b | ||
|   | c637882578 | ||
|   | ef7221e39e | ||
|   | 6b571a7799 | ||
|   | 0638b6cb69 | ||
|   | a39c1706a1 | ||
|   | c741e27057 | ||
|   | ede854c215 | ||
|   | 6a953b4d94 | ||
|   | 1d763096c7 | ||
|   | 630d873ec0 | ||
|   | d427957ea7 | ||
|   | 2430cc0e2c | ||
|   | e57ee24864 | ||
|   | 3bc05ab3f2 | ||
|   | 4e50dcfa93 | ||
|   | fcf5531e5b | ||
|   | 3bef69ee58 | ||
|   | 311a4f28b0 | ||
|   | 01e692b353 | ||
|   | 3b445af6fc | ||
|   | 1e43ece637 | ||
|   | 6f1048c006 | ||
|   | d686e70f2b | ||
|   | 70f524b82d | ||
|   | 79c6475028 | ||
|   | 7dee5309dc | ||
|   | 958ec7b03f | ||
|   | 9153434906 | ||
|   | 3a08364c24 | ||
|   | a9beeab502 | ||
|   | 38c901069a | ||
|   | 3f7606060e | ||
|   | 777f20e9be | ||
|   | 8e39aecffe | ||
|   | 9e1ab54097 | ||
|   | 0f9e09f4bd | ||
|   | 743ebc17b9 | ||
|   | 9bc4af76b8 | ||
|   | 46fa26426d | ||
|   | 58d0dc1795 | ||
|   | bc11702f7d | ||
|   | 6288de5813 | ||
|   | bbf59c7d9f | ||
| ![imgbot[bot]](/assets/img/avatar_default.png)  | d7787bacf7 | ||
|   | 23ae0515c4 | ||
|   | fe88b34b8a | ||
|   | 74aa031a22 | ||
|   | 6aeed212d9 | ||
|   | 45b972c059 | ||
|   | 7ecfc007a9 | ||
|   | fc78c75bab | ||
|   | 59493a0cd9 | ||
|   | 6060c6d56e | ||
|   | 6cdbb27169 | ||
|   | ed8b073e54 | ||
|   | 7dd193636c | ||
|   | 01d018510c | ||
|   | 1c273a0a75 | ||
|   | fa2c7658a0 | ||
|   | 84ca3a7d45 | ||
|   | 902c73e6ac | ||
|   | 3b626f72e4 | ||
|   | f5ce137a6b | ||
|   | 3ce9d12361 | ||
|   | 2fe2f3b1eb | ||
|   | 17b3ee41db | ||
|   | 56d2a5d5d3 | ||
|   | 3a17ff0983 | ||
|   | 9a9270bbe9 | ||
|   | 512eee4f51 | ||
|   | db01fa0eef | ||
|   | 0c49a1ebd5 | ||
|   | 636d6394e3 | ||
|   | c67c091b3a | ||
|   | 333604898c | ||
|   | 076ac3b614 | ||
|   | 0e1468b159 | ||
|   | 66409029e7 | ||
|   | 8ec6b2ec11 | ||
|   | 14736620ec | ||
|   | 831ca53b63 | ||
|   | 6138d46509 | ||
|   | c3003cb363 | ||
|   | 4277e53433 | ||
|   | 6516bd2ade | ||
|   | 27d22f954a | ||
|   | 88f5e8e8e2 | ||
|   | fd2ae6d3cf | ||
|   | 238edd36f7 | ||
|   | 5d847f9808 | ||
|   | ac914af9c3 | ||
|   | 636f90ca0c | ||
|   | 29469bb7c6 | ||
|   | 4f043b1841 | ||
|   | 85008303f5 | ||
|   | 3432d6e615 | 
| @@ -116,8 +116,25 @@ autoAdmin: true | |||||||
| # Whether disable HSTS | # Whether disable HSTS | ||||||
| #disableHsts: true | #disableHsts: true | ||||||
|  |  | ||||||
| # Clustering | # Number of worker processes | ||||||
| #clusterLimit: 1 | #clusterLimit: 1 | ||||||
|  |  | ||||||
|  | # Job concurrency per worker | ||||||
|  | # deliverJobConcurrency: 128; | ||||||
|  | # inboxJobConcurrency: 16; | ||||||
|  |  | ||||||
| # IP address family used for outgoing request (ipv4, ipv6 or dual) | # IP address family used for outgoing request (ipv4, ipv6 or dual) | ||||||
| #outgoingAddressFamily: ipv4 | #outgoingAddressFamily: ipv4 | ||||||
|  |  | ||||||
|  | # Syslog option | ||||||
|  | #syslog: | ||||||
|  | #  host: localhost | ||||||
|  | #  port: 514 | ||||||
|  |  | ||||||
|  | # Proxy for HTTP/HTTPS | ||||||
|  | #proxy: http://127.0.0.1:3128 | ||||||
|  |  | ||||||
|  | # Proxy for SMTP/SMTPS | ||||||
|  | #proxySmtp: http://127.0.0.1:3128   # use HTTP/1.1 CONNECT | ||||||
|  | #proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4 | ||||||
|  | #proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5 | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,7 @@ | |||||||
| *.svg -diff -text | *.svg -diff -text | ||||||
| *.psd -diff -text | *.psd -diff -text | ||||||
| *.ai -diff -text | *.ai -diff -text | ||||||
|  | *.mqo -diff -text | ||||||
|  | *.glb -diff -text | ||||||
|  | *.blend -diff -text | ||||||
|  | *.afdesign -diff -text | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -30,3 +30,10 @@ api-docs.json | |||||||
| .DS_Store | .DS_Store | ||||||
| /files | /files | ||||||
| ormconfig.json | ormconfig.json | ||||||
|  |  | ||||||
|  | # blender backups | ||||||
|  | *.blend1 | ||||||
|  | *.blend2 | ||||||
|  | *.blend3 | ||||||
|  | *.blend4 | ||||||
|  | *.blend5 | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| v12.6.0 | v12.9.1 | ||||||
|   | |||||||
							
								
								
									
										123
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										123
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,6 +1,129 @@ | |||||||
| ChangeLog | ChangeLog | ||||||
| ========= | ========= | ||||||
|  |  | ||||||
|  | 11.31.1 (2019/09/03) | ||||||
|  | -------------------- | ||||||
|  | ### 🐛Fixes | ||||||
|  | * 誰がリアクションしたか見れるやつの表示を改善 | ||||||
|  |  | ||||||
|  | 11.31.0 (2019/09/02) | ||||||
|  | -------------------- | ||||||
|  | ### ✨Improvements | ||||||
|  | * Syslogサポート | ||||||
|  | * チャートの同期機能をAPI経由で使えるように | ||||||
|  | * SMTPでProxyを使用できるように | ||||||
|  | * リアクションにホバーすることで誰がリアクションしたか見れるように | ||||||
|  | * リプライ時、返信元のlocalOnly属性を引き継ぐように | ||||||
|  | * 引用付き、ローカルのみなどの案内文にアイコン追加 | ||||||
|  | * AP deliver/inbox job の並列度を変更できるように | ||||||
|  | * clusterLimitの既定値を1に | ||||||
|  | * AP inbox ジョブの並列度を下げる | ||||||
|  | * CWが付いた投稿はAP上でNote.sensitiveフラグを付けるように | ||||||
|  | * メモウィジェットの内容を自動で保存するように | ||||||
|  | * ページURLが他と重複してたらエラーを投げるように | ||||||
|  | * ページURLが空の時エラーを投げるように | ||||||
|  | * リアクションが解除されたときはアニメーションしないように | ||||||
|  | * 設定の各セクションごとにURLを割り当てるように | ||||||
|  | * 管理画面の各セクションごとにURLを割り当てるように | ||||||
|  |  | ||||||
|  | ### 🐛Fixes | ||||||
|  | * 未実装のTLのRenoteクエリを実装 | ||||||
|  | * タイムラインAPIのexcludeNsfwオプションを実装 | ||||||
|  | * ユーザーページの投稿一覧の私の投稿にRenoteが表示される問題を修正 | ||||||
|  | * meta APIでemojiプロパティに不要な情報が含まれているのを修正 | ||||||
|  | * モバイル版でドライブのファイルを削除したときの挙動がおかしい問題を修正 | ||||||
|  | * visiblity-chooserにlocalOnly属性が伝わらなかったのを修正 | ||||||
|  | * 言語指定したときコードブロックが表示されない問題を修正 | ||||||
|  | * トークのメッセージがはみ出す問題を修正 | ||||||
|  | * CWの中のサムネイルのサイズが変なのを少し修正 | ||||||
|  | * リアクションが初めて付いた時のエフェクトが消えている問題を修正 | ||||||
|  | * 無効になっているスイッチを操作できる問題を修正 | ||||||
|  | * Mキー連打で画面が真っ暗問題を修正 | ||||||
|  | * AmazonのURLプレビューが出来ない問題を修正 | ||||||
|  | * 表記ゆれを修正 | ||||||
|  |  | ||||||
|  | 11.30.0 (2019/08/24) | ||||||
|  | -------------------- | ||||||
|  | ### ✨Improvements | ||||||
|  | * Room: 家具をすべてしまうボタンを追加 | ||||||
|  | * Room: カップ麺追加 | ||||||
|  | * Room: ホログラフィックディスプレイ追加 | ||||||
|  | * Room: エナジードリンク追加 | ||||||
|  | * Room: ピンギンの色を変えられるように | ||||||
|  | * Room: プレビューの見やすさを向上 | ||||||
|  | * Room保存時にダイアログを表示するように | ||||||
|  | * Roomから移動するときに未保存ならば警告するように | ||||||
|  |  | ||||||
|  | ### 🐛Fixes | ||||||
|  | * MisskeyRoomからページを戻した時、テキスト入力画面で選択位置変更ができない問題を修正 | ||||||
|  |  | ||||||
|  | 11.29.0 (2019/08/19) | ||||||
|  | -------------------- | ||||||
|  | ### ✨Improvements | ||||||
|  | * Room: ソファ追加 | ||||||
|  | * Room: 螺旋階段追加 | ||||||
|  | * Room: ゴミ箱追加 | ||||||
|  |  | ||||||
|  | ### 🐛Fixes | ||||||
|  | * Room: 部屋を離れても裏でレンダリングが続く問題を修正 | ||||||
|  | * Room: アバターのレンダリングを修正 | ||||||
|  | * Room: ライティングの調整 | ||||||
|  |  | ||||||
|  | 11.28.2 (2019/08/18) | ||||||
|  | -------------------- | ||||||
|  | ### 🐛Fixes | ||||||
|  | * 他人の部屋なのに部屋編集UIが表示されるのを修正 | ||||||
|  | * オブジェクトストレージを使用している場合Roomで画像を読み込めない問題を修正 | ||||||
|  |  | ||||||
|  | 11.28.1 (2019/08/18) | ||||||
|  | -------------------- | ||||||
|  | ### 🐛Fixes | ||||||
|  | * オブジェクトストレージを使用している場合Roomで画像を読み込めない問題を修正 | ||||||
|  | * Roomで家具を移動など確定せずに「しまう」と部屋ごと消える問題を修正 | ||||||
|  |  | ||||||
|  | 11.28.0 (2019/08/18) | ||||||
|  | -------------------- | ||||||
|  | ### ✨Improvements | ||||||
|  | * 自分の部屋を作れるように | ||||||
|  | * Delキーを押して投稿を削除するときに確認ダイアログを出すように | ||||||
|  | * Elasticsearchのインデックス名をconfigで変更できるように | ||||||
|  |  | ||||||
|  | ### 🐛Fixes | ||||||
|  | * コンテンツを遡ってる途中に新しいアイテムが先頭に追加されると上限に達している場合末尾のアイテムが削除される問題を修正 | ||||||
|  | * ユーザー名が突き抜けるのを修正 | ||||||
|  | * ユーザー一覧とかでサイレンス・凍結されたユーザーの状態が表示されてなかったのを修正 | ||||||
|  | * タイトルやアイコンがきちんと設定されないことがあるのを修正 | ||||||
|  | * ドライブアップロード直後に取得できるURLがoriginalじゃない問題を修正 | ||||||
|  | * リモートユーザー向けのNoteUnreadsレコードが作成される問題を修正 | ||||||
|  | * Hashtagがupdateできない問題を修正 | ||||||
|  | * ハッシュタグの更新がタグの数だけ並列で行われてDBを重くしてしまうことがあるのを修正 | ||||||
|  |  | ||||||
|  | 11.27.1 (2019/08/01) | ||||||
|  | -------------------- | ||||||
|  | ### 🐛Fixes | ||||||
|  | * オブジェクトストレージに関する問題を修正 | ||||||
|  |  | ||||||
|  | 11.27.0 (2019/07/29) | ||||||
|  | -------------------- | ||||||
|  | ### ✨Improvements | ||||||
|  | * 「削除して編集」機能を追加 | ||||||
|  | * HTTPリクエストのKeep-AliveとPrxoy対応(サーバーのパフォーマンス向上) | ||||||
|  | * 通知を種類でフィルタリングして表示できるように | ||||||
|  | * モバイルで通知ページを表示することができるように | ||||||
|  | * 非ログイン時の警告処理 | ||||||
|  |  | ||||||
|  | ### 🐛Fixes | ||||||
|  | * リモートの絵文字が更新されない問題を修正 | ||||||
|  | * リンクバリデーションリンクが一瞬表示されてしまう問題を修正 | ||||||
|  | * 選択していない状態でウィジェットが追加できる問題を修正 | ||||||
|  |  | ||||||
|  | 11.26.2 (2019/07/25) | ||||||
|  | -------------------- | ||||||
|  | ### 🐛Fixes | ||||||
|  | * すでに使われたことのあるユーザー名を再度使えないように | ||||||
|  | * モバイルのウィジェットページが常に i/update-client-setting を呼び続ける問題を修正 | ||||||
|  | * 投稿フォームのヘッダに添付ファイル数がちゃんと表示されない問題を修正 | ||||||
|  |  | ||||||
| 11.26.1 (2019/07/21) | 11.26.1 (2019/07/21) | ||||||
| -------------------- | -------------------- | ||||||
| ### 🐛Fixes | ### 🐛Fixes | ||||||
|   | |||||||
| @@ -7,12 +7,18 @@ Feature suggestions and bug reports are filed in https://github.com/syuilo/missk | |||||||
| * Please search existing issues to avoid duplication. If your issue is already filed, please add your reaction or comment to the existing one. | * Please search existing issues to avoid duplication. If your issue is already filed, please add your reaction or comment to the existing one. | ||||||
| * If you have multiple independent issues, please submit them separately. | * If you have multiple independent issues, please submit them separately. | ||||||
|  |  | ||||||
|  | ## Branches | ||||||
|  | * **master** branch is tracking the latest release and used for production purposes. | ||||||
|  | * **develop** branch is where we work for the next release. | ||||||
|  | * **l10n_develop** branch is reserved for localization management. | ||||||
|  |  | ||||||
| ## Localization (l10n) | ## Localization (l10n) | ||||||
| Misskey uses [Crowdin](https://crowdin.com/project/misskey) for localization management. | Misskey uses [Crowdin](https://crowdin.com/project/misskey) for localization management. | ||||||
| You can improve our translations with your Crowdin account. | You can improve our translations with your Crowdin account. | ||||||
| Changes you make in Crowdin will be merged into the develop branch by @syuilo. | Your changes in Crowdin are automatically submitted as a PR (with the title "New Crowdin translations") to the repository. | ||||||
|  | The owner [@syuilo](https://github.com/syuilo) merges the PR into the develop branch before the next release. | ||||||
|  |  | ||||||
| If you cannot find the language you want to contribute with, please open an issue. | If your language is not listed in Crowdin, please open an issue. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -32,8 +38,18 @@ Documentation of Vue I18n is available at http://kazupon.github.io/vue-i18n/intr | |||||||
| Misskey uses CircleCI for executing automated tests. | Misskey uses CircleCI for executing automated tests. | ||||||
| Configuration files are located in [`/.circleci`](/.circleci). | Configuration files are located in [`/.circleci`](/.circleci). | ||||||
|  |  | ||||||
| ## FAQ | ## Adding MisskeyRoom items | ||||||
|  | * Use English for material, object and texture names. | ||||||
|  | * Use meter for unit of length. | ||||||
|  | * Your PR should include all source files (e.g. `.png`, `.blend`) of your models (for later editing). | ||||||
|  | * Your PR must include the glTF binary files (`.glb`) of your models. | ||||||
|  | * Add a locale key `room.furnitures.YOUR_ITEM` at [`/locales/ja-JP.yml`](/locales/ja-JP.yml). | ||||||
|  | * Add a furniture definition at [`/src/client/app/common/scripts/room/furnitures.json5`](/src/client/app/common/scripts/room/furnitures.json5). | ||||||
|  |  | ||||||
|  | If you have no experience on 3D modeling, we suggest to use the free 3DCG software [Blender](https://www.blender.org/). | ||||||
|  | You can find information on glTF 2.0 at [glTF 2.0 — Blender Manual]( https://docs.blender.org/manual/en/dev/addons/io_scene_gltf2.html). | ||||||
|  |  | ||||||
|  | ## FAQ | ||||||
| ### How to resolve conflictions occurred at yarn.lock? | ### How to resolve conflictions occurred at yarn.lock? | ||||||
|  |  | ||||||
| Just execute `yarn` to fix it. | Just execute `yarn` to fix it. | ||||||
| @@ -93,6 +109,8 @@ Good: | |||||||
| if (foo) bar; | if (foo) bar; | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | Make sure that the condition and the body statement are on the same line. | ||||||
|  |  | ||||||
| ### Do not use `==` when it can simply be replaced with `===`. | ### Do not use `==` when it can simply be replaced with `===`. | ||||||
| 🥰 | 🥰 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| FROM node:12.6-alpine AS base | FROM node:12.9.1-alpine AS base | ||||||
|  |  | ||||||
| ENV NODE_ENV=production | ENV NODE_ENV=production | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								README.md
									
									
									
									
									
								
							| @@ -104,21 +104,17 @@ 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.jpeg?token-time=2145916800&token-hash=at8QpJXJ8C0zINY_NmoMKv-MhXVoUK-YzTgaJPJzJYU%3D" alt="Hiroshi Seki" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5888816/36da0f7c15954df0ab13f9abdf227f66/1.jpeg?token-time=2145916800&token-hash=at8QpJXJ8C0zINY_NmoMKv-MhXVoUK-YzTgaJPJzJYU%3D" alt="Hiroshi Seki" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/20010324/b8af4bd31ae34fbf8806cc0e6228e400/1.png?token-time=2145916800&token-hash=iyiocfousNIUwASmatsIDq8EOsmLUdrQNkWyktHlmJg%3D" alt="Nemo" width="100"></td> |  | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan" width="100"></td> | ||||||
| <td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi" width="100"></td> | <td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi" width="100"></td> | ||||||
| <td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze" width="100"></td> | <td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1.jpe?token-time=2145916800&token-hash=bqwLTk0Wo0hUJJ8J5y7ii05bLzz-_CDA7Bo0Mp4RFU0%3D" alt="ne_moni" width="100"></td> |  | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/4.jpe?token-time=2145916800&token-hash=zEyJqVM7u9d8Ri-65fJYSJcWF1jBH1nJ5a3taRzrTmw%3D" alt="Melilot" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/4.jpe?token-time=2145916800&token-hash=zEyJqVM7u9d8Ri-65fJYSJcWF1jBH1nJ5a3taRzrTmw%3D" alt="Melilot" 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/rane_hs">Hiroshi Seki</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=20010324">Nemo</a></td> |  | ||||||
| <td><a href="https://www.patreon.com/weepjp">weepjp</a></td> | <td><a href="https://www.patreon.com/weepjp">weepjp</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=19045173">kiritan</a></td> | <td><a href="https://www.patreon.com/user?u=19045173">kiritan</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=776209">Denshi</a></td> | <td><a href="https://www.patreon.com/user?u=776209">Denshi</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=557245">mkatze</a></td> | <td><a href="https://www.patreon.com/user?u=557245">mkatze</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td> |  | ||||||
| <td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td> | <td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td> | ||||||
| </tr></table> | </tr></table> | ||||||
| <table><tr> | <table><tr> | ||||||
| @@ -126,56 +122,50 @@ Please see the [Contribution Guide](./CONTRIBUTING.md). | |||||||
| <td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td> | <td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1.png?token-time=2145916800&token-hash=9nEQje_eMvUjq9a7L3uBqW-MQbS-rRMaMgd7UYVoFNM%3D" alt="mydarkstar" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1.png?token-time=2145916800&token-hash=9nEQje_eMvUjq9a7L3uBqW-MQbS-rRMaMgd7UYVoFNM%3D" alt="mydarkstar" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1.jpe?token-time=2145916800&token-hash=UQRWf01TwHDV4Cls1K0YAOAjM29ssif7hLVq0ESQ0hs%3D" alt="nemu" width="100"></td> |  | ||||||
| <td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin" width="100"></td> | <td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td> | ||||||
| <td><img src="https://c8.patreon.com/2/200/17463605" alt="Sampot" width="100"></td> |  | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td> | ||||||
| </tr><tr> | </tr><tr> | ||||||
| <td><a href="https://www.patreon.com/osapon">osapon</a></td> | <td><a href="https://www.patreon.com/osapon">osapon</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td> | <td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td> | ||||||
| <td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61</a></td> | <td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61</a></td> | ||||||
| <td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td> | <td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=13039004">nemu</a></td> |  | ||||||
| <td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td> | <td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td> | ||||||
| <td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td> | <td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=17463605">Sampot</a></td> |  | ||||||
| <td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td> | <td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td> | ||||||
| </tr></table> | </tr></table> | ||||||
| <table><tr> | <table><tr> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpe?token-time=2145916800&token-hash=CPxGQhKIlEaa6WUcgbyHixyKEhakiw9RFdOhsIJBQ_o%3D" alt="takimura" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpe?token-time=2145916800&token-hash=CPxGQhKIlEaa6WUcgbyHixyKEhakiw9RFdOhsIJBQ_o%3D" alt="takimura" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/83884b38afc74d4cbe83c30a13b10edd/1.png?token-time=2145916800&token-hash=R5Tog8RWg0rguRoCIoir3lThokrdPvs8Utfikhc0nhY%3D" alt="Atsuko Tominaga" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%3D" alt="Atsuko Tominaga" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1.jpe?token-time=2145916800&token-hash=EWxXhVbZYH7KB4IDT3joc8TbIg8zPO40x1r5IDn3R7c%3D" alt="Hiratake" width="100"></td> |  | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpe?token-time=2145916800&token-hash=qA8j97lIZNc-74AuZ0p4F3ms6sKPeKjtNt2vEuwpsyo%3D" alt="Hekovic" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpe?token-time=2145916800&token-hash=qA8j97lIZNc-74AuZ0p4F3ms6sKPeKjtNt2vEuwpsyo%3D" alt="Hekovic" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/10789744/97175095d8f04c0f86225ff47cb98d40/1.jpeg?token-time=2145916800&token-hash=l4AoMR7Nj7K4yAHrkrk2hAoggPkbSPm12m1nmbe9Pb8%3D" alt="Naoki Hirayama" width="100"></td> |  | ||||||
| </tr><tr> | </tr><tr> | ||||||
| <td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td> | <td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td> | ||||||
| <td><a href="https://www.patreon.com/takimura">takimura</a></td> | <td><a href="https://www.patreon.com/takimura">takimura</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td> | <td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td> | <td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td> | ||||||
| <td><a href="https://www.patreon.com/hiratake">Hiratake</a></td> |  | ||||||
| <td><a href="https://www.patreon.com/noellabo">noellabo</a></td> | <td><a href="https://www.patreon.com/noellabo">noellabo</a></td> | ||||||
| <td><a href="https://www.patreon.com/Corset">CG</a></td> | <td><a href="https://www.patreon.com/Corset">CG</a></td> | ||||||
| <td><a href="https://www.patreon.com/hekovic">Hekovic</a></td> | <td><a href="https://www.patreon.com/hekovic">Hekovic</a></td> | ||||||
| <td><a href="https://www.patreon.com/spinlock">Naoki Hirayama</a></td> |  | ||||||
| </tr></table> | </tr></table> | ||||||
| <table><tr> | <table><tr> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1.jpeg?token-time=2145916800&token-hash=L55UhJ0rcuNAH3w_ryeeGN4hC6taoOixyAhraEi0bzw%3D" alt="dansup" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1.jpeg?token-time=2145916800&token-hash=L55UhJ0rcuNAH3w_ryeeGN4hC6taoOixyAhraEi0bzw%3D" alt="dansup" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1.jpeg?token-time=2145916800&token-hash=d8jBQLMOHD87KtXs5C9fk1o58DMF73pQ-dYH3uZJPBE%3D" alt="Gargron" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1.jpeg?token-time=2145916800&token-hash=d8jBQLMOHD87KtXs5C9fk1o58DMF73pQ-dYH3uZJPBE%3D" alt="Gargron" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td> | ||||||
|  | <td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td> | ||||||
| </tr><tr> | </tr><tr> | ||||||
| <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=23932002">nenohi</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:** Fri, 19 Jul 2019 15:41:09 UTC | **Last updated:** Sun, 01 Sep 2019 22:11:05 UTC | ||||||
| <!-- PATREON_END --> | <!-- PATREON_END --> | ||||||
|  |  | ||||||
| :four_leaf_clover: Copyright | :four_leaf_clover: Copyright | ||||||
|   | |||||||
| @@ -25,10 +25,9 @@ server { | |||||||
| } | } | ||||||
|  |  | ||||||
| server { | server { | ||||||
|     listen 443 http2; |     listen 443 ssl http2; | ||||||
|     listen [::]:443 http2; |     listen [::]:443 ssl http2; | ||||||
|     server_name example.tld; |     server_name example.tld; | ||||||
|     ssl on; |  | ||||||
|     ssl_session_cache shared:ssl_session_cache:10m; |     ssl_session_cache shared:ssl_session_cache:10m; | ||||||
|  |  | ||||||
|     # To use Let's Encrypt certificate |     # To use Let's Encrypt certificate | ||||||
|   | |||||||
| @@ -47,7 +47,11 @@ gulp.task('build:copy:views', () => | |||||||
| 	gulp.src('./src/server/web/views/**/*').pipe(gulp.dest('./built/server/web/views')) | 	gulp.src('./src/server/web/views/**/*').pipe(gulp.dest('./built/server/web/views')) | ||||||
| ); | ); | ||||||
|  |  | ||||||
| gulp.task('build:copy', gulp.parallel('build:copy:views', () => | gulp.task('build:copy:fonts', () => | ||||||
|  | 	gulp.src('./node_modules/three/examples/fonts/**/*').pipe(gulp.dest('./built/client/assets/fonts/')) | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | gulp.task('build:copy', gulp.parallel('build:copy:views', 'build:copy:fonts', () => | ||||||
| 	gulp.src([ | 	gulp.src([ | ||||||
| 		'./src/const.json', | 		'./src/const.json', | ||||||
| 		'./src/server/web/views/**/*', | 		'./src/server/web/views/**/*', | ||||||
|   | |||||||
| @@ -34,6 +34,19 @@ common: | |||||||
|   signup: "Registrovat" |   signup: "Registrovat" | ||||||
|   signout: "Odhlásit" |   signout: "Odhlásit" | ||||||
|   reload-to-apply-the-setting: "Pro uplatnění tohoto nastavení musíte znovu načíst tuto stránku. Chcete ji načíst teď?" |   reload-to-apply-the-setting: "Pro uplatnění tohoto nastavení musíte znovu načíst tuto stránku. Chcete ji načíst teď?" | ||||||
|  |   delete-confirm: "Opravdu chcete smazat tento příspěvek?" | ||||||
|  |   signin-required: "Přihlašte se, prosím" | ||||||
|  |   notification-type: "Typy oznámení" | ||||||
|  |   notification-types: | ||||||
|  |     all: "Všechny" | ||||||
|  |     pollVote: "Hlasy" | ||||||
|  |     follow: "Sledovaní" | ||||||
|  |     receiveFollowRequest: "Žádost o sledování" | ||||||
|  |     reply: "Odpovědi" | ||||||
|  |     quote: "Citace" | ||||||
|  |     renote: "Renotovat" | ||||||
|  |     mention: "Zmínky" | ||||||
|  |     reaction: "Reakce" | ||||||
|   got-it: "Rozumím!" |   got-it: "Rozumím!" | ||||||
|   customization-tips: |   customization-tips: | ||||||
|     title: "Tipy pro přizpůsobení" |     title: "Tipy pro přizpůsobení" | ||||||
| @@ -99,6 +112,7 @@ common: | |||||||
|     hide-contents: "Schovat obsah" |     hide-contents: "Schovat obsah" | ||||||
|     reply-placeholder: "Odpovědět na tento příspěvek" |     reply-placeholder: "Odpovědět na tento příspěvek" | ||||||
|     quote-placeholder: "Citovat tento příspěvek" |     quote-placeholder: "Citovat tento příspěvek" | ||||||
|  |     quote-attached: "Přiložit citaci" | ||||||
|     submit: "Odeslat" |     submit: "Odeslat" | ||||||
|     reply: "Odpovědět" |     reply: "Odpovědět" | ||||||
|     renote: "Renotovat" |     renote: "Renotovat" | ||||||
| @@ -108,12 +122,15 @@ common: | |||||||
|     create-poll: "Vytvořit anketu" |     create-poll: "Vytvořit anketu" | ||||||
|     text-remain: "zbývá ještě {} znaků" |     text-remain: "zbývá ještě {} znaků" | ||||||
|     recent-tags: "Nejnovější" |     recent-tags: "Nejnovější" | ||||||
|  |     click-to-tagging: "Klikni pro otágování" | ||||||
|     visibility: "Viditelnost" |     visibility: "Viditelnost" | ||||||
|     geolocation-alert: "Vaše zařízení nedalo k dispozici lokaci" |     geolocation-alert: "Vaše zařízení nedalo k dispozici lokaci" | ||||||
|     error: "Chyba" |     error: "Chyba" | ||||||
|     enter-username: "Zadejte své uživatelské jméno" |     enter-username: "Zadejte své uživatelské jméno" | ||||||
|  |     specified-recipient: "Pro" | ||||||
|     add-visible-user: "Přidat uživatele" |     add-visible-user: "Přidat uživatele" | ||||||
|     username-prompt: "Zadejte své uživatelské jméno" |     username-prompt: "Zadejte své uživatelské jméno" | ||||||
|  |     enter-file-name: "Upravit název souboru" | ||||||
|   weekday-short: |   weekday-short: | ||||||
|     sunday: "Ne" |     sunday: "Ne" | ||||||
|     monday: "Po" |     monday: "Po" | ||||||
| @@ -180,6 +197,7 @@ common: | |||||||
|     remember-note-visibility: "Zapamatovat viditelnost příspěvků" |     remember-note-visibility: "Zapamatovat viditelnost příspěvků" | ||||||
|     web-search-engine: "Webové vyhledávače" |     web-search-engine: "Webové vyhledávače" | ||||||
|     web-search-engine-desc: "Například: https://www.google.com/?#q={{query}}" |     web-search-engine-desc: "Například: https://www.google.com/?#q={{query}}" | ||||||
|  |     paste: "Vložit" | ||||||
|     keep-cw: "Zachovat varování o obsahu" |     keep-cw: "Zachovat varování o obsahu" | ||||||
|     keep-cw-desc: "Při odpovědi na příspěvek bude varování o obsahu nastaveno stejně jako původní příspěvek." |     keep-cw-desc: "Při odpovědi na příspěvek bude varování o obsahu nastaveno stejně jako původní příspěvek." | ||||||
|     i-like-sushi: "Mam radši sushi (než puding)" |     i-like-sushi: "Mam radši sushi (než puding)" | ||||||
| @@ -217,7 +235,7 @@ common: | |||||||
|     deck-column-width-wide: "Široké" |     deck-column-width-wide: "Široké" | ||||||
|     use-shadow: "Používat v rozhraní stíny" |     use-shadow: "Používat v rozhraní stíny" | ||||||
|     rounded-corners: "Zakulatit rohy v rozhraní" |     rounded-corners: "Zakulatit rohy v rozhraní" | ||||||
|     circle-icons: "Používat kulaté ikony" |     circle-icons: "Používat kulaté avatary" | ||||||
|     contrasted-acct: "Přidat uživatelskému účtu kontrast" |     contrasted-acct: "Přidat uživatelskému účtu kontrast" | ||||||
|     wallpaper: "Obrázek na pozadí" |     wallpaper: "Obrázek na pozadí" | ||||||
|     choose-wallpaper: "Zvolit pozadí" |     choose-wallpaper: "Zvolit pozadí" | ||||||
| @@ -265,6 +283,15 @@ common: | |||||||
|     sync: "Synchronizace" |     sync: "Synchronizace" | ||||||
|     save: "Uložit" |     save: "Uložit" | ||||||
|     saved: "Uloženo" |     saved: "Uloženo" | ||||||
|  |     room: "Místnost" | ||||||
|  |     _room: | ||||||
|  |       graphicsQuality: "Kvalita grafiky" | ||||||
|  |       _graphicsQuality: | ||||||
|  |         ultra: "Nejvyšší" | ||||||
|  |         high: "Vysoká" | ||||||
|  |         medium: "Střední" | ||||||
|  |         low: "Nízká" | ||||||
|  |         cheep: "Nejnižší" | ||||||
|   search: "Hledání" |   search: "Hledání" | ||||||
|   delete: "Odstranit" |   delete: "Odstranit" | ||||||
|   loading: "Načítám..." |   loading: "Načítám..." | ||||||
| @@ -357,6 +384,7 @@ common/views/components/games/reversi/reversi.vue: | |||||||
|     cancel: "Zrušit" |     cancel: "Zrušit" | ||||||
| common/views/components/games/reversi/reversi.game.vue: | common/views/components/games/reversi/reversi.game.vue: | ||||||
|   surrender: "Vzdát se" |   surrender: "Vzdát se" | ||||||
|  |   surrendered: "Vzdaním se" | ||||||
|   looped-map: "Zacyklená mapa" |   looped-map: "Zacyklená mapa" | ||||||
| common/views/components/games/reversi/reversi.index.vue: | common/views/components/games/reversi/reversi.index.vue: | ||||||
|   title: "Misskey Reversi" |   title: "Misskey Reversi" | ||||||
| @@ -390,6 +418,7 @@ common/views/components/connect-failed.vue: | |||||||
|   title: "Nelze se připojit k serveru" |   title: "Nelze se připojit k serveru" | ||||||
|   description: "Nastal problém s Vaším připojením k internetu, nebo server není dostupný nebo zrovna probíhá údržba. Prosím {zkuste to znova} za pár minut." |   description: "Nastal problém s Vaším připojením k internetu, nebo server není dostupný nebo zrovna probíhá údržba. Prosím {zkuste to znova} za pár minut." | ||||||
|   thanks: "Děkujeme že jste použili Misskey." |   thanks: "Děkujeme že jste použili Misskey." | ||||||
|  |   troubleshoot: "Odstranění problémů" | ||||||
| common/views/components/connect-failed.troubleshooter.vue: | common/views/components/connect-failed.troubleshooter.vue: | ||||||
|   title: "Poradce při potížích" |   title: "Poradce při potížích" | ||||||
|   network: "Síťové připojení" |   network: "Síťové připojení" | ||||||
| @@ -398,6 +427,8 @@ common/views/components/connect-failed.troubleshooter.vue: | |||||||
|   checking-internet: "Ověřuji připojení k internetu." |   checking-internet: "Ověřuji připojení k internetu." | ||||||
|   server: "Připojení k serveru" |   server: "Připojení k serveru" | ||||||
|   checking-server: "Spojuji se se serverem" |   checking-server: "Spojuji se se serverem" | ||||||
|  |   finding: "Vyšetřování problému" | ||||||
|  |   no-network: "Žádné připojení k síti" | ||||||
|   no-network-desc: "Ujistěte se že jste připojeni k Internetu." |   no-network-desc: "Ujistěte se že jste připojeni k Internetu." | ||||||
|   no-internet: "Nejste připojeni k internetu" |   no-internet: "Nejste připojeni k internetu" | ||||||
|   no-internet-desc: "Jste připojen k síti, ale zdá se že stále chybí připojení k Internetu. Prosím zkontrolujte Vaše připojení k Internetu." |   no-internet-desc: "Jste připojen k síti, ale zdá se že stále chybí připojení k Internetu. Prosím zkontrolujte Vaše připojení k Internetu." | ||||||
| @@ -489,6 +520,7 @@ common/views/components/note-menu.vue: | |||||||
|   unpin: "Odepnout" |   unpin: "Odepnout" | ||||||
|   delete: "Odstranit" |   delete: "Odstranit" | ||||||
|   delete-confirm: "Opravdu chcete smazat tento příspěvek?" |   delete-confirm: "Opravdu chcete smazat tento příspěvek?" | ||||||
|  |   delete-and-edit: "Smazat a upravit" | ||||||
|   remote: "Ukázat originální poznámku" |   remote: "Ukázat originální poznámku" | ||||||
| common/views/components/user-menu.vue: | common/views/components/user-menu.vue: | ||||||
|   mention: "Zmínění" |   mention: "Zmínění" | ||||||
| @@ -921,6 +953,7 @@ desktop/views/components/ui.header.account.vue: | |||||||
|   lists: "Seznamy" |   lists: "Seznamy" | ||||||
|   groups: "Skupiny" |   groups: "Skupiny" | ||||||
|   admin: "Administrace" |   admin: "Administrace" | ||||||
|  |   room: "Místnost" | ||||||
| desktop/views/components/ui.header.nav.vue: | desktop/views/components/ui.header.nav.vue: | ||||||
|   game: "Hry" |   game: "Hry" | ||||||
| desktop/views/components/ui.header.notifications.vue: | desktop/views/components/ui.header.notifications.vue: | ||||||
| @@ -1264,6 +1297,8 @@ mobile/views/pages/search.vue: | |||||||
|   not-found: "Pro '{q}' nebyly nalezeny žádné příspěvky." |   not-found: "Pro '{q}' nebyly nalezeny žádné příspěvky." | ||||||
| mobile/views/pages/selectdrive.vue: | mobile/views/pages/selectdrive.vue: | ||||||
|   select-file: "Vybrat soubory" |   select-file: "Vybrat soubory" | ||||||
|  | mobile/views/pages/notifications.vue: | ||||||
|  |   notifications: "Oznámení" | ||||||
| mobile/views/pages/user/home.vue: | mobile/views/pages/user/home.vue: | ||||||
|   activity: "Aktivita" |   activity: "Aktivita" | ||||||
|   frequently-replied-users: "Častá zmínění" |   frequently-replied-users: "Častá zmínění" | ||||||
| @@ -1332,3 +1367,10 @@ pages: | |||||||
|         arg1: "Seznamy" |         arg1: "Seznamy" | ||||||
|     types: |     types: | ||||||
|       array: "Seznamy" |       array: "Seznamy" | ||||||
|  | room: | ||||||
|  |   translate: "Přesunout" | ||||||
|  |   save: "Uložit" | ||||||
|  |   saved: "Uloženo" | ||||||
|  |   furnitures: | ||||||
|  |     moon: "Měsíc" | ||||||
|  |     bin: "Koš" | ||||||
|   | |||||||
| @@ -35,6 +35,13 @@ common: | |||||||
|   signout: "Log ud" |   signout: "Log ud" | ||||||
|   reload-to-apply-the-setting: "Denne indstilling slår først igennem, når du har genindlæst siden. Vil du genindlæse siden nu?" |   reload-to-apply-the-setting: "Denne indstilling slår først igennem, når du har genindlæst siden. Vil du genindlæse siden nu?" | ||||||
|   fetching-as-ap-object: "Tilladelse til sammenkobling" |   fetching-as-ap-object: "Tilladelse til sammenkobling" | ||||||
|  |   delete-confirm: "Er du helt sikker på, at du vil slette denne post?" | ||||||
|  |   notification-types: | ||||||
|  |     all: "Alle" | ||||||
|  |     follow: "Følger" | ||||||
|  |     reply: "Svar" | ||||||
|  |     renote: "Gen-postering" | ||||||
|  |     reaction: "Reaktion" | ||||||
|   got-it: "Det er OK" |   got-it: "Det er OK" | ||||||
|   customization-tips: |   customization-tips: | ||||||
|     title: "Tips om tilpasning" |     title: "Tips om tilpasning" | ||||||
| @@ -206,7 +213,7 @@ common: | |||||||
|     deck-column-width-wide: "Bred" |     deck-column-width-wide: "Bred" | ||||||
|     use-shadow: "Vis skygger" |     use-shadow: "Vis skygger" | ||||||
|     rounded-corners: "Vis afrundede hjørner" |     rounded-corners: "Vis afrundede hjørner" | ||||||
|     circle-icons: "Anvend cykliske ikoner" |     circle-icons: "Anvend cykliske avatar" | ||||||
|     contrasted-acct: "Tilføj kontrast til brugerkontoen" |     contrasted-acct: "Tilføj kontrast til brugerkontoen" | ||||||
|     wallpaper: "Baggrundsbillede" |     wallpaper: "Baggrundsbillede" | ||||||
|     choose-wallpaper: "Vælg en baggrund" |     choose-wallpaper: "Vælg en baggrund" | ||||||
| @@ -673,7 +680,7 @@ common/views/components/profile-editor.vue: | |||||||
|   you-can-include-hashtags: "Du må gerne bruge hashtags i din profil beskrivelse" |   you-can-include-hashtags: "Du må gerne bruge hashtags i din profil beskrivelse" | ||||||
|   language: "Sprog" |   language: "Sprog" | ||||||
|   birthday: "Fødselsdag" |   birthday: "Fødselsdag" | ||||||
|   avatar: "Ikon" |   avatar: "Avatar" | ||||||
|   banner: "Banner" |   banner: "Banner" | ||||||
|   is-cat: "Denne konto er en Kat" |   is-cat: "Denne konto er en Kat" | ||||||
|   is-bot: "Denne konto er en Bot" |   is-bot: "Denne konto er en Bot" | ||||||
| @@ -1606,6 +1613,8 @@ mobile/views/pages/search.vue: | |||||||
|   not-found: "Ingen poster fundet for \"{q}\"" |   not-found: "Ingen poster fundet for \"{q}\"" | ||||||
| mobile/views/pages/selectdrive.vue: | mobile/views/pages/selectdrive.vue: | ||||||
|   select-file: "Vælg fil(er)" |   select-file: "Vælg fil(er)" | ||||||
|  | mobile/views/pages/notifications.vue: | ||||||
|  |   notifications: "Notifikationer" | ||||||
| mobile/views/pages/settings.vue: | mobile/views/pages/settings.vue: | ||||||
|   signed-in-as: "Logget ind som {}" |   signed-in-as: "Logget ind som {}" | ||||||
| mobile/views/pages/user.vue: | mobile/views/pages/user.vue: | ||||||
| @@ -1901,3 +1910,10 @@ pages: | |||||||
|     enviromentVariables: "Miljø variabel" |     enviromentVariables: "Miljø variabel" | ||||||
|     pageVariables: "Side element" |     pageVariables: "Side element" | ||||||
|     argVariables: "Input slot" |     argVariables: "Input slot" | ||||||
|  | room: | ||||||
|  |   translate: "Flyt" | ||||||
|  |   save: "Gem" | ||||||
|  |   saved: "Gemt" | ||||||
|  |   furnitures: | ||||||
|  |     moon: "Måne" | ||||||
|  |     bin: "Skraldespand" | ||||||
|   | |||||||
| @@ -35,6 +35,10 @@ common: | |||||||
|   signout: "Ausloggen" |   signout: "Ausloggen" | ||||||
|   reload-to-apply-the-setting: "Die Seite muss zum Übernehmen dieser Einstellung aktualisiert werden. Soll die Seite jetzt neu geladen werden?" |   reload-to-apply-the-setting: "Die Seite muss zum Übernehmen dieser Einstellung aktualisiert werden. Soll die Seite jetzt neu geladen werden?" | ||||||
|   fetching-as-ap-object: "Hole Daten…" |   fetching-as-ap-object: "Hole Daten…" | ||||||
|  |   delete-confirm: "Diesen Beitrag löschen?" | ||||||
|  |   notification-types: | ||||||
|  |     reply: "Antworten" | ||||||
|  |     renote: "Anmerkung" | ||||||
|   got-it: "Verstanden!" |   got-it: "Verstanden!" | ||||||
|   customization-tips: |   customization-tips: | ||||||
|     title: "Anpassung-Tipps" |     title: "Anpassung-Tipps" | ||||||
| @@ -203,7 +207,7 @@ common: | |||||||
|     deck-column-width-wide: "Sehr breit" |     deck-column-width-wide: "Sehr breit" | ||||||
|     use-shadow: "Nutze Schatten" |     use-shadow: "Nutze Schatten" | ||||||
|     rounded-corners: "Abgerundete Ecken" |     rounded-corners: "Abgerundete Ecken" | ||||||
|     circle-icons: "Kreisförmige Icons" |     circle-icons: "Kreisförmige Avatar" | ||||||
|     contrasted-acct: "Nutzernamen kontrastreicher darstellen" |     contrasted-acct: "Nutzernamen kontrastreicher darstellen" | ||||||
|     wallpaper: "Hintergrund" |     wallpaper: "Hintergrund" | ||||||
|     choose-wallpaper: "Hintergrund auswählen" |     choose-wallpaper: "Hintergrund auswählen" | ||||||
| @@ -601,6 +605,7 @@ common/views/widgets/memo.vue: | |||||||
|   save: "Speichern" |   save: "Speichern" | ||||||
| desktop: | desktop: | ||||||
|   banner: "Banner" |   banner: "Banner" | ||||||
|  |   avatar: "Avatar" | ||||||
|   unable-to-process: "Der Vorgang konnte nicht abgeschlossen werden" |   unable-to-process: "Der Vorgang konnte nicht abgeschlossen werden" | ||||||
| desktop/views/components/activity.chart.vue: | desktop/views/components/activity.chart.vue: | ||||||
|   total: "Schwarz ... komplett" |   total: "Schwarz ... komplett" | ||||||
| @@ -876,6 +881,8 @@ mobile/views/pages/note.vue: | |||||||
|   next: "Nächster Kommentar" |   next: "Nächster Kommentar" | ||||||
| mobile/views/pages/search.vue: | mobile/views/pages/search.vue: | ||||||
|   search: "Suchen" |   search: "Suchen" | ||||||
|  | mobile/views/pages/notifications.vue: | ||||||
|  |   notifications: "Benachrichtigungen" | ||||||
| mobile/views/pages/user/home.vue: | mobile/views/pages/user/home.vue: | ||||||
|   activity: "Aktivität" |   activity: "Aktivität" | ||||||
|   keywords: "Schlagwörter" |   keywords: "Schlagwörter" | ||||||
| @@ -940,3 +947,9 @@ pages: | |||||||
|         arg1: "Listen" |         arg1: "Listen" | ||||||
|     types: |     types: | ||||||
|       array: "Listen" |       array: "Listen" | ||||||
|  | room: | ||||||
|  |   save: "Speichern" | ||||||
|  |   saved: "Gespeichert" | ||||||
|  |   furnitures: | ||||||
|  |     moon: "Mond" | ||||||
|  |     bin: "Papierkorb" | ||||||
|   | |||||||
| @@ -30,12 +30,25 @@ common: | |||||||
|   customize-home: "Customize home layout" |   customize-home: "Customize home layout" | ||||||
|   featured-notes: "Featured notes" |   featured-notes: "Featured notes" | ||||||
|   dark-mode: "Dark Mode" |   dark-mode: "Dark Mode" | ||||||
|   signin: "Log In" |   signin: "Login" | ||||||
|   signup: "Sign up" |   signup: "Sign up" | ||||||
|   signout: "Logout" |   signout: "Logout" | ||||||
|   reload-to-apply-the-setting: "You'll need to reload the page to reflect this setting. Do you want to reload it now?" |   reload-to-apply-the-setting: "You'll need to reload the page to reflect this setting. Do you want to reload it now?" | ||||||
|   fetching-as-ap-object: "Inquiring to union" |   fetching-as-ap-object: "Inquiring to fediverse" | ||||||
|   unfollow-confirm: "Do you want to unfollow {name}?" |   unfollow-confirm: "Do you want to unfollow {name}?" | ||||||
|  |   delete-confirm: "Are you sure you want to delete this post?" | ||||||
|  |   signin-required: "Please login" | ||||||
|  |   notification-type: "Notification Type" | ||||||
|  |   notification-types: | ||||||
|  |     all: "All" | ||||||
|  |     pollVote: "Votes" | ||||||
|  |     follow: "Following" | ||||||
|  |     receiveFollowRequest: "Follow requests" | ||||||
|  |     reply: "Reply" | ||||||
|  |     quote: "Quote" | ||||||
|  |     renote: "Renote" | ||||||
|  |     mention: "Mentions" | ||||||
|  |     reaction: "Reaction" | ||||||
|   got-it: "Got it!" |   got-it: "Got it!" | ||||||
|   customization-tips: |   customization-tips: | ||||||
|     title: "Customization tips" |     title: "Customization tips" | ||||||
| @@ -120,6 +133,7 @@ common: | |||||||
|     geolocation-alert: "Your device does not provide location services" |     geolocation-alert: "Your device does not provide location services" | ||||||
|     error: "Error" |     error: "Error" | ||||||
|     enter-username: "Please enter username" |     enter-username: "Please enter username" | ||||||
|  |     specified-recipient: "Recipient" | ||||||
|     add-visible-user: "Add a user" |     add-visible-user: "Add a user" | ||||||
|     cw-placeholder: "Comments for the post (optional)" |     cw-placeholder: "Comments for the post (optional)" | ||||||
|     username-prompt: "Please enter username" |     username-prompt: "Please enter username" | ||||||
| @@ -202,6 +216,7 @@ common: | |||||||
|     use-avatar-reversi-stones: "Use avatar as a stone in reversi" |     use-avatar-reversi-stones: "Use avatar as a stone in reversi" | ||||||
|     disable-animated-mfm: "Disable animated texts in a post" |     disable-animated-mfm: "Disable animated texts in a post" | ||||||
|     disable-showing-animated-images: "Do not play animated images" |     disable-showing-animated-images: "Do not play animated images" | ||||||
|  |     enable-quick-notification-view: "Enable Quick Notification View" | ||||||
|     suggest-recent-hashtags: "Show recent popular hashtags on the post form" |     suggest-recent-hashtags: "Show recent popular hashtags on the post form" | ||||||
|     always-show-nsfw: "Always show NSFW contents" |     always-show-nsfw: "Always show NSFW contents" | ||||||
|     always-mark-nsfw: "Always mark posts with media attachments as NSFW" |     always-mark-nsfw: "Always mark posts with media attachments as NSFW" | ||||||
| @@ -232,7 +247,7 @@ common: | |||||||
|     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" | ||||||
|     circle-icons: "Use circular icons" |     circle-icons: "Use circular avatar icon" | ||||||
|     contrasted-acct: "Add contrast to user account" |     contrasted-acct: "Add contrast to user account" | ||||||
|     wallpaper: "Background image" |     wallpaper: "Background image" | ||||||
|     choose-wallpaper: "Choose a background" |     choose-wallpaper: "Choose a background" | ||||||
| @@ -282,6 +297,16 @@ common: | |||||||
|     saved: "Saved" |     saved: "Saved" | ||||||
|     home-profile: "Home profile" |     home-profile: "Home profile" | ||||||
|     deck-profile: "Deck profile" |     deck-profile: "Deck profile" | ||||||
|  |     room: "Room" | ||||||
|  |     _room: | ||||||
|  |       graphicsQuality: "Graphics Quality" | ||||||
|  |       _graphicsQuality: | ||||||
|  |         ultra: "Ultra" | ||||||
|  |         high: "High" | ||||||
|  |         medium: "Medium" | ||||||
|  |         low: "Low" | ||||||
|  |         cheep: "Cheep" | ||||||
|  |       useOrthographicCamera: "Use Orthographic Camera" | ||||||
|   search: "Search" |   search: "Search" | ||||||
|   delete: "Delete" |   delete: "Delete" | ||||||
|   loading: "Loading" |   loading: "Loading" | ||||||
| @@ -366,6 +391,9 @@ common/views/pages/explore.vue: | |||||||
|   federated: "From the fediverse" |   federated: "From the fediverse" | ||||||
|   explore: "Explore {host}" |   explore: "Explore {host}" | ||||||
|   users-info: "Currently, {users} users are registered here" |   users-info: "Currently, {users} users are registered here" | ||||||
|  | common/views/components/reactions-viewer.details.vue: | ||||||
|  |   few-users: "{users} reacted with {reaction}" | ||||||
|  |   many-users: "{users}, and {omitted} more reacted with {reaction}" | ||||||
| common/views/components/url-preview.vue: | common/views/components/url-preview.vue: | ||||||
|   enable-player: "Enable playback" |   enable-player: "Enable playback" | ||||||
|   disable-player: "Close the player" |   disable-player: "Close the player" | ||||||
| @@ -531,6 +559,8 @@ common/views/components/note-menu.vue: | |||||||
|   unpin: "Unpin" |   unpin: "Unpin" | ||||||
|   delete: "Delete" |   delete: "Delete" | ||||||
|   delete-confirm: "Are you sure you want to delete this post?" |   delete-confirm: "Are you sure you want to delete this post?" | ||||||
|  |   delete-and-edit: "Delete and Edit" | ||||||
|  |   delete-and-edit-confirm: "Are you sure you want to delete this note and edit it? You will lose all reactions, renotes and replies to it." | ||||||
|   remote: "Show original note" |   remote: "Show original note" | ||||||
|   pin-limit-exceeded: "You can't pin any more posts." |   pin-limit-exceeded: "You can't pin any more posts." | ||||||
| common/views/components/user-menu.vue: | common/views/components/user-menu.vue: | ||||||
| @@ -618,7 +648,7 @@ common/views/components/signin.vue: | |||||||
|   signin-with-twitter: "Log in with Twitter" |   signin-with-twitter: "Log in with Twitter" | ||||||
|   signin-with-github: "Sign in with GitHub" |   signin-with-github: "Sign in with GitHub" | ||||||
|   signin-with-discord: "Sign in with Discord" |   signin-with-discord: "Sign in with Discord" | ||||||
|   login-failed: "Logging in has failed. Make sure you have entered the correct username and password." |   login-failed: "Unable to log in. The username or password you entered is incorrect." | ||||||
|   tap-key: "Click on the Security Key to log in" |   tap-key: "Click on the Security Key to log in" | ||||||
|   enter-2fa-code: "Enter your verification code" |   enter-2fa-code: "Enter your verification code" | ||||||
| common/views/components/signup.vue: | common/views/components/signup.vue: | ||||||
| @@ -726,8 +756,8 @@ common/views/components/profile-editor.vue: | |||||||
|   uploading: "Uploading" |   uploading: "Uploading" | ||||||
|   upload-failed: "Failed to upload" |   upload-failed: "Failed to upload" | ||||||
|   unable-to-process: "The operation could not be completed." |   unable-to-process: "The operation could not be completed." | ||||||
|   avatar-not-an-image: "The file specified as an avatar is not an image" |   avatar-not-an-image: "The file you specified as an avatar is not an image" | ||||||
|   banner-not-an-image: "The file specified as a banner is not an image" |   banner-not-an-image: "The file you specified as a banner is not an image" | ||||||
|   email: "Email settings" |   email: "Email settings" | ||||||
|   email-address: "Email Address" |   email-address: "Email Address" | ||||||
|   email-verified: "Your email has been verified." |   email-verified: "Your email has been verified." | ||||||
| @@ -901,7 +931,7 @@ desktop/views/components/drive.file.vue: | |||||||
|     copy-url: "Copy URL" |     copy-url: "Copy URL" | ||||||
|     download: "Download" |     download: "Download" | ||||||
|     else-files: "Other" |     else-files: "Other" | ||||||
|     set-as-avatar: "Set as an avatar" |     set-as-avatar: "Set as avatar" | ||||||
|     set-as-banner: "Set as a banner" |     set-as-banner: "Set as a banner" | ||||||
|     open-in-app: "Open in app" |     open-in-app: "Open in app" | ||||||
|     add-app: "Add app" |     add-app: "Add app" | ||||||
| @@ -1119,6 +1149,7 @@ desktop/views/components/ui.header.account.vue: | |||||||
|   groups: "Groups" |   groups: "Groups" | ||||||
|   follow-requests: "Follow requests" |   follow-requests: "Follow requests" | ||||||
|   admin: "Admin" |   admin: "Admin" | ||||||
|  |   room: "Room" | ||||||
| desktop/views/components/ui.header.nav.vue: | desktop/views/components/ui.header.nav.vue: | ||||||
|   game: "Games" |   game: "Games" | ||||||
| desktop/views/components/ui.header.notifications.vue: | desktop/views/components/ui.header.notifications.vue: | ||||||
| @@ -1698,6 +1729,8 @@ mobile/views/pages/search.vue: | |||||||
|   not-found: "No posts were found for '{q}'" |   not-found: "No posts were found for '{q}'" | ||||||
| mobile/views/pages/selectdrive.vue: | mobile/views/pages/selectdrive.vue: | ||||||
|   select-file: "Choose files" |   select-file: "Choose files" | ||||||
|  | mobile/views/pages/notifications.vue: | ||||||
|  |   notifications: "Notifications" | ||||||
| mobile/views/pages/settings.vue: | mobile/views/pages/settings.vue: | ||||||
|   signed-in-as: "Signed in as {}" |   signed-in-as: "Signed in as {}" | ||||||
| mobile/views/pages/user.vue: | mobile/views/pages/user.vue: | ||||||
| @@ -1788,6 +1821,7 @@ pages: | |||||||
|   read-page: "Viewing the source" |   read-page: "Viewing the source" | ||||||
|   page-created: "Created the page!" |   page-created: "Created the page!" | ||||||
|   page-updated: "Updated the page" |   page-updated: "Updated the page" | ||||||
|  |   name-already-exists: "The specified page name already exists" | ||||||
|   are-you-sure-delete: "Do you want to delete this page?" |   are-you-sure-delete: "Do you want to delete this page?" | ||||||
|   page-deleted: "The page has been deleted" |   page-deleted: "The page has been deleted" | ||||||
|   edit-this-page: "Edit this page" |   edit-this-page: "Edit this page" | ||||||
| @@ -2056,3 +2090,64 @@ pages: | |||||||
|     enviromentVariables: "Environment variable" |     enviromentVariables: "Environment variable" | ||||||
|     pageVariables: "Page element" |     pageVariables: "Page element" | ||||||
|     argVariables: "Input slot" |     argVariables: "Input slot" | ||||||
|  | room: | ||||||
|  |   add-furniture: "Place furniture" | ||||||
|  |   translate: "Move" | ||||||
|  |   rotate: "Rotate" | ||||||
|  |   exit: "Deselect" | ||||||
|  |   remove: "Remove" | ||||||
|  |   save: "Save" | ||||||
|  |   saved: "Saved" | ||||||
|  |   clear: "Remove All" | ||||||
|  |   clear-confirm: "Are you sure to remove all furnitures in your room?" | ||||||
|  |   leave-confirm: "There are unsaved changes. Do you really want to leave?" | ||||||
|  |   chooseImage: "Select an image" | ||||||
|  |   room-type: "Room type" | ||||||
|  |   carpet-color: "Color of carpet" | ||||||
|  |   rooms: | ||||||
|  |     default: "Default" | ||||||
|  |     washitsu: "Japanese-style" | ||||||
|  |   furnitures: | ||||||
|  |     milk: "Milk carton" | ||||||
|  |     bed: "Bed" | ||||||
|  |     low-table: "Low Table" | ||||||
|  |     desk: "Desk" | ||||||
|  |     chair: "Chair" | ||||||
|  |     chair2: "Chair 2" | ||||||
|  |     fan: "Fan" | ||||||
|  |     pc: "Computer" | ||||||
|  |     plant: "Houseplant" | ||||||
|  |     plant2: "Houseplant 2" | ||||||
|  |     eraser: "Eraser" | ||||||
|  |     pencil: "Pencil" | ||||||
|  |     pudding: "Pudding" | ||||||
|  |     cardboard-box: "Cardboard Box" | ||||||
|  |     cardboard-box2: "Cardboard Box 2" | ||||||
|  |     cardboard-box3: "Cardboard Box 3" | ||||||
|  |     book: "Book" | ||||||
|  |     book2: "Book 2" | ||||||
|  |     piano: "Piano" | ||||||
|  |     facial-tissue: "Facial tissue" | ||||||
|  |     server: "Servers" | ||||||
|  |     moon: "Moon" | ||||||
|  |     corkboard: "Cork board" | ||||||
|  |     mousepad: "Mousepad" | ||||||
|  |     monitor: "Monitor" | ||||||
|  |     keyboard: "Keyboard" | ||||||
|  |     carpet-stripe: "Carpet (stripe)" | ||||||
|  |     mat: "Mat" | ||||||
|  |     color-box: "Bookshelf" | ||||||
|  |     wall-clock: "Wall clock" | ||||||
|  |     photoframe: "Picture frame" | ||||||
|  |     cube: "Cube" | ||||||
|  |     tv: "TV" | ||||||
|  |     pinguin: "Penguin" | ||||||
|  |     rubik-cube: "Rubik's Cube" | ||||||
|  |     poster-h: "Poster (Horizontal)" | ||||||
|  |     poster-v: "Poster (Vertical)" | ||||||
|  |     sofa: "Sofa" | ||||||
|  |     spiral: "Spiral Staircase" | ||||||
|  |     bin: "Waste bin" | ||||||
|  |     cup-noodle: "Cup noodle" | ||||||
|  |     holo-display: "Holographic display" | ||||||
|  |     energy-drink: "Energy drink" | ||||||
|   | |||||||
| @@ -31,6 +31,11 @@ common: | |||||||
|   signin: "Iniciar sesión" |   signin: "Iniciar sesión" | ||||||
|   signup: "¡Regístrate!" |   signup: "¡Regístrate!" | ||||||
|   signout: "Cerrar sesión" |   signout: "Cerrar sesión" | ||||||
|  |   delete-confirm: "¿Seguro que quieres borrar la publicación?" | ||||||
|  |   notification-types: | ||||||
|  |     all: "Todo" | ||||||
|  |     reply: "Responder" | ||||||
|  |     renote: "Volver a publicar" | ||||||
|   got-it: "¡Listo!" |   got-it: "¡Listo!" | ||||||
|   customization-tips: |   customization-tips: | ||||||
|     title: "Consejos de personalización" |     title: "Consejos de personalización" | ||||||
| @@ -172,7 +177,7 @@ common: | |||||||
|     deck-column-width-wide: "Ancho" |     deck-column-width-wide: "Ancho" | ||||||
|     use-shadow: "Usar sombras en la Interfaz de Usuario" |     use-shadow: "Usar sombras en la Interfaz de Usuario" | ||||||
|     rounded-corners: "Esquinas redondeadas en la Interfaz de Usuario" |     rounded-corners: "Esquinas redondeadas en la Interfaz de Usuario" | ||||||
|     circle-icons: "Usar iconos circulares" |     circle-icons: "Usar avatar circulares" | ||||||
|     contrasted-acct: "Añadir contraste al nombre de usuario" |     contrasted-acct: "Añadir contraste al nombre de usuario" | ||||||
|     wallpaper: "Fondo de pantalla" |     wallpaper: "Fondo de pantalla" | ||||||
|     choose-wallpaper: "Escoge un fondo de pantalla" |     choose-wallpaper: "Escoge un fondo de pantalla" | ||||||
| @@ -1089,6 +1094,8 @@ mobile/views/pages/games/reversi.vue: | |||||||
|   reversi: "Reversi" |   reversi: "Reversi" | ||||||
| mobile/views/pages/search.vue: | mobile/views/pages/search.vue: | ||||||
|   search: "Buscar" |   search: "Buscar" | ||||||
|  | mobile/views/pages/notifications.vue: | ||||||
|  |   notifications: "Notificaciones" | ||||||
| mobile/views/pages/user/home.vue: | mobile/views/pages/user/home.vue: | ||||||
|   activity: "Actividad" |   activity: "Actividad" | ||||||
| mobile/views/pages/user/home.photos.vue: | mobile/views/pages/user/home.photos.vue: | ||||||
| @@ -1128,3 +1135,9 @@ pages: | |||||||
|         arg1: "Listas" |         arg1: "Listas" | ||||||
|     types: |     types: | ||||||
|       array: "Listas" |       array: "Listas" | ||||||
|  | room: | ||||||
|  |   save: "Guardar" | ||||||
|  |   saved: "Guardado" | ||||||
|  |   furnitures: | ||||||
|  |     moon: "Luna" | ||||||
|  |     bin: "Papelera" | ||||||
|   | |||||||
| @@ -35,6 +35,19 @@ common: | |||||||
|   signout: "Se déconnecter" |   signout: "Se déconnecter" | ||||||
|   reload-to-apply-the-setting: "Le rechargement de la page est nécessaire pour appliquer ces paramètres. Désirez-vous la recharger maintenant ?" |   reload-to-apply-the-setting: "Le rechargement de la page est nécessaire pour appliquer ces paramètres. Désirez-vous la recharger maintenant ?" | ||||||
|   unfollow-confirm: "Désirez-vous vous désabonner de {name} ?" |   unfollow-confirm: "Désirez-vous vous désabonner de {name} ?" | ||||||
|  |   delete-confirm: "Supprimer cette publication ?" | ||||||
|  |   signin-required: "Veuillez vous connecter" | ||||||
|  |   notification-type: "Type de notification" | ||||||
|  |   notification-types: | ||||||
|  |     all: "Tout" | ||||||
|  |     pollVote: "Sondage" | ||||||
|  |     follow: "Abonnements" | ||||||
|  |     receiveFollowRequest: "Demandes d’abonnements" | ||||||
|  |     reply: "Répondre" | ||||||
|  |     quote: "Cité par" | ||||||
|  |     renote: "Republier" | ||||||
|  |     mention: "Mentions" | ||||||
|  |     reaction: "Réaction" | ||||||
|   got-it: "J’ai compris !" |   got-it: "J’ai compris !" | ||||||
|   customization-tips: |   customization-tips: | ||||||
|     title: "Conseils de personnalisation" |     title: "Conseils de personnalisation" | ||||||
| @@ -223,7 +236,7 @@ common: | |||||||
|     deck-column-width-wide: "Large" |     deck-column-width-wide: "Large" | ||||||
|     use-shadow: "Utiliser les ombres dans l'interface utilisateur" |     use-shadow: "Utiliser les ombres dans l'interface utilisateur" | ||||||
|     rounded-corners: "Coins arrondis de l'interface utilisateur" |     rounded-corners: "Coins arrondis de l'interface utilisateur" | ||||||
|     circle-icons: "Utiliser des icônes circulaires" |     circle-icons: "Utiliser des avatar circulaires" | ||||||
|     contrasted-acct: "Ajouter du contraste au nom de l’utilisateur" |     contrasted-acct: "Ajouter du contraste au nom de l’utilisateur" | ||||||
|     wallpaper: "Image du fond d'écran" |     wallpaper: "Image du fond d'écran" | ||||||
|     choose-wallpaper: "Sélectionner un fond d'écran" |     choose-wallpaper: "Sélectionner un fond d'écran" | ||||||
| @@ -271,6 +284,18 @@ common: | |||||||
|     sync: "Synchroniser" |     sync: "Synchroniser" | ||||||
|     save: "Enregistrer" |     save: "Enregistrer" | ||||||
|     saved: "enregistré" |     saved: "enregistré" | ||||||
|  |     home-profile: "Profil principal" | ||||||
|  |     deck-profile: "Profil deck" | ||||||
|  |     room: "Pièce" | ||||||
|  |     _room: | ||||||
|  |       graphicsQuality: "Qualité des graphismes" | ||||||
|  |       _graphicsQuality: | ||||||
|  |         ultra: "Très élevée" | ||||||
|  |         high: "Élevée" | ||||||
|  |         medium: "Moyenne" | ||||||
|  |         low: "Basse" | ||||||
|  |         cheep: "Minimale" | ||||||
|  |       useOrthographicCamera: "Utiliser une caméra orthographique" | ||||||
|   search: "Recherche" |   search: "Recherche" | ||||||
|   delete: "Supprimer" |   delete: "Supprimer" | ||||||
|   loading: "Chargement en cours …" |   loading: "Chargement en cours …" | ||||||
| @@ -392,6 +417,7 @@ common/views/components/games/reversi/reversi.room.vue: | |||||||
|   black-or-white: "Noirs/Blancs" |   black-or-white: "Noirs/Blancs" | ||||||
|   black-is: "{} Noirs" |   black-is: "{} Noirs" | ||||||
|   rules: "Règles" |   rules: "Règles" | ||||||
|  |   is-llotheo: "Celui ou celle qui a le moins de pièces gagne (Llotheo)" | ||||||
|   looped-map: "Carte en boucle" |   looped-map: "Carte en boucle" | ||||||
|   can-put-everywhere: "Peut poser partout" |   can-put-everywhere: "Peut poser partout" | ||||||
|   settings-of-the-bot: "Configuration du bot" |   settings-of-the-bot: "Configuration du bot" | ||||||
| @@ -519,6 +545,7 @@ common/views/components/note-menu.vue: | |||||||
|   unpin: "Désépingler" |   unpin: "Désépingler" | ||||||
|   delete: "Supprimer" |   delete: "Supprimer" | ||||||
|   delete-confirm: "Supprimer cette publication ?" |   delete-confirm: "Supprimer cette publication ?" | ||||||
|  |   delete-and-edit: "Effacer et éditer" | ||||||
|   remote: "Afficher la note originale" |   remote: "Afficher la note originale" | ||||||
| common/views/components/user-menu.vue: | common/views/components/user-menu.vue: | ||||||
|   mention: "Mention" |   mention: "Mention" | ||||||
| @@ -587,6 +614,7 @@ common/views/components/emoji-picker.vue: | |||||||
|   flags: "Drapeaux" |   flags: "Drapeaux" | ||||||
| common/views/components/settings/app-type.vue: | common/views/components/settings/app-type.vue: | ||||||
|   title: "Mode" |   title: "Mode" | ||||||
|  |   intro: "Vous pouvez choisir, si vous voulez utiliser la disposition de bureau ou mobile." | ||||||
|   choices: |   choices: | ||||||
|     auto: "Choisir la disposition automatiquement" |     auto: "Choisir la disposition automatiquement" | ||||||
|     desktop: "Toujours utiliser la disposition de bureau" |     desktop: "Toujours utiliser la disposition de bureau" | ||||||
| @@ -709,6 +737,8 @@ common/views/components/profile-editor.vue: | |||||||
|   uploading: "En cours d’envoi …" |   uploading: "En cours d’envoi …" | ||||||
|   upload-failed: "Échec de l'envoi" |   upload-failed: "Échec de l'envoi" | ||||||
|   unable-to-process: "L'opération n'a pas pu être complétée" |   unable-to-process: "L'opération n'a pas pu être complétée" | ||||||
|  |   avatar-not-an-image: "Le fichier sélectionné pour votre avatar n'est pas une image" | ||||||
|  |   banner-not-an-image: "Le fichier sélectionné pour votre bannière n'est pas une image" | ||||||
|   email: "Paramètres de messagerie" |   email: "Paramètres de messagerie" | ||||||
|   email-address: "Adresse de courrier électronique" |   email-address: "Adresse de courrier électronique" | ||||||
|   email-verified: "L’adresse du courrier électronique a été vérifiée." |   email-verified: "L’adresse du courrier électronique a été vérifiée." | ||||||
| @@ -894,6 +924,8 @@ desktop/views/components/drive.folder.vue: | |||||||
|   unable-to-process: "L'opération n'a pas pu être complétée" |   unable-to-process: "L'opération n'a pas pu être complétée" | ||||||
|   circular-reference-detected: "Le dossier de destination est un sous-dossier du dossier que vous souhaitez déplacer." |   circular-reference-detected: "Le dossier de destination est un sous-dossier du dossier que vous souhaitez déplacer." | ||||||
|   unhandled-error: "Erreur inconnue" |   unhandled-error: "Erreur inconnue" | ||||||
|  |   unable-to-delete: "Ne peut pas être supprimé" | ||||||
|  |   has-child-files-or-folders: "Ce dossier n'est pas vide, il ne peut pas être supprimé" | ||||||
|   contextmenu: |   contextmenu: | ||||||
|     move-to-this-folder: "Déplacer dans ce dossier" |     move-to-this-folder: "Déplacer dans ce dossier" | ||||||
|     show-in-new-window: "Ouvrir dans une nouvelle fenêtre" |     show-in-new-window: "Ouvrir dans une nouvelle fenêtre" | ||||||
| @@ -1007,6 +1039,7 @@ desktop/views/components/settings.2fa.vue: | |||||||
|   success: "Sauvegarde des paramètres avec succès !" |   success: "Sauvegarde des paramètres avec succès !" | ||||||
|   failed: "L’opération a échoué. Veuillez vous assurer que le jeton a été saisi correctement." |   failed: "L’opération a échoué. Veuillez vous assurer que le jeton a été saisi correctement." | ||||||
|   info: "À partir de maintenant, à chaque fois que vous vous connectez entrez votre mot de passe ainsi que le jeton généré sur votre appareil." |   info: "À partir de maintenant, à chaque fois que vous vous connectez entrez votre mot de passe ainsi que le jeton généré sur votre appareil." | ||||||
|  |   totp-header: "Application d'authentification" | ||||||
|   security-key-header: "Clé de sécurité" |   security-key-header: "Clé de sécurité" | ||||||
|   last-used: "Dernière utilisation :" |   last-used: "Dernière utilisation :" | ||||||
|   activate-key: "Cliquez pour activer la clé de sécurité" |   activate-key: "Cliquez pour activer la clé de sécurité" | ||||||
| @@ -1038,6 +1071,7 @@ common/views/components/drive-settings.vue: | |||||||
|   max: "Maximale" |   max: "Maximale" | ||||||
|   in-use: "utilisé" |   in-use: "utilisé" | ||||||
|   stats: "Statistiques" |   stats: "Statistiques" | ||||||
|  |   default-upload-folder: "Emplacement par défaut du dossier de transfert" | ||||||
|   default-upload-folder-name: "Dossier·s" |   default-upload-folder-name: "Dossier·s" | ||||||
|   change-default-upload-folder: "Changer de dossier" |   change-default-upload-folder: "Changer de dossier" | ||||||
| common/views/components/mute-and-block.vue: | common/views/components/mute-and-block.vue: | ||||||
| @@ -1093,6 +1127,7 @@ desktop/views/components/ui.header.account.vue: | |||||||
|   groups: "Groupes" |   groups: "Groupes" | ||||||
|   follow-requests: "Demandes d’abonnement" |   follow-requests: "Demandes d’abonnement" | ||||||
|   admin: "Admin" |   admin: "Admin" | ||||||
|  |   room: "Pièce" | ||||||
| desktop/views/components/ui.header.nav.vue: | desktop/views/components/ui.header.nav.vue: | ||||||
|   game: "Jeux" |   game: "Jeux" | ||||||
| desktop/views/components/ui.header.notifications.vue: | desktop/views/components/ui.header.notifications.vue: | ||||||
| @@ -1146,11 +1181,13 @@ admin/views/queue.vue: | |||||||
|     deliver: "Délivrées" |     deliver: "Délivrées" | ||||||
|     inbox: "Reçues" |     inbox: "Reçues" | ||||||
|     db: "Base de données" |     db: "Base de données" | ||||||
|  |     objectStorage: "Stockage d'objets" | ||||||
|   state: "État" |   state: "État" | ||||||
|   states: |   states: | ||||||
|     active: "en cours" |     active: "en cours" | ||||||
|     delayed: "Programmé" |     delayed: "Programmé" | ||||||
|     waiting: "En file d'attente" |     waiting: "En file d'attente" | ||||||
|  |   result-is-truncated: "Le résultat est tronqué" | ||||||
|   other-queues: "Autres files d’attente" |   other-queues: "Autres files d’attente" | ||||||
| admin/views/logs.vue: | admin/views/logs.vue: | ||||||
|   logs: "Journaux" |   logs: "Journaux" | ||||||
| @@ -1183,12 +1220,14 @@ admin/views/instance.vue: | |||||||
|   languages-desc: "Vous pouvez en définir plus d’une, séparées par des espaces." |   languages-desc: "Vous pouvez en définir plus d’une, séparées par des espaces." | ||||||
|   tos-url: "URL des conditions d'utilisation" |   tos-url: "URL des conditions d'utilisation" | ||||||
|   repository-url: "URL du dépôt" |   repository-url: "URL du dépôt" | ||||||
|  |   feedback-url: "URL pour les commentaires" | ||||||
|   maintainer-config: "Informations de l’administrateur" |   maintainer-config: "Informations de l’administrateur" | ||||||
|   maintainer-name: "Nom de l’administrateur" |   maintainer-name: "Nom de l’administrateur" | ||||||
|   maintainer-email: "Contact administratif" |   maintainer-email: "Contact administratif" | ||||||
|   advanced-config: "Autres réglages" |   advanced-config: "Autres réglages" | ||||||
|   note-and-tl: "Notes et fils" |   note-and-tl: "Notes et fils" | ||||||
|   drive-config: "Paramètres du lecteur" |   drive-config: "Paramètres du lecteur" | ||||||
|  |   use-object-storage: "Utiliser le stockage d'objets" | ||||||
|   object-storage-base-url: "URL" |   object-storage-base-url: "URL" | ||||||
|   object-storage-prefix: "Préfixe" |   object-storage-prefix: "Préfixe" | ||||||
|   object-storage-endpoint: "Point de terminaison" |   object-storage-endpoint: "Point de terminaison" | ||||||
| @@ -1366,6 +1405,7 @@ admin/views/moderators.vue: | |||||||
|     title: "Journaux" |     title: "Journaux" | ||||||
|     moderator: "Modérateurs" |     moderator: "Modérateurs" | ||||||
|     type: "Actions" |     type: "Actions" | ||||||
|  |     at: "Date de modification" | ||||||
|     info: "Informations" |     info: "Informations" | ||||||
| admin/views/emoji.vue: | admin/views/emoji.vue: | ||||||
|   add-emoji: |   add-emoji: | ||||||
| @@ -1643,6 +1683,8 @@ mobile/views/pages/search.vue: | |||||||
|   not-found: "Aucune publication trouvée pour « {q} »." |   not-found: "Aucune publication trouvée pour « {q} »." | ||||||
| mobile/views/pages/selectdrive.vue: | mobile/views/pages/selectdrive.vue: | ||||||
|   select-file: "Choisissez un fichier" |   select-file: "Choisissez un fichier" | ||||||
|  | mobile/views/pages/notifications.vue: | ||||||
|  |   notifications: "Notifications" | ||||||
| mobile/views/pages/settings.vue: | mobile/views/pages/settings.vue: | ||||||
|   signed-in-as: "Connecté·e en tant que {}" |   signed-in-as: "Connecté·e en tant que {}" | ||||||
| mobile/views/pages/user.vue: | mobile/views/pages/user.vue: | ||||||
| @@ -1970,3 +2012,60 @@ pages: | |||||||
|     emptySlot: "Slot vide" |     emptySlot: "Slot vide" | ||||||
|     enviromentVariables: "Variables d'environnement" |     enviromentVariables: "Variables d'environnement" | ||||||
|     pageVariables: "Élément de page" |     pageVariables: "Élément de page" | ||||||
|  | room: | ||||||
|  |   add-furniture: "Placer des meubles" | ||||||
|  |   translate: "Déplacer" | ||||||
|  |   rotate: "Tourner" | ||||||
|  |   remove: "Enlever" | ||||||
|  |   save: "Enregistrer" | ||||||
|  |   saved: "enregistré" | ||||||
|  |   clear: "Tout enlever" | ||||||
|  |   clear-confirm: "Désirez-vous enlever tout les meubles de votre chambre ?" | ||||||
|  |   leave-confirm: "Vous avez des modifications non-sauvegardées. Voulez-vous vraiment quitter ?" | ||||||
|  |   chooseImage: "Sélectionnez une image" | ||||||
|  |   room-type: "Type de chambre" | ||||||
|  |   carpet-color: "Couleur du tapis" | ||||||
|  |   rooms: | ||||||
|  |     default: "Par défaut" | ||||||
|  |     washitsu: "Style japonnais" | ||||||
|  |   furnitures: | ||||||
|  |     milk: "Lait en carton" | ||||||
|  |     bed: "Lit" | ||||||
|  |     low-table: "Table basse" | ||||||
|  |     desk: "Bureau" | ||||||
|  |     chair: "Chaise" | ||||||
|  |     chair2: "Chaise 2" | ||||||
|  |     fan: "Ventilateur" | ||||||
|  |     pc: "Ordinateur" | ||||||
|  |     plant: "Plante d’intérieur" | ||||||
|  |     plant2: "Plante d’intérieur 2" | ||||||
|  |     eraser: "Gomme" | ||||||
|  |     pencil: "Crayon" | ||||||
|  |     cardboard-box: "Boîte en carton" | ||||||
|  |     cardboard-box2: "Boîte en carton 2" | ||||||
|  |     cardboard-box3: "Boîte en carton 3" | ||||||
|  |     book: "Livre" | ||||||
|  |     book2: "Livre 2" | ||||||
|  |     piano: "Piano" | ||||||
|  |     server: "Serveurs" | ||||||
|  |     moon: "Lune" | ||||||
|  |     corkboard: "Tableau en liège" | ||||||
|  |     mousepad: "Tapis de souris" | ||||||
|  |     monitor: "Écran" | ||||||
|  |     keyboard: "Clavier" | ||||||
|  |     carpet-stripe: "Tapis (zébré)" | ||||||
|  |     color-box: "Étagère" | ||||||
|  |     wall-clock: "Horloge murale" | ||||||
|  |     photoframe: "Cadre photo" | ||||||
|  |     cube: "Cube" | ||||||
|  |     tv: "Téléviseur" | ||||||
|  |     pinguin: "Pingouin" | ||||||
|  |     rubik-cube: "Cube de Rubik" | ||||||
|  |     poster-h: "Affiche (horizontale)" | ||||||
|  |     poster-v: "Affiche (verticale)" | ||||||
|  |     sofa: "Canapé" | ||||||
|  |     spiral: "Escaliers en spirale" | ||||||
|  |     bin: "Corbeille" | ||||||
|  |     cup-noodle: "Bol de nouilles" | ||||||
|  |     holo-display: "Affichage holographique" | ||||||
|  |     energy-drink: "Boisson énergétique" | ||||||
|   | |||||||
| @@ -37,6 +37,19 @@ common: | |||||||
|   reload-to-apply-the-setting: "この設定を反映するにはページをリロードする必要があります。今すぐリロードしますか?" |   reload-to-apply-the-setting: "この設定を反映するにはページをリロードする必要があります。今すぐリロードしますか?" | ||||||
|   fetching-as-ap-object: "連合に照会中" |   fetching-as-ap-object: "連合に照会中" | ||||||
|   unfollow-confirm: "{name}さんをフォロー解除しますか?" |   unfollow-confirm: "{name}さんをフォロー解除しますか?" | ||||||
|  |   delete-confirm: "この投稿を削除しますか?" | ||||||
|  |   signin-required: "ログインしてください" | ||||||
|  |   notification-type: "通知の種類" | ||||||
|  |   notification-types: | ||||||
|  |     all: "すべて" | ||||||
|  |     pollVote: "投票" | ||||||
|  |     follow: "フォロー" | ||||||
|  |     receiveFollowRequest: "フォローリクエスト" | ||||||
|  |     reply: "返信" | ||||||
|  |     quote: "引用" | ||||||
|  |     renote: "Renote" | ||||||
|  |     mention: "言及" | ||||||
|  |     reaction: "リアクション" | ||||||
|  |  | ||||||
|   got-it: "わかった" |   got-it: "わかった" | ||||||
|   customization-tips: |   customization-tips: | ||||||
| @@ -126,6 +139,7 @@ common: | |||||||
|     geolocation-alert: "お使いの端末は位置情報に対応していません" |     geolocation-alert: "お使いの端末は位置情報に対応していません" | ||||||
|     error: "エラー" |     error: "エラー" | ||||||
|     enter-username: "ユーザー名を入力してください" |     enter-username: "ユーザー名を入力してください" | ||||||
|  |     specified-recipient: "宛先" | ||||||
|     add-visible-user: "ユーザーを追加" |     add-visible-user: "ユーザーを追加" | ||||||
|     cw-placeholder: "内容への注釈 (オプション)" |     cw-placeholder: "内容への注釈 (オプション)" | ||||||
|     username-prompt: "ユーザー名を入力してください" |     username-prompt: "ユーザー名を入力してください" | ||||||
| @@ -214,6 +228,7 @@ common: | |||||||
|     use-avatar-reversi-stones: "リバーシの石にアバターを使う" |     use-avatar-reversi-stones: "リバーシの石にアバターを使う" | ||||||
|     disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" |     disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" | ||||||
|     disable-showing-animated-images: "アニメーション画像を再生しない" |     disable-showing-animated-images: "アニメーション画像を再生しない" | ||||||
|  |     enable-quick-notification-view: "通知のクイックビューを有効にする" | ||||||
|     suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する" |     suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する" | ||||||
|     always-show-nsfw: "常に閲覧注意のメディアを表示する" |     always-show-nsfw: "常に閲覧注意のメディアを表示する" | ||||||
|     always-mark-nsfw: "常にメディアを閲覧注意として投稿" |     always-mark-nsfw: "常にメディアを閲覧注意として投稿" | ||||||
| @@ -244,7 +259,7 @@ common: | |||||||
|     deck-column-width-wide: "広" |     deck-column-width-wide: "広" | ||||||
|     use-shadow: "UIに影を使用" |     use-shadow: "UIに影を使用" | ||||||
|     rounded-corners: "UIの角を丸める" |     rounded-corners: "UIの角を丸める" | ||||||
|     circle-icons: "円形のアイコンを使用" |     circle-icons: "円形のアバターを使用" | ||||||
|     contrasted-acct: "ユーザー名にコントラストを付ける" |     contrasted-acct: "ユーザー名にコントラストを付ける" | ||||||
|     wallpaper: "壁紙" |     wallpaper: "壁紙" | ||||||
|     choose-wallpaper: "壁紙を選択" |     choose-wallpaper: "壁紙を選択" | ||||||
| @@ -294,6 +309,16 @@ common: | |||||||
|     saved: "保存しました" |     saved: "保存しました" | ||||||
|     home-profile: "ホームのプロファイル" |     home-profile: "ホームのプロファイル" | ||||||
|     deck-profile: "デッキのプロファイル" |     deck-profile: "デッキのプロファイル" | ||||||
|  |     room: "ルーム" | ||||||
|  |     _room: | ||||||
|  |       graphicsQuality: "グラフィックの品質" | ||||||
|  |       _graphicsQuality: | ||||||
|  |         ultra: "最高" | ||||||
|  |         high: "高" | ||||||
|  |         medium: "中" | ||||||
|  |         low: "低" | ||||||
|  |         cheep: "最低" | ||||||
|  |       useOrthographicCamera: "平行投影カメラを使用" | ||||||
|  |  | ||||||
|   search: "検索" |   search: "検索" | ||||||
|   delete: "削除" |   delete: "削除" | ||||||
| @@ -388,6 +413,10 @@ common/views/pages/explore.vue: | |||||||
|   explore: "{host}を探索" |   explore: "{host}を探索" | ||||||
|   users-info: "現在{users}ユーザーが登録されています" |   users-info: "現在{users}ユーザーが登録されています" | ||||||
|  |  | ||||||
|  | common/views/components/reactions-viewer.details.vue: | ||||||
|  |   few-users: "{users}が{reaction}をリアクション" | ||||||
|  |   many-users: "{users}と他{omitted}人が{reaction}をリアクション" | ||||||
|  |  | ||||||
| common/views/components/url-preview.vue: | common/views/components/url-preview.vue: | ||||||
|   enable-player: "プレイヤーを開く" |   enable-player: "プレイヤーを開く" | ||||||
|   disable-player: "プレイヤーを閉じる" |   disable-player: "プレイヤーを閉じる" | ||||||
| @@ -569,6 +598,8 @@ common/views/components/note-menu.vue: | |||||||
|   unpin: "ピン留め解除" |   unpin: "ピン留め解除" | ||||||
|   delete: "削除" |   delete: "削除" | ||||||
|   delete-confirm: "この投稿を削除しますか?" |   delete-confirm: "この投稿を削除しますか?" | ||||||
|  |   delete-and-edit: "削除して編集" | ||||||
|  |   delete-and-edit-confirm: "この投稿を削除してもう一度編集しますか?この投稿へのリアクション、Renote、返信も全て削除されます。" | ||||||
|   remote: "投稿元で見る" |   remote: "投稿元で見る" | ||||||
|   pin-limit-exceeded: "これ以上ピン留めできません。" |   pin-limit-exceeded: "これ以上ピン留めできません。" | ||||||
|  |  | ||||||
| @@ -769,7 +800,7 @@ common/views/components/profile-editor.vue: | |||||||
|   you-can-include-hashtags: "ハッシュタグを含めることができます。" |   you-can-include-hashtags: "ハッシュタグを含めることができます。" | ||||||
|   language: "言語" |   language: "言語" | ||||||
|   birthday: "誕生日" |   birthday: "誕生日" | ||||||
|   avatar: "アイコン" |   avatar: "アバター" | ||||||
|   banner: "バナー" |   banner: "バナー" | ||||||
|   is-cat: "このアカウントはCatです" |   is-cat: "このアカウントはCatです" | ||||||
|   is-bot: "このアカウントはBotです" |   is-bot: "このアカウントはBotです" | ||||||
| @@ -783,7 +814,7 @@ common/views/components/profile-editor.vue: | |||||||
|   uploading: "アップロード中" |   uploading: "アップロード中" | ||||||
|   upload-failed: "アップロードに失敗しました" |   upload-failed: "アップロードに失敗しました" | ||||||
|   unable-to-process: "操作を完了できません" |   unable-to-process: "操作を完了できません" | ||||||
|   avatar-not-an-image: "アイコンとして指定したファイルは画像ではありません" |   avatar-not-an-image: "アバターとして指定したファイルは画像ではありません" | ||||||
|   banner-not-an-image: "バナーとして指定したファイルは画像ではありません" |   banner-not-an-image: "バナーとして指定したファイルは画像ではありません" | ||||||
|   email: "メール設定" |   email: "メール設定" | ||||||
|   email-address: "メールアドレス" |   email-address: "メールアドレス" | ||||||
| @@ -973,7 +1004,7 @@ desktop/views/components/drive-window.vue: | |||||||
|   used: "使用中" |   used: "使用中" | ||||||
|  |  | ||||||
| desktop/views/components/drive.file.vue: | desktop/views/components/drive.file.vue: | ||||||
|   avatar: "アイコン" |   avatar: "アバター" | ||||||
|   banner: "バナー" |   banner: "バナー" | ||||||
|   nsfw: "閲覧注意" |   nsfw: "閲覧注意" | ||||||
|   contextmenu: |   contextmenu: | ||||||
| @@ -983,7 +1014,7 @@ desktop/views/components/drive.file.vue: | |||||||
|     copy-url: "URLをコピー" |     copy-url: "URLをコピー" | ||||||
|     download: "ダウンロード" |     download: "ダウンロード" | ||||||
|     else-files: "その他" |     else-files: "その他" | ||||||
|     set-as-avatar: "アイコンに設定" |     set-as-avatar: "アバターに設定" | ||||||
|     set-as-banner: "バナーに設定" |     set-as-banner: "バナーに設定" | ||||||
|     open-in-app: "アプリで開く" |     open-in-app: "アプリで開く" | ||||||
|     add-app: "アプリを追加" |     add-app: "アプリを追加" | ||||||
| @@ -1234,6 +1265,7 @@ desktop/views/components/ui.header.account.vue: | |||||||
|   groups: "グループ" |   groups: "グループ" | ||||||
|   follow-requests: "フォロー申請" |   follow-requests: "フォロー申請" | ||||||
|   admin: "管理" |   admin: "管理" | ||||||
|  |   room: "ルーム" | ||||||
|  |  | ||||||
| desktop/views/components/ui.header.nav.vue: | desktop/views/components/ui.header.nav.vue: | ||||||
|   game: "ゲーム" |   game: "ゲーム" | ||||||
| @@ -1883,6 +1915,9 @@ mobile/views/pages/search.vue: | |||||||
| mobile/views/pages/selectdrive.vue: | mobile/views/pages/selectdrive.vue: | ||||||
|   select-file: "ファイルを選択" |   select-file: "ファイルを選択" | ||||||
|  |  | ||||||
|  | mobile/views/pages/notifications.vue: | ||||||
|  |   notifications: "通知" | ||||||
|  |  | ||||||
| mobile/views/pages/settings.vue: | mobile/views/pages/settings.vue: | ||||||
|   signed-in-as: "{}としてサインイン中" |   signed-in-as: "{}としてサインイン中" | ||||||
|  |  | ||||||
| @@ -1984,6 +2019,9 @@ pages: | |||||||
|   read-page: "ソースを表示中" |   read-page: "ソースを表示中" | ||||||
|   page-created: "ページを作成しました" |   page-created: "ページを作成しました" | ||||||
|   page-updated: "ページを更新しました" |   page-updated: "ページを更新しました" | ||||||
|  |   name-already-exists: "指定されたページURLは既に存在しています" | ||||||
|  |   title-invalid-name: "不正なページURLです" | ||||||
|  |   text-invalid-name: "空白でないか確認してください" | ||||||
|   are-you-sure-delete: "このページを削除しますか?" |   are-you-sure-delete: "このページを削除しますか?" | ||||||
|   page-deleted: "ページを削除しました" |   page-deleted: "ページを削除しました" | ||||||
|   edit-this-page: "このページを編集" |   edit-this-page: "このページを編集" | ||||||
| @@ -2262,3 +2300,65 @@ pages: | |||||||
|     enviromentVariables: "環境変数" |     enviromentVariables: "環境変数" | ||||||
|     pageVariables: "ページ要素" |     pageVariables: "ページ要素" | ||||||
|     argVariables: "入力スロット" |     argVariables: "入力スロット" | ||||||
|  |  | ||||||
|  | room: | ||||||
|  |   add-furniture: "家具を置く" | ||||||
|  |   translate: "移動" | ||||||
|  |   rotate: "回転" | ||||||
|  |   exit: "戻る" | ||||||
|  |   remove: "しまう" | ||||||
|  |   save: "保存" | ||||||
|  |   saved: "保存しました" | ||||||
|  |   clear: "片付け" | ||||||
|  |   clear-confirm: "全ての家具をしまいますか?" | ||||||
|  |   leave-confirm: "未保存の変更があります、移動しますか?" | ||||||
|  |   chooseImage: "画像を選択" | ||||||
|  |   room-type: "部屋のタイプ" | ||||||
|  |   carpet-color: "床の色" | ||||||
|  |   rooms: | ||||||
|  |     default: "デフォルト" | ||||||
|  |     washitsu: "和室" | ||||||
|  |   furnitures: | ||||||
|  |     milk: "牛乳パック" | ||||||
|  |     bed: "ベッド" | ||||||
|  |     low-table: "ローテーブル" | ||||||
|  |     desk: "デスク" | ||||||
|  |     chair: "チェア" | ||||||
|  |     chair2: "チェア2" | ||||||
|  |     fan: "換気扇" | ||||||
|  |     pc: "パソコン" | ||||||
|  |     plant: "観葉植物" | ||||||
|  |     plant2: "観葉植物2" | ||||||
|  |     eraser: "消しゴム" | ||||||
|  |     pencil: "鉛筆" | ||||||
|  |     pudding: "プリン" | ||||||
|  |     cardboard-box: "段ボール箱" | ||||||
|  |     cardboard-box2: "段ボール箱2" | ||||||
|  |     cardboard-box3: "段ボール箱3" | ||||||
|  |     book: "本" | ||||||
|  |     book2: "本2" | ||||||
|  |     piano: "ピアノ" | ||||||
|  |     facial-tissue: "ティッシュボックス" | ||||||
|  |     server: "サーバー" | ||||||
|  |     moon: "月" | ||||||
|  |     corkboard: "コルクボード" | ||||||
|  |     mousepad: "マウスパッド" | ||||||
|  |     monitor: "モニター" | ||||||
|  |     keyboard: "キーボード" | ||||||
|  |     carpet-stripe: "カーペット(縞)" | ||||||
|  |     mat: "マット" | ||||||
|  |     color-box: "カラーボックス" | ||||||
|  |     wall-clock: "壁掛け時計" | ||||||
|  |     photoframe: "額縁" | ||||||
|  |     cube: "キューブ" | ||||||
|  |     tv: "テレビ" | ||||||
|  |     pinguin: "ピンギン" | ||||||
|  |     rubik-cube: "ルービックキューブ" | ||||||
|  |     poster-h: "ポスター(横長)" | ||||||
|  |     poster-v: "ポスター(縦長)" | ||||||
|  |     sofa: "ソファ" | ||||||
|  |     spiral: "螺旋階段" | ||||||
|  |     bin: "ゴミ箱" | ||||||
|  |     cup-noodle: "カップ麺" | ||||||
|  |     holo-display: "ホログラフィックディスプレイ" | ||||||
|  |     energy-drink: "エナジードリンク" | ||||||
|   | |||||||
| @@ -27,6 +27,13 @@ common: | |||||||
|   load-more: "もっとあらへんのか!" |   load-more: "もっとあらへんのか!" | ||||||
|   enter-password: "パスワードを入れてや" |   enter-password: "パスワードを入れてや" | ||||||
|   2fa: "二段階認証" |   2fa: "二段階認証" | ||||||
|  |   delete-confirm: "この投稿を削除してもええか?" | ||||||
|  |   notification-types: | ||||||
|  |     all: "すべて" | ||||||
|  |     follow: "フォロー" | ||||||
|  |     reply: "返す" | ||||||
|  |     renote: "Renote" | ||||||
|  |     reaction: "リアクション" | ||||||
|   got-it: "ほい" |   got-it: "ほい" | ||||||
|   customization-tips: |   customization-tips: | ||||||
|     title: "カスタマイズのヒント" |     title: "カスタマイズのヒント" | ||||||
| @@ -460,7 +467,7 @@ common/views/components/profile-editor.vue: | |||||||
|   description: "自己紹介" |   description: "自己紹介" | ||||||
|   language: "言語" |   language: "言語" | ||||||
|   birthday: "誕生日" |   birthday: "誕生日" | ||||||
|   avatar: "アイコン" |   avatar: "アバター" | ||||||
|   banner: "バナー" |   banner: "バナー" | ||||||
|   is-cat: "このアカウントはCatやで" |   is-cat: "このアカウントはCatやで" | ||||||
|   is-bot: "このアカウントはBotやで" |   is-bot: "このアカウントはBotやで" | ||||||
| @@ -598,7 +605,7 @@ desktop/views/components/crop-window.vue: | |||||||
| desktop/views/components/drive-window.vue: | desktop/views/components/drive-window.vue: | ||||||
|   used: "使うとる" |   used: "使うとる" | ||||||
| desktop/views/components/drive.file.vue: | desktop/views/components/drive.file.vue: | ||||||
|   avatar: "アイコン" |   avatar: "アバター" | ||||||
|   banner: "バナー" |   banner: "バナー" | ||||||
|   nsfw: "見たらあかんで" |   nsfw: "見たらあかんで" | ||||||
|   contextmenu: |   contextmenu: | ||||||
| @@ -608,7 +615,7 @@ desktop/views/components/drive.file.vue: | |||||||
|     copy-url: "URLをコピー" |     copy-url: "URLをコピー" | ||||||
|     download: "ダウンロード" |     download: "ダウンロード" | ||||||
|     else-files: "その他" |     else-files: "その他" | ||||||
|     set-as-avatar: "アイコンにする" |     set-as-avatar: "アバターにする" | ||||||
|     set-as-banner: "バナーにする" |     set-as-banner: "バナーにする" | ||||||
|     open-in-app: "アプリで開く" |     open-in-app: "アプリで開く" | ||||||
|     add-app: "アプリ増やす" |     add-app: "アプリ増やす" | ||||||
| @@ -1176,6 +1183,8 @@ mobile/views/pages/search.vue: | |||||||
|   not-found: "ワイは「{q}」なんて投稿知らんわ、無いんちゃう?知らんけど。" |   not-found: "ワイは「{q}」なんて投稿知らんわ、無いんちゃう?知らんけど。" | ||||||
| mobile/views/pages/selectdrive.vue: | mobile/views/pages/selectdrive.vue: | ||||||
|   select-file: "ファイル選んでや" |   select-file: "ファイル選んでや" | ||||||
|  | mobile/views/pages/notifications.vue: | ||||||
|  |   notifications: "通知" | ||||||
| mobile/views/pages/settings.vue: | mobile/views/pages/settings.vue: | ||||||
|   signed-in-as: "あんたは橋の下で拾った{}や!" |   signed-in-as: "あんたは橋の下で拾った{}や!" | ||||||
| mobile/views/pages/user.vue: | mobile/views/pages/user.vue: | ||||||
| @@ -1275,3 +1284,10 @@ pages: | |||||||
|         arg1: "リスト" |         arg1: "リスト" | ||||||
|     types: |     types: | ||||||
|       array: "リスト" |       array: "リスト" | ||||||
|  | room: | ||||||
|  |   translate: "移動" | ||||||
|  |   save: "保存" | ||||||
|  |   saved: "保存したで!" | ||||||
|  |   furnitures: | ||||||
|  |     moon: "月" | ||||||
|  |     bin: "ゴミ箱" | ||||||
|   | |||||||
| @@ -36,6 +36,19 @@ common: | |||||||
|   reload-to-apply-the-setting: "이 설정을 적용하려면 페이지를 새로고침해야 합니다. 바로 새로고침하시겠습니까?" |   reload-to-apply-the-setting: "이 설정을 적용하려면 페이지를 새로고침해야 합니다. 바로 새로고침하시겠습니까?" | ||||||
|   fetching-as-ap-object: "연합에서 조회 중" |   fetching-as-ap-object: "연합에서 조회 중" | ||||||
|   unfollow-confirm: "{name} 님을 팔로우 해제하시겠습니까?" |   unfollow-confirm: "{name} 님을 팔로우 해제하시겠습니까?" | ||||||
|  |   delete-confirm: "이 글을 삭제하시겠습니까?" | ||||||
|  |   signin-required: "로그인 해주세요" | ||||||
|  |   notification-type: "알림의 종류" | ||||||
|  |   notification-types: | ||||||
|  |     all: "모두" | ||||||
|  |     pollVote: "투표" | ||||||
|  |     follow: "팔로잉" | ||||||
|  |     receiveFollowRequest: "팔로우 요청" | ||||||
|  |     reply: "답글 달기" | ||||||
|  |     quote: "인용" | ||||||
|  |     renote: "리노트" | ||||||
|  |     mention: "멘션" | ||||||
|  |     reaction: "리액션" | ||||||
|   got-it: "알겠습니다" |   got-it: "알겠습니다" | ||||||
|   customization-tips: |   customization-tips: | ||||||
|     title: "커스터마이징 도움말" |     title: "커스터마이징 도움말" | ||||||
| @@ -88,7 +101,7 @@ common: | |||||||
|     "read:mutes": "뮤트 보기" |     "read:mutes": "뮤트 보기" | ||||||
|     "write:mutes": "뮤트 수정" |     "write:mutes": "뮤트 수정" | ||||||
|     "write:notes": "글 작성, 삭제" |     "write:notes": "글 작성, 삭제" | ||||||
|     "read:notifications": "글 보기" |     "read:notifications": "알림 보기" | ||||||
|     "write:notifications": "알림 수정" |     "write:notifications": "알림 수정" | ||||||
|     "read:reactions": "리액션 보기" |     "read:reactions": "리액션 보기" | ||||||
|     "write:reactions": "리액션 수정" |     "write:reactions": "리액션 수정" | ||||||
| @@ -120,6 +133,7 @@ common: | |||||||
|     geolocation-alert: "사용 중이신 장치에서는 위치 정보를 사용할 수 없습니다" |     geolocation-alert: "사용 중이신 장치에서는 위치 정보를 사용할 수 없습니다" | ||||||
|     error: "오류" |     error: "오류" | ||||||
|     enter-username: "사용자명을 입력해주세요" |     enter-username: "사용자명을 입력해주세요" | ||||||
|  |     specified-recipient: "수신인" | ||||||
|     add-visible-user: "사용자 추가" |     add-visible-user: "사용자 추가" | ||||||
|     cw-placeholder: "내용에 대한 주석 (옵션)" |     cw-placeholder: "내용에 대한 주석 (옵션)" | ||||||
|     username-prompt: "사용자명을 입력해주세요" |     username-prompt: "사용자명을 입력해주세요" | ||||||
| @@ -202,6 +216,7 @@ common: | |||||||
|     use-avatar-reversi-stones: "리버시의 돌로 아바타를 사용" |     use-avatar-reversi-stones: "리버시의 돌로 아바타를 사용" | ||||||
|     disable-animated-mfm: "글의 문자 애니메이션을 비활성화" |     disable-animated-mfm: "글의 문자 애니메이션을 비활성화" | ||||||
|     disable-showing-animated-images: "움직이는 이미지를 자동으로 재생하지 않음" |     disable-showing-animated-images: "움직이는 이미지를 자동으로 재생하지 않음" | ||||||
|  |     enable-quick-notification-view: "알림의 빠른 보기를 사용합니다" | ||||||
|     suggest-recent-hashtags: "최근 해시태그를 글 작성란에 표시" |     suggest-recent-hashtags: "최근 해시태그를 글 작성란에 표시" | ||||||
|     always-show-nsfw: "항상 열람주의 미디어를 표시" |     always-show-nsfw: "항상 열람주의 미디어를 표시" | ||||||
|     always-mark-nsfw: "항상 미디어를 열람주의로 설정하여 게시" |     always-mark-nsfw: "항상 미디어를 열람주의로 설정하여 게시" | ||||||
| @@ -232,7 +247,7 @@ common: | |||||||
|     deck-column-width-wide: "넓음" |     deck-column-width-wide: "넓음" | ||||||
|     use-shadow: "UI에 그림자 효과 적용" |     use-shadow: "UI에 그림자 효과 적용" | ||||||
|     rounded-corners: "UI의 모서리를 둥글게 설정" |     rounded-corners: "UI의 모서리를 둥글게 설정" | ||||||
|     circle-icons: "원형 아이콘 사용" |     circle-icons: "원형 아바타를 사용" | ||||||
|     contrasted-acct: "사용자명에 대비 추가" |     contrasted-acct: "사용자명에 대비 추가" | ||||||
|     wallpaper: "배경" |     wallpaper: "배경" | ||||||
|     choose-wallpaper: "배경 설정" |     choose-wallpaper: "배경 설정" | ||||||
| @@ -282,6 +297,16 @@ common: | |||||||
|     saved: "저장하였습니다" |     saved: "저장하였습니다" | ||||||
|     home-profile: "홈 프로필" |     home-profile: "홈 프로필" | ||||||
|     deck-profile: "덱 프로필" |     deck-profile: "덱 프로필" | ||||||
|  |     room: "룸" | ||||||
|  |     _room: | ||||||
|  |       graphicsQuality: "그래픽 품질" | ||||||
|  |       _graphicsQuality: | ||||||
|  |         ultra: "최고" | ||||||
|  |         high: "높음" | ||||||
|  |         medium: "보통" | ||||||
|  |         low: "낮음" | ||||||
|  |         cheep: "최저" | ||||||
|  |       useOrthographicCamera: "평행 투시 카메라를 사용" | ||||||
|   search: "검색" |   search: "검색" | ||||||
|   delete: "삭제" |   delete: "삭제" | ||||||
|   loading: "로드 중" |   loading: "로드 중" | ||||||
| @@ -366,6 +391,9 @@ common/views/pages/explore.vue: | |||||||
|   federated: "연합" |   federated: "연합" | ||||||
|   explore: "{host}을(를) 탐색" |   explore: "{host}을(를) 탐색" | ||||||
|   users-info: "현재 {users} 사용자가 등록되어 있습니다" |   users-info: "현재 {users} 사용자가 등록되어 있습니다" | ||||||
|  | common/views/components/reactions-viewer.details.vue: | ||||||
|  |   few-users: "{users}님이 {reaction} 리액션" | ||||||
|  |   many-users: "{users}님 외 {omitted}명이 {reaction} 리액션" | ||||||
| common/views/components/url-preview.vue: | common/views/components/url-preview.vue: | ||||||
|   enable-player: "플레이어 열기" |   enable-player: "플레이어 열기" | ||||||
|   disable-player: "플레이어 닫기" |   disable-player: "플레이어 닫기" | ||||||
| @@ -531,6 +559,8 @@ common/views/components/note-menu.vue: | |||||||
|   unpin: "프로필에서 고정 해제" |   unpin: "프로필에서 고정 해제" | ||||||
|   delete: "삭제" |   delete: "삭제" | ||||||
|   delete-confirm: "이 글을 삭제하시겠습니까?" |   delete-confirm: "이 글을 삭제하시겠습니까?" | ||||||
|  |   delete-and-edit: "삭제 후 편집" | ||||||
|  |   delete-and-edit-confirm: "이 글을 삭제한 뒤 다시 편집하시겠습니까? 이 글에 대한 리액션, 리노트, 답글 또한 모두 삭제됩니다." | ||||||
|   remote: "글 원본 보기" |   remote: "글 원본 보기" | ||||||
|   pin-limit-exceeded: "더 이상 고정할 수 없습니다." |   pin-limit-exceeded: "더 이상 고정할 수 없습니다." | ||||||
| common/views/components/user-menu.vue: | common/views/components/user-menu.vue: | ||||||
| @@ -1119,6 +1149,7 @@ desktop/views/components/ui.header.account.vue: | |||||||
|   groups: "그룹" |   groups: "그룹" | ||||||
|   follow-requests: "팔로우 요청" |   follow-requests: "팔로우 요청" | ||||||
|   admin: "관리" |   admin: "관리" | ||||||
|  |   room: "룸" | ||||||
| desktop/views/components/ui.header.nav.vue: | desktop/views/components/ui.header.nav.vue: | ||||||
|   game: "게임" |   game: "게임" | ||||||
| desktop/views/components/ui.header.notifications.vue: | desktop/views/components/ui.header.notifications.vue: | ||||||
| @@ -1698,6 +1729,8 @@ mobile/views/pages/search.vue: | |||||||
|   not-found: "\"{q}\" 와 일치하는 글을 찾을 수 없습니다." |   not-found: "\"{q}\" 와 일치하는 글을 찾을 수 없습니다." | ||||||
| mobile/views/pages/selectdrive.vue: | mobile/views/pages/selectdrive.vue: | ||||||
|   select-file: "파일 선택" |   select-file: "파일 선택" | ||||||
|  | mobile/views/pages/notifications.vue: | ||||||
|  |   notifications: "알림" | ||||||
| mobile/views/pages/settings.vue: | mobile/views/pages/settings.vue: | ||||||
|   signed-in-as: "{}(으)로 로그인" |   signed-in-as: "{}(으)로 로그인" | ||||||
| mobile/views/pages/user.vue: | mobile/views/pages/user.vue: | ||||||
| @@ -1788,6 +1821,7 @@ pages: | |||||||
|   read-page: "소스 표시중" |   read-page: "소스 표시중" | ||||||
|   page-created: "페이지를 만들었습니다" |   page-created: "페이지를 만들었습니다" | ||||||
|   page-updated: "페이지를 수정했습니다" |   page-updated: "페이지를 수정했습니다" | ||||||
|  |   name-already-exists: "지정한 페이지 URL은 이미 존재합니다" | ||||||
|   are-you-sure-delete: "이 페이지를 삭제하시겠습니까?" |   are-you-sure-delete: "이 페이지를 삭제하시겠습니까?" | ||||||
|   page-deleted: "페이지가 삭제되었습니다" |   page-deleted: "페이지가 삭제되었습니다" | ||||||
|   edit-this-page: "이 페이지를 편집" |   edit-this-page: "이 페이지를 편집" | ||||||
| @@ -2056,3 +2090,64 @@ pages: | |||||||
|     enviromentVariables: "환경 변수" |     enviromentVariables: "환경 변수" | ||||||
|     pageVariables: "페이지 요소" |     pageVariables: "페이지 요소" | ||||||
|     argVariables: "입력 슬롯" |     argVariables: "입력 슬롯" | ||||||
|  | room: | ||||||
|  |   add-furniture: "가구를 배치" | ||||||
|  |   translate: "이동" | ||||||
|  |   rotate: "회전" | ||||||
|  |   exit: "선택 해제" | ||||||
|  |   remove: "치우기" | ||||||
|  |   save: "저장" | ||||||
|  |   saved: "저장하였습니다" | ||||||
|  |   clear: "모두 치우기" | ||||||
|  |   clear-confirm: "정말 방 안의 모든 가구를 치우시겠습니까?" | ||||||
|  |   leave-confirm: "저장되지 않은 변경 사항이 있습니다. 정말 떠나시겠습니까?" | ||||||
|  |   chooseImage: "이미지 선택" | ||||||
|  |   room-type: "룸 종류" | ||||||
|  |   carpet-color: "바닥 색상" | ||||||
|  |   rooms: | ||||||
|  |     default: "기본" | ||||||
|  |     washitsu: "일본식" | ||||||
|  |   furnitures: | ||||||
|  |     milk: "우유 팩" | ||||||
|  |     bed: "침대" | ||||||
|  |     low-table: "낮은 테이블" | ||||||
|  |     desk: "책상" | ||||||
|  |     chair: "의자" | ||||||
|  |     chair2: "의자 2" | ||||||
|  |     fan: "환기구" | ||||||
|  |     pc: "컴퓨터" | ||||||
|  |     plant: "관엽식물" | ||||||
|  |     plant2: "관엽식물 2" | ||||||
|  |     eraser: "지우개" | ||||||
|  |     pencil: "연필" | ||||||
|  |     pudding: "푸딩" | ||||||
|  |     cardboard-box: "골판지 상자" | ||||||
|  |     cardboard-box2: "골판지 상자 2" | ||||||
|  |     cardboard-box3: "골판지 상자 3" | ||||||
|  |     book: "책" | ||||||
|  |     book2: "책 2" | ||||||
|  |     piano: "피아노" | ||||||
|  |     facial-tissue: "휴지 상자" | ||||||
|  |     server: "서버" | ||||||
|  |     moon: "달" | ||||||
|  |     corkboard: "게시판" | ||||||
|  |     mousepad: "마우스 패드" | ||||||
|  |     monitor: "모니터" | ||||||
|  |     keyboard: "키보드" | ||||||
|  |     carpet-stripe: "카페트 (줄무늬)" | ||||||
|  |     mat: "매트" | ||||||
|  |     color-box: "책장" | ||||||
|  |     wall-clock: "벽걸이 시계" | ||||||
|  |     photoframe: "액자" | ||||||
|  |     cube: "큐브" | ||||||
|  |     tv: "TV" | ||||||
|  |     pinguin: "펭귄" | ||||||
|  |     rubik-cube: "루빅스 큐브" | ||||||
|  |     poster-h: "포스터 (가로)" | ||||||
|  |     poster-v: "포스터 (세로)" | ||||||
|  |     sofa: "소파" | ||||||
|  |     spiral: "나선형 계단" | ||||||
|  |     bin: "휴지통" | ||||||
|  |     cup-noodle: "컵라면" | ||||||
|  |     holo-display: "홀로그램" | ||||||
|  |     energy-drink: "에너지 드링크" | ||||||
|   | |||||||
| @@ -8,6 +8,11 @@ common: | |||||||
|     reaction: "Reactie" |     reaction: "Reactie" | ||||||
|   close: "Sluiten" |   close: "Sluiten" | ||||||
|   enter-password: "Voer het wachtwoord in" |   enter-password: "Voer het wachtwoord in" | ||||||
|  |   notification-types: | ||||||
|  |     all: "Alle" | ||||||
|  |     follow: "Volgend" | ||||||
|  |     reply: "Beantwoorden" | ||||||
|  |     reaction: "Reactie" | ||||||
|   time: |   time: | ||||||
|     unknown: "onbekend" |     unknown: "onbekend" | ||||||
|     future: "toekomstig" |     future: "toekomstig" | ||||||
| @@ -227,6 +232,7 @@ common/views/pages/follow.vue: | |||||||
|   follow: "Volgend" |   follow: "Volgend" | ||||||
| desktop: | desktop: | ||||||
|   banner: "Omslagfoto" |   banner: "Omslagfoto" | ||||||
|  |   avatar: "Gebruikersafbeelding" | ||||||
|   unable-to-process: "De operatie kan niet worden voltooid." |   unable-to-process: "De operatie kan niet worden voltooid." | ||||||
| desktop/views/components/activity.chart.vue: | desktop/views/components/activity.chart.vue: | ||||||
|   total: "Zwart ... totaal" |   total: "Zwart ... totaal" | ||||||
| @@ -569,6 +575,8 @@ mobile/views/pages/search.vue: | |||||||
|   search: "Zoeken" |   search: "Zoeken" | ||||||
| mobile/views/pages/selectdrive.vue: | mobile/views/pages/selectdrive.vue: | ||||||
|   select-file: "Kies een bestand" |   select-file: "Kies een bestand" | ||||||
|  | mobile/views/pages/notifications.vue: | ||||||
|  |   notifications: "Meldingen" | ||||||
| mobile/views/pages/settings.vue: | mobile/views/pages/settings.vue: | ||||||
|   signed-in-as: "Ingelogd als {}" |   signed-in-as: "Ingelogd als {}" | ||||||
| mobile/views/pages/user.vue: | mobile/views/pages/user.vue: | ||||||
| @@ -631,3 +639,7 @@ pages: | |||||||
|         arg1: "Lijsten" |         arg1: "Lijsten" | ||||||
|     types: |     types: | ||||||
|       array: "Lijsten" |       array: "Lijsten" | ||||||
|  | room: | ||||||
|  |   translate: "Verplaatsen" | ||||||
|  |   furnitures: | ||||||
|  |     moon: "Maan" | ||||||
|   | |||||||
| @@ -11,6 +11,10 @@ common: | |||||||
|     rich-contents: "Innlegg" |     rich-contents: "Innlegg" | ||||||
|     drive: "Disk" |     drive: "Disk" | ||||||
|   close: "Lukk" |   close: "Lukk" | ||||||
|  |   notification-types: | ||||||
|  |     all: "Alle" | ||||||
|  |     follow: "Følger" | ||||||
|  |     reply: "Svar" | ||||||
|   got-it: "Skjønner!" |   got-it: "Skjønner!" | ||||||
|   notification: |   notification: | ||||||
|     file-uploaded: "Filen ble lastet opp!" |     file-uploaded: "Filen ble lastet opp!" | ||||||
| @@ -467,6 +471,8 @@ mobile/views/pages/games/reversi.vue: | |||||||
|   reversi: "Reversi" |   reversi: "Reversi" | ||||||
| mobile/views/pages/search.vue: | mobile/views/pages/search.vue: | ||||||
|   search: "Søk" |   search: "Søk" | ||||||
|  | mobile/views/pages/notifications.vue: | ||||||
|  |   notifications: "Notifikasjon" | ||||||
| mobile/views/pages/user.vue: | mobile/views/pages/user.vue: | ||||||
|   following: "Følger" |   following: "Følger" | ||||||
|   followers: "Følgere" |   followers: "Følgere" | ||||||
| @@ -514,3 +520,9 @@ pages: | |||||||
|         arg1: "Lister" |         arg1: "Lister" | ||||||
|     types: |     types: | ||||||
|       array: "Lister" |       array: "Lister" | ||||||
|  | room: | ||||||
|  |   translate: "Flytt" | ||||||
|  |   save: "Lagre" | ||||||
|  |   furnitures: | ||||||
|  |     moon: "Måne" | ||||||
|  |     bin: "Papirkurv" | ||||||
|   | |||||||
| @@ -28,6 +28,18 @@ common: | |||||||
|   signin: "Zaloguj się" |   signin: "Zaloguj się" | ||||||
|   signup: "Rejestracja" |   signup: "Rejestracja" | ||||||
|   signout: "Wyloguj się" |   signout: "Wyloguj się" | ||||||
|  |   delete-confirm: "Czy na pewno chcesz usunąć ten wpis?" | ||||||
|  |   notification-type: "Typy powiadomień" | ||||||
|  |   notification-types: | ||||||
|  |     all: "Wszyscy" | ||||||
|  |     pollVote: "Głosy" | ||||||
|  |     follow: "Śledzeni" | ||||||
|  |     receiveFollowRequest: "Prośby o śledzenie" | ||||||
|  |     reply: "Odpowiedzi" | ||||||
|  |     quote: "Cytat" | ||||||
|  |     renote: "Udostępnij" | ||||||
|  |     mention: "Wzmianki" | ||||||
|  |     reaction: "Reakcje" | ||||||
|   got-it: "Rozumiem!" |   got-it: "Rozumiem!" | ||||||
|   customization-tips: |   customization-tips: | ||||||
|     title: "Wskazówki o dostosowywaniu" |     title: "Wskazówki o dostosowywaniu" | ||||||
| @@ -86,8 +98,10 @@ common: | |||||||
|     visibility: "Widoczność" |     visibility: "Widoczność" | ||||||
|     error: "Błąd" |     error: "Błąd" | ||||||
|     enter-username: "Wprowadź nazwę użytkownika" |     enter-username: "Wprowadź nazwę użytkownika" | ||||||
|  |     specified-recipient: "Adresat" | ||||||
|     add-visible-user: "Dodaj użytkownika" |     add-visible-user: "Dodaj użytkownika" | ||||||
|     username-prompt: "Wprowadź nazwę użytkownika" |     username-prompt: "Wprowadź nazwę użytkownika" | ||||||
|  |     enter-file-name: "Wprowadź nazwę pliku" | ||||||
|   weekday-short: |   weekday-short: | ||||||
|     sunday: "N" |     sunday: "N" | ||||||
|     monday: "Pn" |     monday: "Pn" | ||||||
| @@ -150,6 +164,7 @@ common: | |||||||
|     note-visibility: "Widoczność wpisów" |     note-visibility: "Widoczność wpisów" | ||||||
|     remember-note-visibility: "Zapamiętaj widoczność wpisów" |     remember-note-visibility: "Zapamiętaj widoczność wpisów" | ||||||
|     web-search-engine: "Wyszukiwarka internetowa" |     web-search-engine: "Wyszukiwarka internetowa" | ||||||
|  |     paste: "Wklej" | ||||||
|     line-width: "Szerokości linii" |     line-width: "Szerokości linii" | ||||||
|     line-width-thin: "Cienka" |     line-width-thin: "Cienka" | ||||||
|     line-width-normal: "Normalna" |     line-width-normal: "Normalna" | ||||||
| @@ -1137,6 +1152,8 @@ mobile/views/pages/search.vue: | |||||||
|   search: "Szukaj" |   search: "Szukaj" | ||||||
| mobile/views/pages/selectdrive.vue: | mobile/views/pages/selectdrive.vue: | ||||||
|   select-file: "Wybierz plik" |   select-file: "Wybierz plik" | ||||||
|  | mobile/views/pages/notifications.vue: | ||||||
|  |   notifications: "Powiadomienia" | ||||||
| mobile/views/pages/settings.vue: | mobile/views/pages/settings.vue: | ||||||
|   signed-in-as: "Zalogowany jako {}" |   signed-in-as: "Zalogowany jako {}" | ||||||
| mobile/views/pages/user.vue: | mobile/views/pages/user.vue: | ||||||
| @@ -1242,3 +1259,10 @@ pages: | |||||||
|         arg1: "Listy" |         arg1: "Listy" | ||||||
|     types: |     types: | ||||||
|       array: "Listy" |       array: "Listy" | ||||||
|  | room: | ||||||
|  |   translate: "Przenieś" | ||||||
|  |   save: "Zapisz" | ||||||
|  |   saved: "Zapisano" | ||||||
|  |   furnitures: | ||||||
|  |     moon: "Księżyc" | ||||||
|  |     bin: "Kosz" | ||||||
|   | |||||||
| @@ -18,6 +18,8 @@ common: | |||||||
|   application-authorization: "Aplicativos autorizados" |   application-authorization: "Aplicativos autorizados" | ||||||
|   close: "Fechar" |   close: "Fechar" | ||||||
|   do-not-copy-paste: "Por favor, não digite ou copie o código aqui. A conta pode ser comprometida." |   do-not-copy-paste: "Por favor, não digite ou copie o código aqui. A conta pode ser comprometida." | ||||||
|  |   notification-types: | ||||||
|  |     follow: "Seguindo" | ||||||
|   got-it: "Entendi!" |   got-it: "Entendi!" | ||||||
|   customization-tips: |   customization-tips: | ||||||
|     title: "Dicas de personalização" |     title: "Dicas de personalização" | ||||||
| @@ -282,3 +284,7 @@ pages: | |||||||
|   blocks: |   blocks: | ||||||
|     image: "Imagens" |     image: "Imagens" | ||||||
|     post: "Formulário de publicação" |     post: "Formulário de publicação" | ||||||
|  | room: | ||||||
|  |   furnitures: | ||||||
|  |     moon: "Lua" | ||||||
|  |     bin: "Lixo" | ||||||
|   | |||||||
| @@ -168,3 +168,7 @@ pages: | |||||||
|       random: "Случайно" |       random: "Случайно" | ||||||
|     blocks: |     blocks: | ||||||
|       random: "Случайно" |       random: "Случайно" | ||||||
|  | room: | ||||||
|  |   furnitures: | ||||||
|  |     moon: "Луна" | ||||||
|  |     bin: "Мусорное ведро" | ||||||
|   | |||||||
| @@ -36,6 +36,19 @@ common: | |||||||
|   reload-to-apply-the-setting: "必须重新加载页面以应用此设置。 确实要立即重新加载吗?" |   reload-to-apply-the-setting: "必须重新加载页面以应用此设置。 确实要立即重新加载吗?" | ||||||
|   fetching-as-ap-object: "联合查询" |   fetching-as-ap-object: "联合查询" | ||||||
|   unfollow-confirm: "取消对{name}的关注?" |   unfollow-confirm: "取消对{name}的关注?" | ||||||
|  |   delete-confirm: "确定删除这个投稿吗?" | ||||||
|  |   signin-required: "请先登录" | ||||||
|  |   notification-type: "通知类型" | ||||||
|  |   notification-types: | ||||||
|  |     all: "所有" | ||||||
|  |     pollVote: "投票" | ||||||
|  |     follow: "关注中" | ||||||
|  |     receiveFollowRequest: "关注请求" | ||||||
|  |     reply: "回复" | ||||||
|  |     quote: "引用" | ||||||
|  |     renote: "转推" | ||||||
|  |     mention: "提及" | ||||||
|  |     reaction: "回应" | ||||||
|   got-it: "知道了" |   got-it: "知道了" | ||||||
|   customization-tips: |   customization-tips: | ||||||
|     title: "自定义提示" |     title: "自定义提示" | ||||||
| @@ -120,6 +133,7 @@ common: | |||||||
|     geolocation-alert: "您的设备不支持定位服务" |     geolocation-alert: "您的设备不支持定位服务" | ||||||
|     error: "错误" |     error: "错误" | ||||||
|     enter-username: "输入用户名" |     enter-username: "输入用户名" | ||||||
|  |     specified-recipient: "收件人" | ||||||
|     add-visible-user: "添加用户" |     add-visible-user: "添加用户" | ||||||
|     cw-placeholder: "评论帖子(可选)" |     cw-placeholder: "评论帖子(可选)" | ||||||
|     username-prompt: "输入用户名" |     username-prompt: "输入用户名" | ||||||
| @@ -202,6 +216,7 @@ common: | |||||||
|     use-avatar-reversi-stones: "用头像作为黑白棋的棋子" |     use-avatar-reversi-stones: "用头像作为黑白棋的棋子" | ||||||
|     disable-animated-mfm: "在帖子中禁用动画文本" |     disable-animated-mfm: "在帖子中禁用动画文本" | ||||||
|     disable-showing-animated-images: "不播放动画" |     disable-showing-animated-images: "不播放动画" | ||||||
|  |     enable-quick-notification-view: "启用通知快速查看" | ||||||
|     suggest-recent-hashtags: "在帖子表单上显示最近流行的哈希标签" |     suggest-recent-hashtags: "在帖子表单上显示最近流行的哈希标签" | ||||||
|     always-show-nsfw: "总是显示 NSFW 的内容" |     always-show-nsfw: "总是显示 NSFW 的内容" | ||||||
|     always-mark-nsfw: "总是用 NSFW 来标记附件" |     always-mark-nsfw: "总是用 NSFW 来标记附件" | ||||||
| @@ -232,7 +247,7 @@ common: | |||||||
|     deck-column-width-wide: "宽" |     deck-column-width-wide: "宽" | ||||||
|     use-shadow: "在UI中使用阴影效果" |     use-shadow: "在UI中使用阴影效果" | ||||||
|     rounded-corners: "UI界面圆角效果" |     rounded-corners: "UI界面圆角效果" | ||||||
|     circle-icons: "使用圆形图标" |     circle-icons: "使用圆形头像" | ||||||
|     contrasted-acct: "增加用户名的对比度" |     contrasted-acct: "增加用户名的对比度" | ||||||
|     wallpaper: "壁纸" |     wallpaper: "壁纸" | ||||||
|     choose-wallpaper: "选择壁纸" |     choose-wallpaper: "选择壁纸" | ||||||
| @@ -282,6 +297,16 @@ common: | |||||||
|     saved: "已保存" |     saved: "已保存" | ||||||
|     home-profile: "定制首页数据" |     home-profile: "定制首页数据" | ||||||
|     deck-profile: "定制Deck数据" |     deck-profile: "定制Deck数据" | ||||||
|  |     room: "房间" | ||||||
|  |     _room: | ||||||
|  |       graphicsQuality: "图形质量" | ||||||
|  |       _graphicsQuality: | ||||||
|  |         ultra: "最高" | ||||||
|  |         high: "高" | ||||||
|  |         medium: "中" | ||||||
|  |         low: "低" | ||||||
|  |         cheep: "最低" | ||||||
|  |       useOrthographicCamera: "使用正交相机" | ||||||
|   search: "搜索" |   search: "搜索" | ||||||
|   delete: "删除" |   delete: "删除" | ||||||
|   loading: "正在加载中" |   loading: "正在加载中" | ||||||
| @@ -366,6 +391,9 @@ common/views/pages/explore.vue: | |||||||
|   federated: "联合" |   federated: "联合" | ||||||
|   explore: "查找{host}" |   explore: "查找{host}" | ||||||
|   users-info: "当前有{users}个注册用户" |   users-info: "当前有{users}个注册用户" | ||||||
|  | common/views/components/reactions-viewer.details.vue: | ||||||
|  |   few-users: "{users}作出了{reaction}的回应" | ||||||
|  |   many-users: "{users}和其他{omitted}人做出了{reaction}的回应" | ||||||
| common/views/components/url-preview.vue: | common/views/components/url-preview.vue: | ||||||
|   enable-player: "打开播放器" |   enable-player: "打开播放器" | ||||||
|   disable-player: "关闭播放器" |   disable-player: "关闭播放器" | ||||||
| @@ -531,6 +559,8 @@ common/views/components/note-menu.vue: | |||||||
|   unpin: "取消置顶" |   unpin: "取消置顶" | ||||||
|   delete: "删除" |   delete: "删除" | ||||||
|   delete-confirm: "确定删除这个投稿吗?" |   delete-confirm: "确定删除这个投稿吗?" | ||||||
|  |   delete-and-edit: "删除和编辑" | ||||||
|  |   delete-and-edit-confirm: "要删除此帖并再次编辑吗?对此帖的所有回应,转推和回复也将被删除。" | ||||||
|   remote: "显示原始投稿" |   remote: "显示原始投稿" | ||||||
|   pin-limit-exceeded: "无法置顶更多了。" |   pin-limit-exceeded: "无法置顶更多了。" | ||||||
| common/views/components/user-menu.vue: | common/views/components/user-menu.vue: | ||||||
| @@ -726,6 +756,8 @@ common/views/components/profile-editor.vue: | |||||||
|   uploading: "正在上传" |   uploading: "正在上传" | ||||||
|   upload-failed: "上传失败" |   upload-failed: "上传失败" | ||||||
|   unable-to-process: "无法完成操作" |   unable-to-process: "无法完成操作" | ||||||
|  |   avatar-not-an-image: "选择的头像文件不是图片格式" | ||||||
|  |   banner-not-an-image: "选择的横幅背景不是图片格式" | ||||||
|   email: "邮件设置" |   email: "邮件设置" | ||||||
|   email-address: "电子邮件地址" |   email-address: "电子邮件地址" | ||||||
|   email-verified: "电子邮件地址已验证" |   email-verified: "电子邮件地址已验证" | ||||||
| @@ -745,6 +777,8 @@ common/views/components/profile-editor.vue: | |||||||
|   danger-zone: "危险选项" |   danger-zone: "危险选项" | ||||||
|   delete-account: "删除帐户" |   delete-account: "删除帐户" | ||||||
|   account-deleted: "帐户已被删除。 数据会在一段时间之后清除。" |   account-deleted: "帐户已被删除。 数据会在一段时间之后清除。" | ||||||
|  |   profile-metadata: "个人资料补充信息" | ||||||
|  |   metadata-label: "标签" | ||||||
|   metadata-content: "内容" |   metadata-content: "内容" | ||||||
| common/views/components/user-list-editor.vue: | common/views/components/user-list-editor.vue: | ||||||
|   users: "用户" |   users: "用户" | ||||||
| @@ -897,7 +931,7 @@ desktop/views/components/drive.file.vue: | |||||||
|     copy-url: "复制链接" |     copy-url: "复制链接" | ||||||
|     download: "下载" |     download: "下载" | ||||||
|     else-files: "其他" |     else-files: "其他" | ||||||
|     set-as-avatar: "设置为头像" |     set-as-avatar: "设为头像" | ||||||
|     set-as-banner: "设置为背景" |     set-as-banner: "设置为背景" | ||||||
|     open-in-app: "在应用程序中打开" |     open-in-app: "在应用程序中打开" | ||||||
|     add-app: "添加应用" |     add-app: "添加应用" | ||||||
| @@ -1115,6 +1149,7 @@ desktop/views/components/ui.header.account.vue: | |||||||
|   groups: "群组" |   groups: "群组" | ||||||
|   follow-requests: "关注申请" |   follow-requests: "关注申请" | ||||||
|   admin: "管理" |   admin: "管理" | ||||||
|  |   room: "房间" | ||||||
| desktop/views/components/ui.header.nav.vue: | desktop/views/components/ui.header.nav.vue: | ||||||
|   game: "游戏" |   game: "游戏" | ||||||
| desktop/views/components/ui.header.notifications.vue: | desktop/views/components/ui.header.notifications.vue: | ||||||
| @@ -1694,6 +1729,8 @@ mobile/views/pages/search.vue: | |||||||
|   not-found: "没有找到有关于“{q}”的帖子" |   not-found: "没有找到有关于“{q}”的帖子" | ||||||
| mobile/views/pages/selectdrive.vue: | mobile/views/pages/selectdrive.vue: | ||||||
|   select-file: "选择文件" |   select-file: "选择文件" | ||||||
|  | mobile/views/pages/notifications.vue: | ||||||
|  |   notifications: "通知" | ||||||
| mobile/views/pages/settings.vue: | mobile/views/pages/settings.vue: | ||||||
|   signed-in-as: "以{}登录" |   signed-in-as: "以{}登录" | ||||||
| mobile/views/pages/user.vue: | mobile/views/pages/user.vue: | ||||||
| @@ -1784,6 +1821,7 @@ pages: | |||||||
|   read-page: "查看源" |   read-page: "查看源" | ||||||
|   page-created: "页面已创建" |   page-created: "页面已创建" | ||||||
|   page-updated: "页面已更新" |   page-updated: "页面已更新" | ||||||
|  |   name-already-exists: "该页面URL已存在" | ||||||
|   are-you-sure-delete: "是否删除此页面?" |   are-you-sure-delete: "是否删除此页面?" | ||||||
|   page-deleted: "该页面已被删除。" |   page-deleted: "该页面已被删除。" | ||||||
|   edit-this-page: "编辑此页面" |   edit-this-page: "编辑此页面" | ||||||
| @@ -2052,3 +2090,64 @@ pages: | |||||||
|     enviromentVariables: "环境变量" |     enviromentVariables: "环境变量" | ||||||
|     pageVariables: "页面元素" |     pageVariables: "页面元素" | ||||||
|     argVariables: "输入槽函数" |     argVariables: "输入槽函数" | ||||||
|  | room: | ||||||
|  |   add-furniture: "放置家具" | ||||||
|  |   translate: "移动" | ||||||
|  |   rotate: "旋转" | ||||||
|  |   exit: "返回" | ||||||
|  |   remove: "移除" | ||||||
|  |   save: "保存" | ||||||
|  |   saved: "已保存" | ||||||
|  |   clear: "清理" | ||||||
|  |   clear-confirm: "是否清除所有家具?" | ||||||
|  |   leave-confirm: "有尚未保存的修改。是否离开?" | ||||||
|  |   chooseImage: "选择图片" | ||||||
|  |   room-type: "房间类型" | ||||||
|  |   carpet-color: "地板颜色" | ||||||
|  |   rooms: | ||||||
|  |     default: "默认" | ||||||
|  |     washitsu: "和式房间" | ||||||
|  |   furnitures: | ||||||
|  |     milk: "牛奶纸箱" | ||||||
|  |     bed: "床" | ||||||
|  |     low-table: "矮桌" | ||||||
|  |     desk: "书桌" | ||||||
|  |     chair: "椅子" | ||||||
|  |     chair2: "椅子2" | ||||||
|  |     fan: "换气扇" | ||||||
|  |     pc: "电脑" | ||||||
|  |     plant: "观叶植物" | ||||||
|  |     plant2: "观叶植物2" | ||||||
|  |     eraser: "橡皮擦" | ||||||
|  |     pencil: "铅笔" | ||||||
|  |     pudding: "布丁" | ||||||
|  |     cardboard-box: "纸板箱" | ||||||
|  |     cardboard-box2: "纸板箱2" | ||||||
|  |     cardboard-box3: "纸板箱3" | ||||||
|  |     book: "书" | ||||||
|  |     book2: "书2" | ||||||
|  |     piano: "钢琴" | ||||||
|  |     facial-tissue: "纸巾盒" | ||||||
|  |     server: "服务器" | ||||||
|  |     moon: "月球" | ||||||
|  |     corkboard: "软木板" | ||||||
|  |     mousepad: "鼠标垫" | ||||||
|  |     monitor: "显示器" | ||||||
|  |     keyboard: "键盘" | ||||||
|  |     carpet-stripe: "地毯(条纹)" | ||||||
|  |     mat: "垫子" | ||||||
|  |     color-box: "收纳柜" | ||||||
|  |     wall-clock: "挂钟" | ||||||
|  |     photoframe: "相框" | ||||||
|  |     cube: "立方体" | ||||||
|  |     tv: "电视" | ||||||
|  |     pinguin: "企鹅君" | ||||||
|  |     rubik-cube: "魔方" | ||||||
|  |     poster-h: "海报(横向)" | ||||||
|  |     poster-v: "海报(纵向)" | ||||||
|  |     sofa: "沙发" | ||||||
|  |     spiral: "螺旋楼梯" | ||||||
|  |     bin: "垃圾箱" | ||||||
|  |     cup-noodle: "杯面" | ||||||
|  |     holo-display: "全息显示器" | ||||||
|  |     energy-drink: "能量饮料" | ||||||
|   | |||||||
| @@ -88,3 +88,6 @@ admin/views/charts.vue: | |||||||
|   drive: "雲端硬碟" |   drive: "雲端硬碟" | ||||||
| pages: | pages: | ||||||
|   like: "贊" |   like: "贊" | ||||||
|  | room: | ||||||
|  |   furnitures: | ||||||
|  |     moon: "月" | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								migration/1563757595828-UsedUsername.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								migration/1563757595828-UsedUsername.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | import {MigrationInterface, QueryRunner} from "typeorm"; | ||||||
|  |  | ||||||
|  | export class UsedUsername1563757595828 implements MigrationInterface { | ||||||
|  |  | ||||||
|  |     public async up(queryRunner: QueryRunner): Promise<any> { | ||||||
|  |         await queryRunner.query(`CREATE TABLE "used_username" ("username" character varying(128) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, CONSTRAINT "PK_78fd79d2d24c6ac2f4cc9a31a5d" PRIMARY KEY ("username"))`); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public async down(queryRunner: QueryRunner): Promise<any> { | ||||||
|  |         await queryRunner.query(`DROP TABLE "used_username"`); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								migration/1565634203341-room.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								migration/1565634203341-room.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | import {MigrationInterface, QueryRunner} from "typeorm"; | ||||||
|  |  | ||||||
|  | export class room1565634203341 implements MigrationInterface { | ||||||
|  |  | ||||||
|  |     public async up(queryRunner: QueryRunner): Promise<any> { | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user_profile" ADD "room" jsonb NOT NULL DEFAULT '{}'`); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public async down(queryRunner: QueryRunner): Promise<any> { | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "room"`); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										85
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										85
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,7 +1,7 @@ | |||||||
| { | { | ||||||
| 	"name": "misskey", | 	"name": "misskey", | ||||||
| 	"author": "syuilo <i@syuilo.com>", | 	"author": "syuilo <i@syuilo.com>", | ||||||
| 	"version": "11.26.1", | 	"version": "11.31.1", | ||||||
| 	"codename": "daybreak", | 	"codename": "daybreak", | ||||||
| 	"repository": { | 	"repository": { | ||||||
| 		"type": "git", | 		"type": "git", | ||||||
| @@ -30,7 +30,7 @@ | |||||||
| 		"lodash": "^4.17.13" | 		"lodash": "^4.17.13" | ||||||
| 	}, | 	}, | ||||||
| 	"dependencies": { | 	"dependencies": { | ||||||
| 		"@elastic/elasticsearch": "7.1.0", | 		"@elastic/elasticsearch": "7.3.0", | ||||||
| 		"@fortawesome/fontawesome-svg-core": "1.2.19", | 		"@fortawesome/fontawesome-svg-core": "1.2.19", | ||||||
| 		"@fortawesome/free-brands-svg-icons": "5.9.0", | 		"@fortawesome/free-brands-svg-icons": "5.9.0", | ||||||
| 		"@fortawesome/free-regular-svg-icons": "5.9.0", | 		"@fortawesome/free-regular-svg-icons": "5.9.0", | ||||||
| @@ -66,7 +66,6 @@ | |||||||
| 		"@types/koa-views": "2.0.3", | 		"@types/koa-views": "2.0.3", | ||||||
| 		"@types/koa__cors": "2.2.3", | 		"@types/koa__cors": "2.2.3", | ||||||
| 		"@types/lolex": "3.1.1", | 		"@types/lolex": "3.1.1", | ||||||
| 		"@types/minio": "7.0.2", |  | ||||||
| 		"@types/mocha": "5.2.7", | 		"@types/mocha": "5.2.7", | ||||||
| 		"@types/node": "12.0.10", | 		"@types/node": "12.0.10", | ||||||
| 		"@types/nodemailer": "6.2.0", | 		"@types/nodemailer": "6.2.0", | ||||||
| @@ -99,40 +98,42 @@ | |||||||
| 		"@types/websocket": "0.0.40", | 		"@types/websocket": "0.0.40", | ||||||
| 		"@types/ws": "6.0.1", | 		"@types/ws": "6.0.1", | ||||||
| 		"@typescript-eslint/parser": "1.11.0", | 		"@typescript-eslint/parser": "1.11.0", | ||||||
| 		"animejs": "3.0.1", | 		"agentkeepalive": "4.0.2", | ||||||
| 		"apexcharts": "3.8.2", | 		"animejs": "3.1.0", | ||||||
|  | 		"apexcharts": "3.8.5", | ||||||
| 		"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", | ||||||
|  | 		"aws-sdk": "2.520.0", | ||||||
| 		"bcryptjs": "2.4.3", | 		"bcryptjs": "2.4.3", | ||||||
| 		"bootstrap": "4.3.1", | 		"bootstrap": "4.3.1", | ||||||
| 		"bootstrap-vue": "2.0.0-rc.13", | 		"bootstrap-vue": "2.0.0-rc.13", | ||||||
| 		"bull": "3.10.0", | 		"bull": "3.10.0", | ||||||
| 		"cafy": "15.1.1", | 		"cafy": "15.1.1", | ||||||
| 		"cbor": "4.1.5", | 		"cbor": "4.3.0", | ||||||
| 		"chai": "4.2.0", | 		"chai": "4.2.0", | ||||||
| 		"chalk": "2.4.2", | 		"chalk": "2.4.2", | ||||||
| 		"cli-highlight": "2.1.1", | 		"cli-highlight": "2.1.1", | ||||||
| 		"commander": "2.20.0", | 		"commander": "2.20.0", | ||||||
| 		"content-disposition": "0.5.3", | 		"content-disposition": "0.5.3", | ||||||
| 		"crc-32": "1.2.0", | 		"crc-32": "1.2.0", | ||||||
| 		"css-loader": "3.0.0", | 		"css-loader": "3.2.0", | ||||||
| 		"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", | ||||||
| 		"diskusage": "1.1.3", | 		"diskusage": "1.1.3", | ||||||
| 		"double-ended-queue": "2.1.0-0", | 		"double-ended-queue": "2.1.0-0", | ||||||
| 		"emojilib": "2.4.0", | 		"emojilib": "2.4.0", | ||||||
| 		"eslint": "6.0.1", | 		"eslint": "6.1.0", | ||||||
| 		"eslint-plugin-vue": "5.2.3", | 		"eslint-plugin-vue": "5.2.3", | ||||||
| 		"eventemitter3": "4.0.0", | 		"eventemitter3": "4.0.0", | ||||||
| 		"feed": "3.0.0", | 		"feed": "3.0.0", | ||||||
| 		"file-type": "12.0.1", | 		"file-type": "12.2.0", | ||||||
| 		"fluent-ffmpeg": "2.1.2", | 		"fluent-ffmpeg": "2.1.2", | ||||||
| 		"fuckadblock": "3.2.1", | 		"fuckadblock": "3.2.1", | ||||||
| 		"gulp": "4.0.2", | 		"gulp": "4.0.2", | ||||||
| 		"gulp-cssnano": "2.1.3", | 		"gulp-cssnano": "2.1.3", | ||||||
| 		"gulp-imagemin": "6.0.0", | 		"gulp-imagemin": "6.1.0", | ||||||
| 		"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", | ||||||
| @@ -145,6 +146,7 @@ | |||||||
| 		"hard-source-webpack-plugin": "0.13.1", | 		"hard-source-webpack-plugin": "0.13.1", | ||||||
| 		"html-minifier": "4.0.0", | 		"html-minifier": "4.0.0", | ||||||
| 		"http-signature": "1.2.0", | 		"http-signature": "1.2.0", | ||||||
|  | 		"https-proxy-agent": "2.2.2", | ||||||
| 		"insert-text-at-cursor": "0.3.0", | 		"insert-text-at-cursor": "0.3.0", | ||||||
| 		"is-root": "2.1.0", | 		"is-root": "2.1.0", | ||||||
| 		"is-svg": "4.2.0", | 		"is-svg": "4.2.0", | ||||||
| @@ -153,13 +155,13 @@ | |||||||
| 		"json5": "2.1.0", | 		"json5": "2.1.0", | ||||||
| 		"json5-loader": "3.0.0", | 		"json5-loader": "3.0.0", | ||||||
| 		"jsrsasign": "8.0.12", | 		"jsrsasign": "8.0.12", | ||||||
| 		"katex": "0.10.2", | 		"katex": "0.11.0", | ||||||
| 		"koa": "2.7.0", | 		"koa": "2.8.1", | ||||||
| 		"koa-bodyparser": "4.2.1", | 		"koa-bodyparser": "4.2.1", | ||||||
| 		"koa-compress": "3.0.0", | 		"koa-compress": "3.0.0", | ||||||
| 		"koa-favicon": "2.0.1", | 		"koa-favicon": "2.0.1", | ||||||
| 		"koa-json-body": "5.3.0", | 		"koa-json-body": "5.3.0", | ||||||
| 		"koa-logger": "3.2.0", | 		"koa-logger": "3.2.1", | ||||||
| 		"koa-mount": "4.0.0", | 		"koa-mount": "4.0.0", | ||||||
| 		"koa-multer": "1.0.2", | 		"koa-multer": "1.0.2", | ||||||
| 		"koa-router": "7.4.0", | 		"koa-router": "7.4.0", | ||||||
| @@ -168,38 +170,35 @@ | |||||||
| 		"koa-views": "6.2.0", | 		"koa-views": "6.2.0", | ||||||
| 		"langmap": "0.0.16", | 		"langmap": "0.0.16", | ||||||
| 		"loader-utils": "1.2.3", | 		"loader-utils": "1.2.3", | ||||||
| 		"lolex": "4.1.0", | 		"lolex": "4.2.0", | ||||||
| 		"lookup-dns-cache": "2.1.0", | 		"lookup-dns-cache": "2.1.0", | ||||||
| 		"minio": "7.0.10", | 		"mocha": "6.2.0", | ||||||
| 		"mocha": "6.1.4", |  | ||||||
| 		"moji": "0.5.1", | 		"moji": "0.5.1", | ||||||
| 		"moment": "2.24.0", |  | ||||||
| 		"ms": "2.1.2", | 		"ms": "2.1.2", | ||||||
| 		"nested-property": "1.0.1", | 		"nested-property": "1.0.1", | ||||||
| 		"node-fetch": "2.6.0", | 		"node-fetch": "2.6.0", | ||||||
| 		"nodemailer": "6.2.1", | 		"nodemailer": "6.3.0", | ||||||
| 		"nprogress": "0.2.0", | 		"nprogress": "0.2.0", | ||||||
| 		"object-assign-deep": "0.4.0", | 		"object-assign-deep": "0.4.0", | ||||||
| 		"os-utils": "0.0.14", | 		"os-utils": "0.0.14", | ||||||
| 		"parse5": "5.1.0", | 		"parse5": "5.1.0", | ||||||
| 		"parsimmon": "1.12.1", | 		"parsimmon": "1.13.0", | ||||||
| 		"pg": "7.11.0", | 		"pg": "7.12.1", | ||||||
| 		"portscanner": "2.2.0", | 		"portscanner": "2.2.0", | ||||||
| 		"postcss-loader": "3.0.0", | 		"postcss-loader": "3.0.0", | ||||||
| 		"prismjs": "1.16.0", | 		"prismjs": "1.17.1", | ||||||
| 		"progress-bar-webpack-plugin": "1.12.1", | 		"progress-bar-webpack-plugin": "1.12.1", | ||||||
| 		"promise-any": "0.2.0", |  | ||||||
| 		"promise-limit": "2.7.0", | 		"promise-limit": "2.7.0", | ||||||
| 		"promise-sequential": "1.1.1", | 		"promise-sequential": "1.1.1", | ||||||
| 		"pug": "2.0.4", | 		"pug": "2.0.4", | ||||||
| 		"punycode": "2.1.1", | 		"punycode": "2.1.1", | ||||||
| 		"pureimage": "0.1.6", | 		"pureimage": "0.1.6", | ||||||
| 		"qrcode": "1.4.0", | 		"qrcode": "1.4.1", | ||||||
| 		"random-seed": "0.3.0", | 		"random-seed": "0.3.0", | ||||||
| 		"randomcolor": "0.5.4", | 		"randomcolor": "0.5.4", | ||||||
| 		"ratelimiter": "3.3.0", | 		"ratelimiter": "3.3.1", | ||||||
| 		"recaptcha-promise": "0.1.3", | 		"recaptcha-promise": "0.1.3", | ||||||
| 		"reconnecting-websocket": "4.1.10", | 		"reconnecting-websocket": "4.2.0", | ||||||
| 		"redis": "2.8.0", | 		"redis": "2.8.0", | ||||||
| 		"reflect-metadata": "0.1.13", | 		"reflect-metadata": "0.1.13", | ||||||
| 		"rename": "1.0.4", | 		"rename": "1.0.4", | ||||||
| @@ -210,20 +209,22 @@ | |||||||
| 		"rimraf": "2.6.3", | 		"rimraf": "2.6.3", | ||||||
| 		"rndstr": "1.0.0", | 		"rndstr": "1.0.0", | ||||||
| 		"s-age": "1.1.2", | 		"s-age": "1.1.2", | ||||||
| 		"seedrandom": "3.0.1", | 		"seedrandom": "3.0.3", | ||||||
| 		"sharp": "0.22.1", | 		"sharp": "0.23.0", | ||||||
| 		"showdown": "1.9.0", | 		"showdown": "1.9.0", | ||||||
| 		"showdown-highlightjs-extension": "0.1.2", | 		"showdown-highlightjs-extension": "0.1.2", | ||||||
| 		"speakeasy": "2.0.0", | 		"speakeasy": "2.0.0", | ||||||
| 		"stringz": "2.0.0", | 		"stringz": "2.0.0", | ||||||
| 		"style-loader": "0.23.1", | 		"style-loader": "0.23.1", | ||||||
| 		"stylus": "0.54.5", | 		"stylus": "0.54.7", | ||||||
| 		"stylus-loader": "3.0.2", | 		"stylus-loader": "3.0.2", | ||||||
| 		"summaly": "2.3.0", | 		"summaly": "2.3.1", | ||||||
| 		"systeminformation": "4.14.3", | 		"syslog-pro": "1.0.0", | ||||||
|  | 		"systeminformation": "4.14.8", | ||||||
| 		"syuilo-password-strength": "0.0.1", | 		"syuilo-password-strength": "0.0.1", | ||||||
| 		"terser-webpack-plugin": "1.3.0", | 		"terser-webpack-plugin": "1.4.1", | ||||||
| 		"textarea-caret": "3.1.0", | 		"textarea-caret": "3.1.0", | ||||||
|  | 		"three": "0.108.0", | ||||||
| 		"tinycolor2": "1.4.1", | 		"tinycolor2": "1.4.1", | ||||||
| 		"tmp": "0.1.0", | 		"tmp": "0.1.0", | ||||||
| 		"ts-loader": "5.3.3", | 		"ts-loader": "5.3.3", | ||||||
| @@ -231,37 +232,37 @@ | |||||||
| 		"tslint": "5.18.0", | 		"tslint": "5.18.0", | ||||||
| 		"tslint-sonarts": "1.9.0", | 		"tslint-sonarts": "1.9.0", | ||||||
| 		"typeorm": "0.2.18", | 		"typeorm": "0.2.18", | ||||||
| 		"typescript": "3.5.2", | 		"typescript": "3.5.3", | ||||||
| 		"uglify-es": "3.3.9", | 		"uglify-es": "3.3.9", | ||||||
| 		"ulid": "2.3.0", | 		"ulid": "2.3.0", | ||||||
| 		"url-loader": "2.0.1", | 		"url-loader": "2.1.0", | ||||||
| 		"uuid": "3.3.2", | 		"uuid": "3.3.3", | ||||||
| 		"v-animate-css": "0.0.3", | 		"v-animate-css": "0.0.3", | ||||||
| 		"v-debounce": "0.1.2", | 		"v-debounce": "0.1.2", | ||||||
| 		"vue": "2.6.10", | 		"vue": "2.6.10", | ||||||
| 		"vue-color": "2.7.0", | 		"vue-color": "2.7.0", | ||||||
| 		"vue-content-loading": "1.6.0", | 		"vue-content-loading": "1.6.0", | ||||||
| 		"vue-cropperjs": "4.0.0", | 		"vue-cropperjs": "4.0.0", | ||||||
| 		"vue-i18n": "8.12.0", | 		"vue-i18n": "8.14.0", | ||||||
| 		"vue-js-modal": "1.3.31", | 		"vue-js-modal": "1.3.31", | ||||||
| 		"vue-json-pretty": "1.6.0", | 		"vue-json-pretty": "1.6.1", | ||||||
| 		"vue-loader": "15.7.0", | 		"vue-loader": "15.7.1", | ||||||
| 		"vue-marquee-text-component": "1.1.1", | 		"vue-marquee-text-component": "1.1.1", | ||||||
| 		"vue-prism-component": "1.1.1", | 		"vue-prism-component": "1.1.1", | ||||||
| 		"vue-router": "3.0.7", | 		"vue-router": "3.1.2", | ||||||
| 		"vue-sequential-entrance": "1.1.3", | 		"vue-sequential-entrance": "1.1.3", | ||||||
| 		"vue-style-loader": "4.1.2", | 		"vue-style-loader": "4.1.2", | ||||||
| 		"vue-svg-inline-loader": "1.2.16", | 		"vue-svg-inline-loader": "1.2.17", | ||||||
| 		"vue-template-compiler": "2.6.10", | 		"vue-template-compiler": "2.6.10", | ||||||
| 		"vuedraggable": "2.23.0", | 		"vuedraggable": "2.23.0", | ||||||
| 		"vuewordcloud": "18.7.11", | 		"vuewordcloud": "18.7.11", | ||||||
| 		"vuex": "3.1.1", | 		"vuex": "3.1.1", | ||||||
| 		"vuex-persistedstate": "2.5.4", | 		"vuex-persistedstate": "2.5.4", | ||||||
| 		"web-push": "3.3.5", | 		"web-push": "3.3.5", | ||||||
| 		"webpack": "4.35.3", | 		"webpack": "4.39.3", | ||||||
| 		"webpack-cli": "3.3.5", | 		"webpack-cli": "3.3.7", | ||||||
| 		"websocket": "1.0.29", | 		"websocket": "1.0.29", | ||||||
| 		"ws": "7.1.0", | 		"ws": "7.1.2", | ||||||
| 		"xev": "2.0.1" | 		"xev": "2.0.1" | ||||||
| 	}, | 	}, | ||||||
| 	"devDependencies": { | 	"devDependencies": { | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								src/@types/lookup-dns-cache.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								src/@types/lookup-dns-cache.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -1,17 +1,9 @@ | |||||||
| declare module 'lookup-dns-cache' { | declare module 'lookup-dns-cache' { | ||||||
| 	type IPv4 = 4; | 	import { LookupOneOptions, LookupAllOptions, LookupOptions, LookupAddress } from 'dns'; | ||||||
|  |  | ||||||
| 	type IPv6 = 6; | 	function lookup(hostname: string, family: number, callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void): void; | ||||||
|  | 	function lookup(hostname: string, options: LookupOneOptions, callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void): void; | ||||||
| 	type Family = IPv4 | IPv6 | undefined; | 	function lookup(hostname: string, options: LookupAllOptions, callback: (err: NodeJS.ErrnoException | null, addresses: LookupAddress[]) => void): void; | ||||||
|  | 	function lookup(hostname: string, options: LookupOptions, callback: (err: NodeJS.ErrnoException | null, address: string | LookupAddress[], family: number) => void): void; | ||||||
| 	interface IRunOptions { | 	function lookup(hostname: string, callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void): void; | ||||||
| 		family?: Family; |  | ||||||
| 		all?: boolean; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	type RunCallback = (error: Error | null, address?: string | string[], family?: Family) => void; |  | ||||||
|  |  | ||||||
| 	export function lookup(hostname: string, options: IRunOptions | Family, callback: RunCallback): {} | undefined; |  | ||||||
| 	export function lookup(hostname: string, callback: RunCallback): {} | undefined; |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								src/@types/promise-any.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								src/@types/promise-any.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -1,7 +0,0 @@ | |||||||
| declare module 'promise-any' { |  | ||||||
| 	function promiseAny<T>(iterable: Iterable<T | PromiseLike<T>>): Promise<T>; |  | ||||||
|  |  | ||||||
| 	namespace promiseAny {} // Hack |  | ||||||
|  |  | ||||||
| 	export = promiseAny; |  | ||||||
| } |  | ||||||
| @@ -159,7 +159,7 @@ async function init(): Promise<Config> { | |||||||
| 	return config; | 	return config; | ||||||
| } | } | ||||||
|  |  | ||||||
| async function spawnWorkers(limit: number = Infinity) { | async function spawnWorkers(limit: number = 1) { | ||||||
| 	const workers = Math.min(limit, os.cpus().length); | 	const workers = Math.min(limit, os.cpus().length); | ||||||
| 	bootLogger.info(`Starting ${workers} worker${workers === 1 ? '' : 's'}...`); | 	bootLogger.info(`Starting ${workers} worker${workers === 1 ? '' : 's'}...`); | ||||||
| 	await Promise.all([...Array(workers)].map(spawnWorker)); | 	await Promise.all([...Array(workers)].map(spawnWorker)); | ||||||
|   | |||||||
| @@ -19,7 +19,8 @@ init(launch => { | |||||||
| 		mode: 'history', | 		mode: 'history', | ||||||
| 		base: '/admin/', | 		base: '/admin/', | ||||||
| 		routes: [ | 		routes: [ | ||||||
| 			{ path: '/', component: Index }, | 			{ path: '/:page', component: Index }, | ||||||
|  | 			{ path: '/', redirect: '/dashboard' }, | ||||||
| 			{ path: '*', component: NotFound } | 			{ path: '*', component: NotFound } | ||||||
| 		] | 		] | ||||||
| 	}); | 	}); | ||||||
|   | |||||||
| @@ -18,18 +18,18 @@ | |||||||
| 			<p class="name"><mk-user-name :user="$store.state.i"/></p> | 			<p class="name"><mk-user-name :user="$store.state.i"/></p> | ||||||
| 		</div> | 		</div> | ||||||
| 		<ul> | 		<ul> | ||||||
| 			<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }"><fa icon="home" fixed-width/>{{ $t('dashboard') }}</li> | 			<li><router-link to="/dashboard" active-class="active"><fa icon="home" fixed-width/>{{ $t('dashboard') }}</router-link></li> | ||||||
| 			<li @click="nav('instance')" :class="{ active: page == 'instance' }"><fa icon="cog" fixed-width/>{{ $t('instance') }}</li> | 			<li><router-link to="/instance" active-class="active"><fa icon="cog" fixed-width/>{{ $t('instance') }}</router-link></li> | ||||||
| 			<li @click="nav('queue')" :class="{ active: page == 'queue' }"><fa :icon="faTasks" fixed-width/>{{ $t('queue') }}</li> | 			<li><router-link to="/queue" active-class="active"><fa :icon="faTasks" fixed-width/>{{ $t('queue') }}</router-link></li> | ||||||
| 			<li @click="nav('logs')" :class="{ active: page == 'logs' }"><fa :icon="faStream" fixed-width/>{{ $t('logs') }}</li> | 			<li><router-link to="/logs" active-class="active"><fa :icon="faStream" fixed-width/>{{ $t('logs') }}</router-link></li> | ||||||
| 			<li @click="nav('db')" :class="{ active: page == 'db' }"><fa :icon="faDatabase" fixed-width/>{{ $t('db') }}</li> | 			<li><router-link to="/db" active-class="active"><fa :icon="faDatabase" fixed-width/>{{ $t('db') }}</router-link></li> | ||||||
| 			<li @click="nav('moderators')" :class="{ active: page == 'moderators' }"><fa :icon="faHeadset" fixed-width/>{{ $t('moderators') }}</li> | 			<li><router-link to="/moderators" active-class="active"><fa :icon="faHeadset" fixed-width/>{{ $t('moderators') }}</router-link></li> | ||||||
| 			<li @click="nav('users')" :class="{ active: page == 'users' }"><fa icon="users" fixed-width/>{{ $t('users') }}</li> | 			<li><router-link to="/users" active-class="active"><fa icon="users" fixed-width/>{{ $t('users') }}</router-link></li> | ||||||
| 			<li @click="nav('drive')" :class="{ active: page == 'drive' }"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</li> | 			<li><router-link to="/drive" active-class="active"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</router-link></li> | ||||||
| 			<li @click="nav('federation')" :class="{ active: page == 'federation' }"><fa :icon="faGlobe" fixed-width/>{{ $t('federation') }}</li> | 			<li><router-link to="/federation" active-class="active"><fa :icon="faGlobe" fixed-width/>{{ $t('federation') }}</router-link></li> | ||||||
| 			<li @click="nav('emoji')" :class="{ active: page == 'emoji' }"><fa :icon="faGrin" fixed-width/>{{ $t('emoji') }}</li> | 			<li><router-link to="/emoji" active-class="active"><fa :icon="faGrin" fixed-width/>{{ $t('emoji') }}</router-link></li> | ||||||
| 			<li @click="nav('announcements')" :class="{ active: page == 'announcements' }"><fa icon="broadcast-tower" fixed-width/>{{ $t('announcements') }}</li> | 			<li><router-link to="/announcements" active-class="active"><fa icon="broadcast-tower" fixed-width/>{{ $t('announcements') }}</router-link></li> | ||||||
| 			<li @click="nav('abuse')" :class="{ active: page == 'abuse' }"><fa :icon="faExclamationCircle" fixed-width/>{{ $t('abuse') }}</li> | 			<li><router-link to="/abuse" active-class="active"><fa :icon="faExclamationCircle" fixed-width/>{{ $t('abuse') }}</router-link></li> | ||||||
| 		</ul> | 		</ul> | ||||||
| 		<div class="back-to-misskey"> | 		<div class="back-to-misskey"> | ||||||
| 			<a href="/"><fa :icon="faArrowLeft"/> {{ $t('back-to-misskey') }}</a> | 			<a href="/"><fa :icon="faArrowLeft"/> {{ $t('back-to-misskey') }}</a> | ||||||
| @@ -102,7 +102,6 @@ export default Vue.extend({ | |||||||
| 	}, | 	}, | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			page: 'dashboard', |  | ||||||
| 			version, | 			version, | ||||||
| 			isMobile, | 			isMobile, | ||||||
| 			navOpend: !isMobile, | 			navOpend: !isMobile, | ||||||
| @@ -116,9 +115,9 @@ export default Vue.extend({ | |||||||
| 			faDatabase, | 			faDatabase, | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| 	methods: { | 	computed: { | ||||||
| 		nav(page: string) { | 		page() { | ||||||
| 			this.page = page; | 			return this.$route.params.page; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| @@ -240,11 +239,10 @@ export default Vue.extend({ | |||||||
| 			list-style none | 			list-style none | ||||||
| 			font-size 15px | 			font-size 15px | ||||||
|  |  | ||||||
| 			> li | 			> li > a | ||||||
| 				display block | 				display block | ||||||
| 				padding 10px 16px | 				padding 10px 16px | ||||||
| 				margin 0 | 				margin 0 | ||||||
| 				cursor pointer |  | ||||||
| 				user-select none | 				user-select none | ||||||
| 				color #eee | 				color #eee | ||||||
| 				transition margin-left 0.2s ease | 				transition margin-left 0.2s ease | ||||||
|   | |||||||
| @@ -3,6 +3,8 @@ import { sum, unique } from '../../../../prelude/array'; | |||||||
| import shouldMuteNote from './should-mute-note'; | import shouldMuteNote from './should-mute-note'; | ||||||
| import MkNoteMenu from '../views/components/note-menu.vue'; | import MkNoteMenu from '../views/components/note-menu.vue'; | ||||||
| import MkReactionPicker from '../views/components/reaction-picker.vue'; | import MkReactionPicker from '../views/components/reaction-picker.vue'; | ||||||
|  | import pleaseLogin from './please-login'; | ||||||
|  | import i18n from '../../i18n'; | ||||||
|  |  | ||||||
| function focus(el, fn) { | function focus(el, fn) { | ||||||
| 	const target = fn(el); | 	const target = fn(el); | ||||||
| @@ -20,10 +22,13 @@ type Opts = { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| export default (opts: Opts = {}) => ({ | export default (opts: Opts = {}) => ({ | ||||||
|  | 	i18n: i18n(), | ||||||
|  |  | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			showContent: false, | 			showContent: false, | ||||||
| 			hideThisNote: false | 			hideThisNote: false, | ||||||
|  | 			openingMenu: false | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -108,6 +113,7 @@ export default (opts: Opts = {}) => ({ | |||||||
|  |  | ||||||
| 	methods: { | 	methods: { | ||||||
| 		reply(viaKeyboard = false) { | 		reply(viaKeyboard = false) { | ||||||
|  | 			pleaseLogin(this.$root); | ||||||
| 			this.$root.$post({ | 			this.$root.$post({ | ||||||
| 				reply: this.appearNote, | 				reply: this.appearNote, | ||||||
| 				animation: !viaKeyboard, | 				animation: !viaKeyboard, | ||||||
| @@ -118,6 +124,7 @@ export default (opts: Opts = {}) => ({ | |||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		renote(viaKeyboard = false) { | 		renote(viaKeyboard = false) { | ||||||
|  | 			pleaseLogin(this.$root); | ||||||
| 			this.$root.$post({ | 			this.$root.$post({ | ||||||
| 				renote: this.appearNote, | 				renote: this.appearNote, | ||||||
| 				animation: !viaKeyboard, | 				animation: !viaKeyboard, | ||||||
| @@ -134,6 +141,7 @@ export default (opts: Opts = {}) => ({ | |||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		react(viaKeyboard = false) { | 		react(viaKeyboard = false) { | ||||||
|  | 			pleaseLogin(this.$root); | ||||||
| 			this.blur(); | 			this.blur(); | ||||||
| 			this.$root.new(MkReactionPicker, { | 			this.$root.new(MkReactionPicker, { | ||||||
| 				source: this.$refs.reactButton, | 				source: this.$refs.reactButton, | ||||||
| @@ -159,6 +167,7 @@ export default (opts: Opts = {}) => ({ | |||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		favorite() { | 		favorite() { | ||||||
|  | 			pleaseLogin(this.$root); | ||||||
| 			this.$root.api('notes/favorites/create', { | 			this.$root.api('notes/favorites/create', { | ||||||
| 				noteId: this.appearNote.id | 				noteId: this.appearNote.id | ||||||
| 			}).then(() => { | 			}).then(() => { | ||||||
| @@ -170,17 +179,30 @@ export default (opts: Opts = {}) => ({ | |||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		del() { | 		del() { | ||||||
|  | 			this.$root.dialog({ | ||||||
|  | 				type: 'warning', | ||||||
|  | 				text: this.$t('@.delete-confirm'), | ||||||
|  | 				showCancelButton: true | ||||||
|  | 			}).then(({ canceled }) => { | ||||||
|  | 				if (canceled) return; | ||||||
|  |  | ||||||
| 				this.$root.api('notes/delete', { | 				this.$root.api('notes/delete', { | ||||||
| 					noteId: this.appearNote.id | 					noteId: this.appearNote.id | ||||||
| 				}); | 				}); | ||||||
|  | 			}); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		menu(viaKeyboard = false) { | 		menu(viaKeyboard = false) { | ||||||
|  | 			if (this.openingMenu) return; | ||||||
|  | 			this.openingMenu = true; | ||||||
| 			this.$root.new(MkNoteMenu, { | 			this.$root.new(MkNoteMenu, { | ||||||
| 				source: this.$refs.menuButton, | 				source: this.$refs.menuButton, | ||||||
| 				note: this.appearNote, | 				note: this.appearNote, | ||||||
| 				animation: !viaKeyboard | 				animation: !viaKeyboard | ||||||
| 			}).$once('closed', this.focus); | 			}).$once('closed', () => { | ||||||
|  | 				this.openingMenu = false; | ||||||
|  | 				this.focus(); | ||||||
|  | 			}); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		toggleShowContent() { | 		toggleShowContent() { | ||||||
|   | |||||||
| @@ -44,13 +44,21 @@ export default (opts) => ({ | |||||||
| 				return window.scrollY <= 8; | 				return window.scrollY <= 8; | ||||||
| 			}; | 			}; | ||||||
|  |  | ||||||
| 			window.addEventListener('scroll', this.onWindowScroll, { passive: true }); | 			window.addEventListener('scroll', this.onScroll, { passive: true }); | ||||||
|  | 		} else if (opts.isContainer) { | ||||||
|  | 			this.isScrollTop = () => { | ||||||
|  | 				return this.$el.scrollTop <= 8; | ||||||
|  | 			}; | ||||||
|  |  | ||||||
|  | 			this.$el.addEventListener('scroll', this.onScroll, { passive: true }); | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	beforeDestroy() { | 	beforeDestroy() { | ||||||
| 		if (opts.captureWindowScroll) { | 		if (opts.captureWindowScroll) { | ||||||
| 			window.removeEventListener('scroll', this.onWindowScroll); | 			window.removeEventListener('scroll', this.onScroll); | ||||||
|  | 		} else if (opts.isContainer) { | ||||||
|  | 			this.$el.removeEventListener('scroll', this.onScroll); | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -67,6 +75,7 @@ export default (opts) => ({ | |||||||
|  |  | ||||||
| 		async init() { | 		async init() { | ||||||
| 			this.fetching = true; | 			this.fetching = true; | ||||||
|  | 			if (opts.beforeInit) opts.beforeInit(this); | ||||||
| 			let params = typeof this.pagination.params === 'function' ? this.pagination.params(true) : this.pagination.params; | 			let params = typeof this.pagination.params === 'function' ? this.pagination.params(true) : this.pagination.params; | ||||||
| 			if (params && params.then) params = await params; | 			if (params && params.then) params = await params; | ||||||
| 			await this.$root.api(this.pagination.endpoint, { | 			await this.$root.api(this.pagination.endpoint, { | ||||||
| @@ -151,7 +160,7 @@ export default (opts) => ({ | |||||||
| 			this.queue = []; | 			this.queue = []; | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		onWindowScroll() { | 		onScroll() { | ||||||
| 			if (this.isScrollTop()) { | 			if (this.isScrollTop()) { | ||||||
| 				this.onTop(); | 				this.onTop(); | ||||||
| 			} | 			} | ||||||
| @@ -162,8 +171,10 @@ export default (opts) => ({ | |||||||
| 				// http://d.hatena.ne.jp/favril/20091105/1257403319 | 				// http://d.hatena.ne.jp/favril/20091105/1257403319 | ||||||
| 				if (this.$el.offsetHeight == 0) return; | 				if (this.$el.offsetHeight == 0) return; | ||||||
|  |  | ||||||
| 				const current = window.scrollY + window.innerHeight; | 				const bottomPosition = opts.isContainer ? this.$el.scrollHeight : document.body.offsetHeight; | ||||||
| 				if (current > document.body.offsetHeight - 8) this.onBottom(); |  | ||||||
|  | 				const currentBottomPosition = opts.isContainer ? this.$el.scrollTop + this.$el.clientHeight : window.scrollY + window.innerHeight; | ||||||
|  | 				if (currentBottomPosition > (bottomPosition - 8)) this.onBottom(); | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								src/client/app/common/scripts/please-login.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/client/app/common/scripts/please-login.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | export default ($root: any) => { | ||||||
|  | 	if ($root.$store.getters.isSignedIn) return; | ||||||
|  |  | ||||||
|  | 	$root.dialog({ | ||||||
|  | 		title: $root.$t('@.signin-required'), | ||||||
|  | 		text: null | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	throw new Error('signin required'); | ||||||
|  | }; | ||||||
| @@ -35,6 +35,10 @@ export default (opts) => ({ | |||||||
| 			type: String, | 			type: String, | ||||||
| 			required: false | 			required: false | ||||||
| 		}, | 		}, | ||||||
|  | 		initialNote: { | ||||||
|  | 			type: Object, | ||||||
|  | 			required: false | ||||||
|  | 		}, | ||||||
| 		instant: { | 		instant: { | ||||||
| 			type: Boolean, | 			type: Boolean, | ||||||
| 			required: false, | 			required: false, | ||||||
| @@ -149,6 +153,10 @@ export default (opts) => ({ | |||||||
| 		// デフォルト公開範囲 | 		// デフォルト公開範囲 | ||||||
| 		this.applyVisibility(this.$store.state.settings.rememberNoteVisibility ? (this.$store.state.device.visibility || this.$store.state.settings.defaultNoteVisibility) : this.$store.state.settings.defaultNoteVisibility); | 		this.applyVisibility(this.$store.state.settings.rememberNoteVisibility ? (this.$store.state.device.visibility || this.$store.state.settings.defaultNoteVisibility) : this.$store.state.settings.defaultNoteVisibility); | ||||||
|  |  | ||||||
|  | 		if (this.reply && this.reply.localOnly) { | ||||||
|  | 			this.localOnly = true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		// 公開以外へのリプライ時は元の公開範囲を引き継ぐ | 		// 公開以外へのリプライ時は元の公開範囲を引き継ぐ | ||||||
| 		if (this.reply && ['home', 'followers', 'specified'].includes(this.reply.visibility)) { | 		if (this.reply && ['home', 'followers', 'specified'].includes(this.reply.visibility)) { | ||||||
| 			this.visibility = this.reply.visibility; | 			this.visibility = this.reply.visibility; | ||||||
| @@ -158,14 +166,14 @@ export default (opts) => ({ | |||||||
| 				}).then(users => { | 				}).then(users => { | ||||||
| 					this.visibleUsers.push(...users); | 					this.visibleUsers.push(...users); | ||||||
| 				}); | 				}); | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if (this.reply && this.reply.userId !== this.$store.state.i.id) { | 				if (this.reply.userId !== this.$store.state.i.id) { | ||||||
| 					this.$root.api('users/show', { userId: this.reply.userId }).then(user => { | 					this.$root.api('users/show', { userId: this.reply.userId }).then(user => { | ||||||
| 						this.visibleUsers.push(user); | 						this.visibleUsers.push(user); | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		// keep cw when reply | 		// keep cw when reply | ||||||
| 		if (this.$store.state.settings.keepCw && this.reply && this.reply.cw) { | 		if (this.$store.state.settings.keepCw && this.reply && this.reply.cw) { | ||||||
| @@ -196,6 +204,29 @@ export default (opts) => ({ | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			// 削除して編集 | ||||||
|  | 			if (this.initialNote) { | ||||||
|  | 				const init = this.initialNote; | ||||||
|  | 				this.text = init.text ? init.text : ''; | ||||||
|  | 				this.files = init.files; | ||||||
|  | 				this.cw = init.cw; | ||||||
|  | 				this.useCw = init.cw != null; | ||||||
|  | 				if (init.poll) { | ||||||
|  | 					this.poll = true; | ||||||
|  | 					this.$nextTick(() => { | ||||||
|  | 						(this.$refs.poll as any).set({ | ||||||
|  | 							choices: init.poll.choices.map(c => c.text), | ||||||
|  | 							multiple: init.poll.multiple | ||||||
|  | 						}); | ||||||
|  | 					}); | ||||||
|  | 				} | ||||||
|  | 				// hack 位置情報投稿が動くようになったら適用する | ||||||
|  | 				this.geo = null; | ||||||
|  | 				this.visibility = init.visibility; | ||||||
|  | 				this.localOnly = init.localOnly; | ||||||
|  | 				this.quoteId = init.renote ? init.renote.id : null; | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			this.$nextTick(() => this.watch()); | 			this.$nextTick(() => this.watch()); | ||||||
| 		}); | 		}); | ||||||
| 	}, | 	}, | ||||||
| @@ -292,7 +323,7 @@ export default (opts) => ({ | |||||||
| 		setVisibility() { | 		setVisibility() { | ||||||
| 			const w = this.$root.new(MkVisibilityChooser, { | 			const w = this.$root.new(MkVisibilityChooser, { | ||||||
| 				source: this.$refs.visibilityButton, | 				source: this.$refs.visibilityButton, | ||||||
| 				currentVisibility: this.visibility | 				currentVisibility: this.localOnly ? `local-${this.visibility}` : this.visibility | ||||||
| 			}); | 			}); | ||||||
| 			w.$once('chosen', v => { | 			w.$once('chosen', v => { | ||||||
| 				this.applyVisibility(v); | 				this.applyVisibility(v); | ||||||
|   | |||||||
							
								
								
									
										21
									
								
								src/client/app/common/scripts/room/furniture.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/client/app/common/scripts/room/furniture.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | export type RoomInfo = { | ||||||
|  | 	roomType: string; | ||||||
|  | 	carpetColor: string; | ||||||
|  | 	furnitures: Furniture[]; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export type Furniture = { | ||||||
|  | 	id: string; // 同じ家具が複数ある場合にそれぞれを識別するためのIDであり、家具IDではない | ||||||
|  | 	type: string; // こっちが家具ID(chairとか) | ||||||
|  | 	position: { | ||||||
|  | 		x: number; | ||||||
|  | 		y: number; | ||||||
|  | 		z: number; | ||||||
|  | 	}; | ||||||
|  | 	rotation: { | ||||||
|  | 		x: number; | ||||||
|  | 		y: number; | ||||||
|  | 		z: number; | ||||||
|  | 	}; | ||||||
|  | 	props?: Record<string, any>; | ||||||
|  | }; | ||||||
							
								
								
									
										397
									
								
								src/client/app/common/scripts/room/furnitures.json5
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										397
									
								
								src/client/app/common/scripts/room/furnitures.json5
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,397 @@ | |||||||
|  | // 家具メタデータ | ||||||
|  |  | ||||||
|  | // 家具にはユーザーが設定できるプロパティを設定可能です: | ||||||
|  | // | ||||||
|  | // props: { | ||||||
|  | //   <propname>: <proptype> | ||||||
|  | // } | ||||||
|  | // | ||||||
|  | // proptype一覧: | ||||||
|  | // * image ... 画像選択ダイアログを出し、その画像のURLが格納されます | ||||||
|  | // * color ... 色選択コントロールを出し、選択された色が格納されます | ||||||
|  |  | ||||||
|  | // 家具にカスタムテクスチャを適用できるようにするには、textureプロパティに以下の追加の情報を含めます: | ||||||
|  | // 便宜上そのUVのどの部分にカスタムテクスチャを貼り合わせるかのエリアをテクスチャエリアと呼びます。 | ||||||
|  | // UVは1024*1024だと仮定します。 | ||||||
|  | // | ||||||
|  | // <key>: { | ||||||
|  | //   prop: <プロパティ名>, | ||||||
|  | //   uv: { | ||||||
|  | //     x: <テクスチャエリアX座標>, | ||||||
|  | //     y: <テクスチャエリアY座標>, | ||||||
|  | //     width: <テクスチャエリアの幅>, | ||||||
|  | //     height: <テクスチャエリアの高さ>, | ||||||
|  | //   }, | ||||||
|  | // } | ||||||
|  | // | ||||||
|  | // <key>には、カスタムテクスチャを適用したいメッシュ名を指定します | ||||||
|  | // <プロパティ名>には、カスタムテクスチャとして使用する画像を格納するプロパティ(前述)名を指定します | ||||||
|  |  | ||||||
|  | // 家具にカスタムカラーを適用できるようにするには、colorプロパティに以下の追加の情報を含めます: | ||||||
|  | // | ||||||
|  | // <key>: <プロパティ名> | ||||||
|  | // | ||||||
|  | // <key>には、カスタムカラーを適用したいマテリアル名を指定します | ||||||
|  | // <プロパティ名>には、カスタムカラーとして使用する色を格納するプロパティ(前述)名を指定します | ||||||
|  |  | ||||||
|  | [ | ||||||
|  | 	{ | ||||||
|  | 		id: "milk", | ||||||
|  | 		place: "floor" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "bed", | ||||||
|  | 		place: "floor" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "low-table", | ||||||
|  | 		place: "floor", | ||||||
|  | 		props: { | ||||||
|  | 			color: 'color' | ||||||
|  | 		}, | ||||||
|  | 		color: { | ||||||
|  | 			Table: 'color' | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "desk", | ||||||
|  | 		place: "floor", | ||||||
|  | 		props: { | ||||||
|  | 			color: 'color' | ||||||
|  | 		}, | ||||||
|  | 		color: { | ||||||
|  | 			Board: 'color' | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "chair", | ||||||
|  | 		place: "floor", | ||||||
|  | 		props: { | ||||||
|  | 			color: 'color' | ||||||
|  | 		}, | ||||||
|  | 		color: { | ||||||
|  | 			Chair: 'color' | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "chair2", | ||||||
|  | 		place: "floor", | ||||||
|  | 		props: { | ||||||
|  | 			color1: 'color', | ||||||
|  | 			color2: 'color' | ||||||
|  | 		}, | ||||||
|  | 		color: { | ||||||
|  | 			Cushion: 'color1', | ||||||
|  | 			Leg: 'color2' | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "fan", | ||||||
|  | 		place: "wall" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "pc", | ||||||
|  | 		place: "floor" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "plant", | ||||||
|  | 		place: "floor" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "plant2", | ||||||
|  | 		place: "floor" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "eraser", | ||||||
|  | 		place: "floor" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "pencil", | ||||||
|  | 		place: "floor" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "pudding", | ||||||
|  | 		place: "floor" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "cardboard-box", | ||||||
|  | 		place: "floor" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "cardboard-box2", | ||||||
|  | 		place: "floor" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "cardboard-box3", | ||||||
|  | 		place: "floor" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "book", | ||||||
|  | 		place: "floor", | ||||||
|  | 		props: { | ||||||
|  | 			color: 'color' | ||||||
|  | 		}, | ||||||
|  | 		color: { | ||||||
|  | 			Cover: 'color' | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "book2", | ||||||
|  | 		place: "floor" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "piano", | ||||||
|  | 		place: "floor" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "facial-tissue", | ||||||
|  | 		place: "floor" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "server", | ||||||
|  | 		place: "floor" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "moon", | ||||||
|  | 		place: "floor" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "corkboard", | ||||||
|  | 		place: "wall" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "mousepad", | ||||||
|  | 		place: "floor", | ||||||
|  | 		props: { | ||||||
|  | 			color: 'color' | ||||||
|  | 		}, | ||||||
|  | 		color: { | ||||||
|  | 			Pad: 'color' | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "monitor", | ||||||
|  | 		place: "floor", | ||||||
|  | 		props: { | ||||||
|  | 			screen: 'image' | ||||||
|  | 		}, | ||||||
|  | 		texture: { | ||||||
|  | 			Screen: { | ||||||
|  | 				prop: 'screen', | ||||||
|  | 				uv: { | ||||||
|  | 					x: 0, | ||||||
|  | 					y: 434, | ||||||
|  | 					width: 1024, | ||||||
|  | 					height: 588, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "tv", | ||||||
|  | 		place: "floor", | ||||||
|  | 		props: { | ||||||
|  | 			screen: 'image' | ||||||
|  | 		}, | ||||||
|  | 		texture: { | ||||||
|  | 			Screen: { | ||||||
|  | 				prop: 'screen', | ||||||
|  | 				uv: { | ||||||
|  | 					x: 0, | ||||||
|  | 					y: 434, | ||||||
|  | 					width: 1024, | ||||||
|  | 					height: 588, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "keyboard", | ||||||
|  | 		place: "floor" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "carpet-stripe", | ||||||
|  | 		place: "floor", | ||||||
|  | 		props: { | ||||||
|  | 			color1: 'color', | ||||||
|  | 			color2: 'color' | ||||||
|  | 		}, | ||||||
|  | 		color: { | ||||||
|  | 			CarpetAreaA: 'color1', | ||||||
|  | 			CarpetAreaB: 'color2' | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "mat", | ||||||
|  | 		place: "floor", | ||||||
|  | 		props: { | ||||||
|  | 			color: 'color' | ||||||
|  | 		}, | ||||||
|  | 		color: { | ||||||
|  | 			Mat: 'color' | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "color-box", | ||||||
|  | 		place: "floor", | ||||||
|  | 		props: { | ||||||
|  | 			color: 'color' | ||||||
|  | 		}, | ||||||
|  | 		color: { | ||||||
|  | 			main: 'color' | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "wall-clock", | ||||||
|  | 		place: "wall" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "cube", | ||||||
|  | 		place: "floor", | ||||||
|  | 		props: { | ||||||
|  | 			color: 'color' | ||||||
|  | 		}, | ||||||
|  | 		color: { | ||||||
|  | 			Cube: 'color' | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "photoframe", | ||||||
|  | 		place: "wall", | ||||||
|  | 		props: { | ||||||
|  | 			photo: 'image', | ||||||
|  | 			color: 'color' | ||||||
|  | 		}, | ||||||
|  | 		texture: { | ||||||
|  | 			Photo: { | ||||||
|  | 				prop: 'photo', | ||||||
|  | 				uv: { | ||||||
|  | 					x: 0, | ||||||
|  | 					y: 342, | ||||||
|  | 					width: 1024, | ||||||
|  | 					height: 683, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		color: { | ||||||
|  | 			Frame: 'color' | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "pinguin", | ||||||
|  | 		place: "floor", | ||||||
|  | 		props: { | ||||||
|  | 			body: 'color', | ||||||
|  | 			belly: 'color' | ||||||
|  | 		}, | ||||||
|  | 		color: { | ||||||
|  | 			Body: 'body', | ||||||
|  | 			Belly: 'belly', | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "rubik-cube", | ||||||
|  | 		place: "floor", | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "poster-h", | ||||||
|  | 		place: "wall", | ||||||
|  | 		props: { | ||||||
|  | 			picture: 'image' | ||||||
|  | 		}, | ||||||
|  | 		texture: { | ||||||
|  | 			Poster: { | ||||||
|  | 				prop: 'picture', | ||||||
|  | 				uv: { | ||||||
|  | 					x: 0, | ||||||
|  | 					y: 277, | ||||||
|  | 					width: 1024, | ||||||
|  | 					height: 745, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "poster-v", | ||||||
|  | 		place: "wall", | ||||||
|  | 		props: { | ||||||
|  | 			picture: 'image' | ||||||
|  | 		}, | ||||||
|  | 		texture: { | ||||||
|  | 			Poster: { | ||||||
|  | 				prop: 'picture', | ||||||
|  | 				uv: { | ||||||
|  | 					x: 0, | ||||||
|  | 					y: 0, | ||||||
|  | 					width: 745, | ||||||
|  | 					height: 1024, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "sofa", | ||||||
|  | 		place: "floor", | ||||||
|  | 		props: { | ||||||
|  | 			color: 'color' | ||||||
|  | 		}, | ||||||
|  | 		color: { | ||||||
|  | 			Sofa: 'color' | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "spiral", | ||||||
|  | 		place: "floor", | ||||||
|  | 		props: { | ||||||
|  | 			color: 'color' | ||||||
|  | 		}, | ||||||
|  | 		color: { | ||||||
|  | 			Step: 'color' | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "bin", | ||||||
|  | 		place: "floor", | ||||||
|  | 		props: { | ||||||
|  | 			color: 'color' | ||||||
|  | 		}, | ||||||
|  | 		color: { | ||||||
|  | 			Bin: 'color' | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "cup-noodle", | ||||||
|  | 		place: "floor" | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: "holo-display", | ||||||
|  | 		place: "floor", | ||||||
|  | 		props: { | ||||||
|  | 			image: 'image' | ||||||
|  | 		}, | ||||||
|  | 		texture: { | ||||||
|  | 			Image_Front: { | ||||||
|  | 				prop: 'image', | ||||||
|  | 				uv: { | ||||||
|  | 					x: 0, | ||||||
|  | 					y: 0, | ||||||
|  | 					width: 1024, | ||||||
|  | 					height: 1024, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			Image_Back: { | ||||||
|  | 				prop: 'image', | ||||||
|  | 				uv: { | ||||||
|  | 					x: 0, | ||||||
|  | 					y: 0, | ||||||
|  | 					width: 1024, | ||||||
|  | 					height: 1024, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		id: 'energy-drink', | ||||||
|  | 		place: "floor", | ||||||
|  | 	} | ||||||
|  | ] | ||||||
							
								
								
									
										776
									
								
								src/client/app/common/scripts/room/room.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										776
									
								
								src/client/app/common/scripts/room/room.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,776 @@ | |||||||
|  | import autobind from 'autobind-decorator'; | ||||||
|  | import { v4 as uuid } from 'uuid'; | ||||||
|  | import * as THREE from 'three'; | ||||||
|  | import { GLTFLoader, GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; | ||||||
|  | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; | ||||||
|  | import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'; | ||||||
|  | import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'; | ||||||
|  | import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'; | ||||||
|  | import { BloomPass } from 'three/examples/jsm/postprocessing/BloomPass.js'; | ||||||
|  | import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js'; | ||||||
|  | import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js'; | ||||||
|  | import { Furniture, RoomInfo } from './furniture'; | ||||||
|  | import { query as urlQuery } from '../../../../../prelude/url'; | ||||||
|  | const furnitureDefs = require('./furnitures.json5'); | ||||||
|  |  | ||||||
|  | THREE.ImageUtils.crossOrigin = ''; | ||||||
|  |  | ||||||
|  | type Options = { | ||||||
|  | 	graphicsQuality: Room['graphicsQuality']; | ||||||
|  | 	onChangeSelect: Room['onChangeSelect']; | ||||||
|  | 	useOrthographicCamera: boolean; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * MisskeyRoom Core Engine | ||||||
|  |  */ | ||||||
|  | export class Room { | ||||||
|  | 	private clock: THREE.Clock; | ||||||
|  | 	private scene: THREE.Scene; | ||||||
|  | 	private renderer: THREE.WebGLRenderer; | ||||||
|  | 	private camera: THREE.PerspectiveCamera | THREE.OrthographicCamera; | ||||||
|  | 	private controls: OrbitControls; | ||||||
|  | 	private composer: EffectComposer; | ||||||
|  | 	private mixers: THREE.AnimationMixer[] = []; | ||||||
|  | 	private furnitureControl: TransformControls; | ||||||
|  | 	private roomInfo: RoomInfo; | ||||||
|  | 	private graphicsQuality: 'cheep' | 'low' | 'medium' | 'high' | 'ultra'; | ||||||
|  | 	private roomObj: THREE.Object3D; | ||||||
|  | 	private objects: THREE.Object3D[] = []; | ||||||
|  | 	private selectedObject: THREE.Object3D = null; | ||||||
|  | 	private onChangeSelect: Function; | ||||||
|  | 	private isTransformMode = false; | ||||||
|  | 	private renderFrameRequestId: number; | ||||||
|  |  | ||||||
|  | 	private get canvas(): HTMLCanvasElement { | ||||||
|  | 		return this.renderer.domElement; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	private get furnitures(): Furniture[] { | ||||||
|  | 		return this.roomInfo.furnitures; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	private set furnitures(furnitures: Furniture[]) { | ||||||
|  | 		this.roomInfo.furnitures = furnitures; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	private get enableShadow() { | ||||||
|  | 		return this.graphicsQuality != 'cheep'; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	private get usePostFXs() { | ||||||
|  | 		return this.graphicsQuality !== 'cheep' && this.graphicsQuality !== 'low'; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	private get shadowQuality() { | ||||||
|  | 		return ( | ||||||
|  | 			this.graphicsQuality === 'ultra' ? 16384 : | ||||||
|  | 			this.graphicsQuality === 'high' ? 8192 : | ||||||
|  | 			this.graphicsQuality === 'medium' ? 4096 : | ||||||
|  | 			this.graphicsQuality === 'low' ? 1024 : | ||||||
|  | 			0); // cheep | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	constructor(user, isMyRoom, roomInfo: RoomInfo, container, options: Options) { | ||||||
|  | 		this.roomInfo = roomInfo; | ||||||
|  | 		this.graphicsQuality = options.graphicsQuality; | ||||||
|  | 		this.onChangeSelect = options.onChangeSelect; | ||||||
|  |  | ||||||
|  | 		this.clock = new THREE.Clock(true); | ||||||
|  |  | ||||||
|  | 		//#region Init a scene | ||||||
|  | 		this.scene = new THREE.Scene(); | ||||||
|  |  | ||||||
|  | 		const width = window.innerWidth; | ||||||
|  | 		const height = window.innerHeight; | ||||||
|  |  | ||||||
|  | 		//#region Init a renderer | ||||||
|  | 		this.renderer = new THREE.WebGLRenderer({ | ||||||
|  | 			antialias: false, | ||||||
|  | 			stencil: false, | ||||||
|  | 			alpha: false, | ||||||
|  | 			powerPreference: | ||||||
|  | 				this.graphicsQuality === 'ultra' ? 'high-performance' : | ||||||
|  | 				this.graphicsQuality === 'high' ? 'high-performance' : | ||||||
|  | 				this.graphicsQuality === 'medium' ? 'default' : | ||||||
|  | 				this.graphicsQuality === 'low' ? 'low-power' : | ||||||
|  | 				'low-power' // cheep | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		this.renderer.setPixelRatio(window.devicePixelRatio); | ||||||
|  | 		this.renderer.setSize(width, height); | ||||||
|  | 		this.renderer.autoClear = false; | ||||||
|  | 		this.renderer.setClearColor(new THREE.Color(0x051f2d)); | ||||||
|  | 		this.renderer.shadowMap.enabled = this.enableShadow; | ||||||
|  | 		this.renderer.shadowMap.type = | ||||||
|  | 			this.graphicsQuality === 'ultra' ? THREE.PCFSoftShadowMap : | ||||||
|  | 			this.graphicsQuality === 'high' ? THREE.PCFSoftShadowMap : | ||||||
|  | 			this.graphicsQuality === 'medium' ? THREE.PCFShadowMap : | ||||||
|  | 			this.graphicsQuality === 'low' ? THREE.BasicShadowMap : | ||||||
|  | 			THREE.BasicShadowMap; // cheep | ||||||
|  |  | ||||||
|  | 		container.appendChild(this.canvas); | ||||||
|  | 		//#endregion | ||||||
|  |  | ||||||
|  | 		//#region Init a camera | ||||||
|  | 		this.camera = options.useOrthographicCamera | ||||||
|  | 			? new THREE.OrthographicCamera( | ||||||
|  | 				width / - 2, width / 2, height / 2, height / - 2, -10, 10) | ||||||
|  | 			: new THREE.PerspectiveCamera(45, width / height); | ||||||
|  |  | ||||||
|  | 		if (options.useOrthographicCamera) { | ||||||
|  | 			this.camera.position.x = 2; | ||||||
|  | 			this.camera.position.y = 2; | ||||||
|  | 			this.camera.position.z = 2; | ||||||
|  | 			this.camera.zoom = 100; | ||||||
|  | 			this.camera.updateProjectionMatrix(); | ||||||
|  | 		} else { | ||||||
|  | 			this.camera.position.x = 5; | ||||||
|  | 			this.camera.position.y = 2; | ||||||
|  | 			this.camera.position.z = 5; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		this.scene.add(this.camera); | ||||||
|  | 		//#endregion | ||||||
|  |  | ||||||
|  | 		//#region AmbientLight | ||||||
|  | 		const ambientLight = new THREE.AmbientLight(0xffffff, 1); | ||||||
|  | 		this.scene.add(ambientLight); | ||||||
|  | 		//#endregion | ||||||
|  |  | ||||||
|  | 		if (this.graphicsQuality !== 'cheep') { | ||||||
|  | 			//#region Room light | ||||||
|  | 			const roomLight = new THREE.SpotLight(0xffffff, 0.1); | ||||||
|  |  | ||||||
|  | 			roomLight.position.set(0, 8, 0); | ||||||
|  | 			roomLight.castShadow = this.enableShadow; | ||||||
|  | 			roomLight.shadow.bias = -0.0001; | ||||||
|  | 			roomLight.shadow.mapSize.width = this.shadowQuality; | ||||||
|  | 			roomLight.shadow.mapSize.height = this.shadowQuality; | ||||||
|  | 			roomLight.shadow.camera.near = 0.1; | ||||||
|  | 			roomLight.shadow.camera.far = 9; | ||||||
|  | 			roomLight.shadow.camera.fov = 45; | ||||||
|  |  | ||||||
|  | 			this.scene.add(roomLight); | ||||||
|  | 			//#endregion | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		//#region Out light | ||||||
|  | 		const outLight1 = new THREE.SpotLight(0xffffff, 0.4); | ||||||
|  | 		outLight1.position.set(9, 3, -2); | ||||||
|  | 		outLight1.castShadow = this.enableShadow; | ||||||
|  | 		outLight1.shadow.bias = -0.001; // アクネ、アーチファクト対策 その代わりピーターパンが発生する可能性がある | ||||||
|  | 		outLight1.shadow.mapSize.width = this.shadowQuality; | ||||||
|  | 		outLight1.shadow.mapSize.height = this.shadowQuality; | ||||||
|  | 		outLight1.shadow.camera.near = 6; | ||||||
|  | 		outLight1.shadow.camera.far = 15; | ||||||
|  | 		outLight1.shadow.camera.fov = 45; | ||||||
|  | 		this.scene.add(outLight1); | ||||||
|  |  | ||||||
|  | 		const outLight2 = new THREE.SpotLight(0xffffff, 0.2); | ||||||
|  | 		outLight2.position.set(-2, 3, 9); | ||||||
|  | 		outLight2.castShadow = false; | ||||||
|  | 		outLight2.shadow.bias = -0.001; // アクネ、アーチファクト対策 その代わりピーターパンが発生する可能性がある | ||||||
|  | 		outLight2.shadow.camera.near = 6; | ||||||
|  | 		outLight2.shadow.camera.far = 15; | ||||||
|  | 		outLight2.shadow.camera.fov = 45; | ||||||
|  | 		this.scene.add(outLight2); | ||||||
|  | 		//#endregion | ||||||
|  |  | ||||||
|  | 		//#region Init a controller | ||||||
|  | 		this.controls = new OrbitControls(this.camera, this.canvas); | ||||||
|  |  | ||||||
|  | 		this.controls.target.set(0, 1, 0); | ||||||
|  | 		this.controls.enableZoom = true; | ||||||
|  | 		this.controls.enablePan = isMyRoom; | ||||||
|  | 		this.controls.minPolarAngle = 0; | ||||||
|  | 		this.controls.maxPolarAngle = Math.PI / 2; | ||||||
|  | 		this.controls.minAzimuthAngle = 0; | ||||||
|  | 		this.controls.maxAzimuthAngle = Math.PI / 2; | ||||||
|  | 		this.controls.enableDamping = true; | ||||||
|  | 		this.controls.dampingFactor = 0.2; | ||||||
|  | 		this.controls.mouseButtons.LEFT = 1; | ||||||
|  | 		this.controls.mouseButtons.MIDDLE = 2; | ||||||
|  | 		this.controls.mouseButtons.RIGHT = 0; | ||||||
|  | 		//#endregion | ||||||
|  |  | ||||||
|  | 		//#region POST FXs | ||||||
|  | 		if (!this.usePostFXs) { | ||||||
|  | 			this.composer = null; | ||||||
|  | 		} else { | ||||||
|  | 			const renderTarget = new THREE.WebGLRenderTarget(width, height, { | ||||||
|  | 				minFilter: THREE.LinearFilter, | ||||||
|  | 				magFilter: THREE.LinearFilter, | ||||||
|  | 				format: THREE.RGBFormat, | ||||||
|  | 				stencilBuffer: false, | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  | 			const fxaa = new ShaderPass(FXAAShader); | ||||||
|  | 			fxaa.uniforms['resolution'].value = new THREE.Vector2(1 / width, 1 / height); | ||||||
|  | 			fxaa.renderToScreen = true; | ||||||
|  |  | ||||||
|  | 			this.composer = new EffectComposer(this.renderer, renderTarget); | ||||||
|  | 			this.composer.addPass(new RenderPass(this.scene, this.camera)); | ||||||
|  | 			if (this.graphicsQuality === 'ultra') { | ||||||
|  | 				this.composer.addPass(new BloomPass(0.25, 30, 128.0, 512)); | ||||||
|  | 			} | ||||||
|  | 			this.composer.addPass(fxaa); | ||||||
|  | 		} | ||||||
|  | 		//#endregion | ||||||
|  | 		//#endregion | ||||||
|  |  | ||||||
|  | 		//#region Label | ||||||
|  | 		//#region Avatar | ||||||
|  | 		const avatarUrl = `/proxy/?${urlQuery({ url: user.avatarUrl })}`; | ||||||
|  |  | ||||||
|  | 		const textureLoader = new THREE.TextureLoader(); | ||||||
|  | 		textureLoader.crossOrigin = 'anonymous'; | ||||||
|  |  | ||||||
|  | 		const iconTexture = textureLoader.load(avatarUrl); | ||||||
|  | 		iconTexture.wrapS = THREE.RepeatWrapping; | ||||||
|  | 		iconTexture.wrapT = THREE.RepeatWrapping; | ||||||
|  | 		iconTexture.anisotropy = 16; | ||||||
|  |  | ||||||
|  | 		const avatarMaterial = new THREE.MeshBasicMaterial({ | ||||||
|  | 			map: iconTexture, | ||||||
|  | 			side: THREE.DoubleSide, | ||||||
|  | 			alphaTest: 0.5 | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		const iconGeometry = new THREE.PlaneGeometry(1, 1); | ||||||
|  |  | ||||||
|  | 		const avatarObject = new THREE.Mesh(iconGeometry, avatarMaterial); | ||||||
|  | 		avatarObject.position.set(-3, 2.5, 2); | ||||||
|  | 		avatarObject.rotation.y = Math.PI / 2; | ||||||
|  | 		avatarObject.castShadow = false; | ||||||
|  |  | ||||||
|  | 		this.scene.add(avatarObject); | ||||||
|  | 		//#endregion | ||||||
|  |  | ||||||
|  | 		//#region Username | ||||||
|  | 		const name = user.username; | ||||||
|  |  | ||||||
|  | 		new THREE.FontLoader().load('/assets/fonts/helvetiker_regular.typeface.json', font => { | ||||||
|  | 			const nameGeometry = new THREE.TextGeometry(name, { | ||||||
|  | 				size: 0.5, | ||||||
|  | 				height: 0, | ||||||
|  | 				curveSegments: 8, | ||||||
|  | 				font: font, | ||||||
|  | 				bevelThickness: 0, | ||||||
|  | 				bevelSize: 0, | ||||||
|  | 				bevelEnabled: false | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  | 			const nameMaterial = new THREE.MeshLambertMaterial({ | ||||||
|  | 				color: 0xffffff | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  | 			const nameObject = new THREE.Mesh(nameGeometry, nameMaterial); | ||||||
|  | 			nameObject.position.set(-3, 2.25, 1.25); | ||||||
|  | 			nameObject.rotation.y = Math.PI / 2; | ||||||
|  | 			nameObject.castShadow = false; | ||||||
|  |  | ||||||
|  | 			this.scene.add(nameObject); | ||||||
|  | 		}); | ||||||
|  | 		//#endregion | ||||||
|  | 		//#endregion | ||||||
|  |  | ||||||
|  | 		//#region Interaction | ||||||
|  | 		if (isMyRoom) { | ||||||
|  | 			this.furnitureControl = new TransformControls(this.camera, this.canvas); | ||||||
|  | 			this.scene.add(this.furnitureControl); | ||||||
|  |  | ||||||
|  | 			// Hover highlight | ||||||
|  | 			this.canvas.onmousemove = this.onmousemove; | ||||||
|  |  | ||||||
|  | 			// Click | ||||||
|  | 			this.canvas.onmousedown = this.onmousedown; | ||||||
|  | 		} | ||||||
|  | 		//#endregion | ||||||
|  |  | ||||||
|  | 		//#region Init room | ||||||
|  | 		this.loadRoom(); | ||||||
|  | 		//#endregion | ||||||
|  |  | ||||||
|  | 		//#region Load furnitures | ||||||
|  | 		for (const furniture of this.furnitures) { | ||||||
|  | 			this.loadFurniture(furniture).then(obj => { | ||||||
|  | 				this.scene.add(obj.scene); | ||||||
|  | 				this.objects.push(obj.scene); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 		//#endregion | ||||||
|  |  | ||||||
|  | 		// Start render | ||||||
|  | 		if (this.usePostFXs) { | ||||||
|  | 			this.renderWithPostFXs(); | ||||||
|  | 		} else { | ||||||
|  | 			this.renderWithoutPostFXs(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@autobind | ||||||
|  | 	private renderWithoutPostFXs() { | ||||||
|  | 		this.renderFrameRequestId = | ||||||
|  | 			window.requestAnimationFrame(this.renderWithoutPostFXs); | ||||||
|  |  | ||||||
|  | 		// Update animations | ||||||
|  | 		const clock = this.clock.getDelta(); | ||||||
|  | 		for (const mixer of this.mixers) { | ||||||
|  | 			mixer.update(clock); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		this.controls.update(); | ||||||
|  | 		this.renderer.render(this.scene, this.camera); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@autobind | ||||||
|  | 	private renderWithPostFXs() { | ||||||
|  | 		this.renderFrameRequestId = | ||||||
|  | 			window.requestAnimationFrame(this.renderWithPostFXs); | ||||||
|  |  | ||||||
|  | 		// Update animations | ||||||
|  | 		const clock = this.clock.getDelta(); | ||||||
|  | 		for (const mixer of this.mixers) { | ||||||
|  | 			mixer.update(clock); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		this.controls.update(); | ||||||
|  | 		this.renderer.clear(); | ||||||
|  | 		this.composer.render(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@autobind | ||||||
|  | 	private loadRoom() { | ||||||
|  | 		const type = this.roomInfo.roomType; | ||||||
|  | 		new GLTFLoader().load(`/assets/room/rooms/${type}/${type}.glb`, gltf => { | ||||||
|  | 			gltf.scene.traverse(child => { | ||||||
|  | 				if (!(child instanceof THREE.Mesh)) return; | ||||||
|  |  | ||||||
|  | 				child.receiveShadow = this.enableShadow; | ||||||
|  |  | ||||||
|  | 				child.material = new THREE.MeshLambertMaterial({ | ||||||
|  | 					color: (child.material as THREE.MeshStandardMaterial).color, | ||||||
|  | 					map: (child.material as THREE.MeshStandardMaterial).map, | ||||||
|  | 					name: (child.material as THREE.MeshStandardMaterial).name, | ||||||
|  | 				}); | ||||||
|  |  | ||||||
|  | 				// 異方性フィルタリング | ||||||
|  | 				if ((child.material as THREE.MeshLambertMaterial).map && this.graphicsQuality !== 'cheep') { | ||||||
|  | 					(child.material as THREE.MeshLambertMaterial).map.minFilter = THREE.LinearMipMapLinearFilter; | ||||||
|  | 					(child.material as THREE.MeshLambertMaterial).map.magFilter = THREE.LinearMipMapLinearFilter; | ||||||
|  | 					(child.material as THREE.MeshLambertMaterial).map.anisotropy = 8; | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  | 			gltf.scene.position.set(0, 0, 0); | ||||||
|  |  | ||||||
|  | 			this.scene.add(gltf.scene); | ||||||
|  | 			this.roomObj = gltf.scene; | ||||||
|  | 			if (this.roomInfo.roomType === 'default') { | ||||||
|  | 				this.applyCarpetColor(); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@autobind | ||||||
|  | 	private loadFurniture(furniture: Furniture) { | ||||||
|  | 		const def = furnitureDefs.find(d => d.id === furniture.type); | ||||||
|  | 		return new Promise<GLTF>((res, rej) => { | ||||||
|  | 			const loader = new GLTFLoader(); | ||||||
|  | 			loader.load(`/assets/room/furnitures/${furniture.type}/${furniture.type}.glb`, gltf => { | ||||||
|  | 				const model = gltf.scene; | ||||||
|  |  | ||||||
|  | 				// Load animation | ||||||
|  | 				if (gltf.animations.length > 0) { | ||||||
|  | 					const mixer = new THREE.AnimationMixer(model); | ||||||
|  | 					this.mixers.push(mixer); | ||||||
|  | 					for (const clip of gltf.animations) { | ||||||
|  | 						mixer.clipAction(clip).play(); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				model.name = furniture.id; | ||||||
|  | 				model.position.x = furniture.position.x; | ||||||
|  | 				model.position.y = furniture.position.y; | ||||||
|  | 				model.position.z = furniture.position.z; | ||||||
|  | 				model.rotation.x = furniture.rotation.x; | ||||||
|  | 				model.rotation.y = furniture.rotation.y; | ||||||
|  | 				model.rotation.z = furniture.rotation.z; | ||||||
|  |  | ||||||
|  | 				model.traverse(child => { | ||||||
|  | 					if (!(child instanceof THREE.Mesh)) return; | ||||||
|  | 					child.castShadow = this.enableShadow; | ||||||
|  | 					child.receiveShadow = this.enableShadow; | ||||||
|  | 					(child.material as THREE.MeshStandardMaterial).metalness = 0; | ||||||
|  |  | ||||||
|  | 					// 異方性フィルタリング | ||||||
|  | 					if ((child.material as THREE.MeshStandardMaterial).map && this.graphicsQuality !== 'cheep') { | ||||||
|  | 						(child.material as THREE.MeshStandardMaterial).map.minFilter = THREE.LinearMipMapLinearFilter; | ||||||
|  | 						(child.material as THREE.MeshStandardMaterial).map.magFilter = THREE.LinearMipMapLinearFilter; | ||||||
|  | 						(child.material as THREE.MeshStandardMaterial).map.anisotropy = 8; | ||||||
|  | 					} | ||||||
|  | 				}); | ||||||
|  |  | ||||||
|  | 				if (def.color) { // カスタムカラー | ||||||
|  | 					this.applyCustomColor(model); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if (def.texture) { // カスタムテクスチャ | ||||||
|  | 					this.applyCustomTexture(model); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				res(gltf); | ||||||
|  | 			}, null, rej); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@autobind | ||||||
|  | 	private applyCarpetColor() { | ||||||
|  | 		this.roomObj.traverse(child => { | ||||||
|  | 			if (!(child instanceof THREE.Mesh)) return; | ||||||
|  | 			if (child.material && | ||||||
|  | 				(child.material as THREE.MeshStandardMaterial).name && | ||||||
|  | 				(child.material as THREE.MeshStandardMaterial).name === 'Carpet' | ||||||
|  | 			) { | ||||||
|  | 				const colorHex = parseInt(this.roomInfo.carpetColor.substr(1), 16); | ||||||
|  | 				(child.material as THREE.MeshStandardMaterial).color.setHex(colorHex); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@autobind | ||||||
|  | 	private applyCustomColor(model: THREE.Object3D) { | ||||||
|  | 		const furniture = this.furnitures.find(furniture => furniture.id === model.name); | ||||||
|  | 		const def = furnitureDefs.find(d => d.id === furniture.type); | ||||||
|  | 		if (def.color == null) return; | ||||||
|  | 		model.traverse(child => { | ||||||
|  | 			if (!(child instanceof THREE.Mesh)) return; | ||||||
|  | 			for (const t of Object.keys(def.color)) { | ||||||
|  | 				if (!child.material || | ||||||
|  | 					!(child.material as THREE.MeshStandardMaterial).name || | ||||||
|  | 					(child.material as THREE.MeshStandardMaterial).name !== t | ||||||
|  | 				) continue; | ||||||
|  |  | ||||||
|  | 				const prop = def.color[t]; | ||||||
|  | 				const val = furniture.props ? furniture.props[prop] : undefined; | ||||||
|  |  | ||||||
|  | 				if (val == null) continue; | ||||||
|  |  | ||||||
|  | 				const colorHex = parseInt(val.substr(1), 16); | ||||||
|  | 				(child.material as THREE.MeshStandardMaterial).color.setHex(colorHex); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@autobind | ||||||
|  | 	private applyCustomTexture(model: THREE.Object3D) { | ||||||
|  | 		const furniture = this.furnitures.find(furniture => furniture.id === model.name); | ||||||
|  | 		const def = furnitureDefs.find(d => d.id === furniture.type); | ||||||
|  | 		if (def.texture == null) return; | ||||||
|  |  | ||||||
|  | 		model.traverse(child => { | ||||||
|  | 			if (!(child instanceof THREE.Mesh)) return; | ||||||
|  | 			for (const t of Object.keys(def.texture)) { | ||||||
|  | 				if (child.name !== t) continue; | ||||||
|  |  | ||||||
|  | 				const prop = def.texture[t].prop; | ||||||
|  | 				const val = furniture.props ? furniture.props[prop] : undefined; | ||||||
|  |  | ||||||
|  | 				if (val == null) continue; | ||||||
|  |  | ||||||
|  | 				const canvas = document.createElement('canvas'); | ||||||
|  | 				canvas.height = 1024; | ||||||
|  | 				canvas.width = 1024; | ||||||
|  |  | ||||||
|  | 				child.material = new THREE.MeshLambertMaterial({ | ||||||
|  | 					emissive: 0x111111, | ||||||
|  | 					side: THREE.DoubleSide, | ||||||
|  | 					alphaTest: 0.5, | ||||||
|  | 				}); | ||||||
|  |  | ||||||
|  | 				const img = new Image(); | ||||||
|  | 				img.crossOrigin = 'anonymous'; | ||||||
|  | 				img.onload = () => { | ||||||
|  | 					const uvInfo = def.texture[t].uv; | ||||||
|  |  | ||||||
|  | 					const ctx = canvas.getContext('2d'); | ||||||
|  | 					ctx.drawImage(img, | ||||||
|  | 						0, 0, img.width, img.height, | ||||||
|  | 						uvInfo.x, uvInfo.y, uvInfo.width, uvInfo.height); | ||||||
|  |  | ||||||
|  | 					const texture = new THREE.Texture(canvas); | ||||||
|  | 					texture.wrapS = THREE.RepeatWrapping; | ||||||
|  | 					texture.wrapT = THREE.RepeatWrapping; | ||||||
|  | 					texture.anisotropy = 16; | ||||||
|  | 					texture.flipY = false; | ||||||
|  |  | ||||||
|  | 					(child.material as THREE.MeshLambertMaterial).map = texture; | ||||||
|  | 					(child.material as THREE.MeshLambertMaterial).needsUpdate = true; | ||||||
|  | 					(child.material as THREE.MeshLambertMaterial).map.needsUpdate = true; | ||||||
|  | 				}; | ||||||
|  | 				img.src = val; | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@autobind | ||||||
|  | 	private onmousemove(ev: MouseEvent) { | ||||||
|  | 		if (this.isTransformMode) return; | ||||||
|  |  | ||||||
|  | 		const rect = (ev.target as HTMLElement).getBoundingClientRect(); | ||||||
|  | 		const x = (((ev.clientX * window.devicePixelRatio) - rect.left) / this.canvas.width) * 2 - 1; | ||||||
|  | 		const y = -(((ev.clientY * window.devicePixelRatio) - rect.top) / this.canvas.height) * 2 + 1; | ||||||
|  | 		const pos = new THREE.Vector2(x, y); | ||||||
|  |  | ||||||
|  | 		this.camera.updateMatrixWorld(); | ||||||
|  |  | ||||||
|  | 		const raycaster = new THREE.Raycaster(); | ||||||
|  | 		raycaster.setFromCamera(pos, this.camera); | ||||||
|  |  | ||||||
|  | 		const intersects = raycaster.intersectObjects(this.objects, true); | ||||||
|  |  | ||||||
|  | 		for (const object of this.objects) { | ||||||
|  | 			if (this.isSelectedObject(object)) continue; | ||||||
|  | 			object.traverse(child => { | ||||||
|  | 				if (child instanceof THREE.Mesh) { | ||||||
|  | 					(child.material as THREE.MeshStandardMaterial).emissive.setHex(0x000000); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (intersects.length > 0) { | ||||||
|  | 			const intersected = this.getRoot(intersects[0].object); | ||||||
|  | 			if (this.isSelectedObject(intersected)) return; | ||||||
|  | 			intersected.traverse(child => { | ||||||
|  | 				if (child instanceof THREE.Mesh) { | ||||||
|  | 					(child.material as THREE.MeshStandardMaterial).emissive.setHex(0x191919); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@autobind | ||||||
|  | 	private onmousedown(ev: MouseEvent) { | ||||||
|  | 		if (this.isTransformMode) return; | ||||||
|  | 		if (ev.target !== this.canvas || ev.button !== 0) return; | ||||||
|  |  | ||||||
|  | 		const rect = (ev.target as HTMLElement).getBoundingClientRect(); | ||||||
|  | 		const x = (((ev.clientX * window.devicePixelRatio) - rect.left) / this.canvas.width) * 2 - 1; | ||||||
|  | 		const y = -(((ev.clientY * window.devicePixelRatio) - rect.top) / this.canvas.height) * 2 + 1; | ||||||
|  | 		const pos = new THREE.Vector2(x, y); | ||||||
|  |  | ||||||
|  | 		this.camera.updateMatrixWorld(); | ||||||
|  |  | ||||||
|  | 		const raycaster = new THREE.Raycaster(); | ||||||
|  | 		raycaster.setFromCamera(pos, this.camera); | ||||||
|  |  | ||||||
|  | 		const intersects = raycaster.intersectObjects(this.objects, true); | ||||||
|  |  | ||||||
|  | 		for (const object of this.objects) { | ||||||
|  | 			object.traverse(child => { | ||||||
|  | 				if (child instanceof THREE.Mesh) { | ||||||
|  | 					(child.material as THREE.MeshStandardMaterial).emissive.setHex(0x000000); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (intersects.length > 0) { | ||||||
|  | 			const selectedObj = this.getRoot(intersects[0].object); | ||||||
|  | 			this.selectFurniture(selectedObj); | ||||||
|  | 		} else { | ||||||
|  | 			this.selectedObject = null; | ||||||
|  | 			this.onChangeSelect(null); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@autobind | ||||||
|  | 	private getRoot(obj: THREE.Object3D): THREE.Object3D { | ||||||
|  | 		let found = false; | ||||||
|  | 		let x = obj.parent; | ||||||
|  | 		while (!found) { | ||||||
|  | 			if (x.parent.parent == null) { | ||||||
|  | 				found = true; | ||||||
|  | 			} else { | ||||||
|  | 				x = x.parent; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return x; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@autobind | ||||||
|  | 	private isSelectedObject(obj: THREE.Object3D): boolean { | ||||||
|  | 		if (this.selectedObject == null) { | ||||||
|  | 			return false; | ||||||
|  | 		} else { | ||||||
|  | 			return obj.name === this.selectedObject.name; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@autobind | ||||||
|  | 	private selectFurniture(obj: THREE.Object3D) { | ||||||
|  | 		this.selectedObject = obj; | ||||||
|  | 		this.onChangeSelect(obj); | ||||||
|  | 		obj.traverse(child => { | ||||||
|  | 			if (child instanceof THREE.Mesh) { | ||||||
|  | 				(child.material as THREE.MeshStandardMaterial).emissive.setHex(0xff0000); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * 家具の移動/回転モードにします | ||||||
|  | 	 * @param type 移動か回転か | ||||||
|  | 	 */ | ||||||
|  | 	@autobind | ||||||
|  | 	public enterTransformMode(type: 'translate' | 'rotate') { | ||||||
|  | 		this.isTransformMode = true; | ||||||
|  | 		this.furnitureControl.setMode(type); | ||||||
|  | 		this.furnitureControl.attach(this.selectedObject); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * 家具の移動/回転モードを終了します | ||||||
|  | 	 */ | ||||||
|  | 	@autobind | ||||||
|  | 	public exitTransformMode() { | ||||||
|  | 		this.isTransformMode = false; | ||||||
|  | 		this.furnitureControl.detach(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * 家具プロパティを更新します | ||||||
|  | 	 * @param key プロパティ名 | ||||||
|  | 	 * @param value 値 | ||||||
|  | 	 */ | ||||||
|  | 	@autobind | ||||||
|  | 	public updateProp(key: string, value: any) { | ||||||
|  | 		const furniture = this.furnitures.find(furniture => furniture.id === this.selectedObject.name); | ||||||
|  | 		if (furniture.props == null) furniture.props = {}; | ||||||
|  | 		furniture.props[key] = value; | ||||||
|  | 		this.applyCustomColor(this.selectedObject); | ||||||
|  | 		this.applyCustomTexture(this.selectedObject); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * 部屋に家具を追加します | ||||||
|  | 	 * @param type 家具の種類 | ||||||
|  | 	 */ | ||||||
|  | 	@autobind | ||||||
|  | 	public addFurniture(type: string) { | ||||||
|  | 		const furniture = { | ||||||
|  | 			id: uuid(), | ||||||
|  | 			type: type, | ||||||
|  | 			position: { | ||||||
|  | 				x: 0, | ||||||
|  | 				y: 0, | ||||||
|  | 				z: 0, | ||||||
|  | 			}, | ||||||
|  | 			rotation: { | ||||||
|  | 				x: 0, | ||||||
|  | 				y: 0, | ||||||
|  | 				z: 0, | ||||||
|  | 			}, | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		this.furnitures.push(furniture); | ||||||
|  |  | ||||||
|  | 		this.loadFurniture(furniture).then(obj => { | ||||||
|  | 			this.scene.add(obj.scene); | ||||||
|  | 			this.objects.push(obj.scene); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * 現在選択されている家具を部屋から削除します | ||||||
|  | 	 */ | ||||||
|  | 	@autobind | ||||||
|  | 	public removeFurniture() { | ||||||
|  | 		this.exitTransformMode(); | ||||||
|  | 		const obj = this.selectedObject; | ||||||
|  | 		this.scene.remove(obj); | ||||||
|  | 		this.objects = this.objects.filter(object => object.name !== obj.name); | ||||||
|  | 		this.furnitures = this.furnitures.filter(furniture => furniture.id !== obj.name); | ||||||
|  | 		this.selectedObject = null; | ||||||
|  | 		this.onChangeSelect(null); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * 全ての家具を部屋から削除します | ||||||
|  | 	 */ | ||||||
|  | 	@autobind | ||||||
|  | 	public removeAllFurnitures() { | ||||||
|  | 		this.exitTransformMode(); | ||||||
|  | 		for (const obj of this.objects) { | ||||||
|  | 			this.scene.remove(obj); | ||||||
|  | 		} | ||||||
|  | 		this.objects = []; | ||||||
|  | 		this.furnitures = []; | ||||||
|  | 		this.selectedObject = null; | ||||||
|  | 		this.onChangeSelect(null); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * 部屋の床の色を変更します | ||||||
|  | 	 * @param color 色 | ||||||
|  | 	 */ | ||||||
|  | 	@autobind | ||||||
|  | 	public updateCarpetColor(color: string) { | ||||||
|  | 		this.roomInfo.carpetColor = color; | ||||||
|  | 		this.applyCarpetColor(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * 部屋の種類を変更します | ||||||
|  | 	 * @param type 種類 | ||||||
|  | 	 */ | ||||||
|  | 	@autobind | ||||||
|  | 	public changeRoomType(type: string) { | ||||||
|  | 		this.roomInfo.roomType = type; | ||||||
|  | 		this.scene.remove(this.roomObj); | ||||||
|  | 		this.loadRoom(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * 部屋データを取得します | ||||||
|  | 	 */ | ||||||
|  | 	@autobind | ||||||
|  | 	public getRoomInfo() { | ||||||
|  | 		for (const obj of this.objects) { | ||||||
|  | 			const furniture = this.furnitures.find(f => f.id === obj.name); | ||||||
|  | 			furniture.position.x = obj.position.x; | ||||||
|  | 			furniture.position.y = obj.position.y; | ||||||
|  | 			furniture.position.z = obj.position.z; | ||||||
|  | 			furniture.rotation.x = obj.rotation.x; | ||||||
|  | 			furniture.rotation.y = obj.rotation.y; | ||||||
|  | 			furniture.rotation.z = obj.rotation.z; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return this.roomInfo; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * 選択されている家具を取得します | ||||||
|  | 	 */ | ||||||
|  | 	@autobind | ||||||
|  | 	public getSelectedObject() { | ||||||
|  | 		return this.selectedObject; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@autobind | ||||||
|  | 	public findFurnitureById(id: string) { | ||||||
|  | 		return this.furnitures.find(furniture => furniture.id === id); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * レンダリングを終了します | ||||||
|  | 	 */ | ||||||
|  | 	@autobind | ||||||
|  | 	public destroy() { | ||||||
|  | 		// Stop render loop | ||||||
|  | 		window.cancelAnimationFrame(this.renderFrameRequestId); | ||||||
|  |  | ||||||
|  | 		this.controls.dispose(); | ||||||
|  | 		this.scene.dispose(); | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -1,16 +1,16 @@ | |||||||
| <template> | <template> | ||||||
| <prism :inline="inline" :language="lang || 'js'">{{ code }}</prism> | <x-prism :inline="inline" :language="prismLang">{{ code }}</x-prism> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import 'prismjs'; | import 'prismjs'; | ||||||
| import 'prismjs/themes/prism-okaidia.css'; | import 'prismjs/themes/prism-okaidia.css'; | ||||||
| import Prism from 'vue-prism-component'; | import XPrism from 'vue-prism-component'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	components: { | 	components: { | ||||||
| 		Prism | 		XPrism | ||||||
| 	}, | 	}, | ||||||
| 	props: { | 	props: { | ||||||
| 		code: { | 		code: { | ||||||
| @@ -25,6 +25,12 @@ export default Vue.extend({ | |||||||
| 			type: Boolean, | 			type: Boolean, | ||||||
| 			required: false | 			required: false | ||||||
| 		} | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	computed: { | ||||||
|  | 		prismLang() { | ||||||
|  | 			return Prism.languages[this.lang] ? this.lang : 'js'; | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -98,7 +98,7 @@ export default Vue.extend({ | |||||||
| 		return { | 		return { | ||||||
| 			inputValue: this.input && this.input.default ? this.input.default : null, | 			inputValue: this.input && this.input.default ? this.input.default : null, | ||||||
| 			userInputValue: null, | 			userInputValue: null, | ||||||
| 			selectedValue: this.select ? this.select.items ? this.select.items[0].value : this.select.groupedItems[0].items[0].value : null, | 			selectedValue: this.select ? this.select.default ? this.select.default : this.select.items ? this.select.items[0].value : this.select.groupedItems[0].items[0].value : null, | ||||||
| 			canOk: true, | 			canOk: true, | ||||||
| 			faTimesCircle, faQuestionCircle | 			faTimesCircle, faQuestionCircle | ||||||
| 		}; | 		}; | ||||||
|   | |||||||
| @@ -57,7 +57,8 @@ export default Vue.extend({ | |||||||
| 		}, | 		}, | ||||||
| 		fit: { | 		fit: { | ||||||
| 			type: String, | 			type: String, | ||||||
| 			required: true | 			required: false, | ||||||
|  | 			default: 'cover' | ||||||
| 		}, | 		}, | ||||||
| 		detail: { | 		detail: { | ||||||
| 			type: Boolean, | 			type: Boolean, | ||||||
|   | |||||||
| @@ -35,7 +35,8 @@ export default Vue.extend({ | |||||||
| 	mounted() { | 	mounted() { | ||||||
| 		//#region for Safari bug | 		//#region for Safari bug | ||||||
| 		if (this.$refs.grid) { | 		if (this.$refs.grid) { | ||||||
| 			this.$refs.grid.style.height = this.$refs.grid.clientHeight ? `${this.$refs.grid.clientHeight}px` : '128px'; | 			this.$refs.grid.style.height = this.$refs.grid.clientHeight ? `${this.$refs.grid.clientHeight}px` | ||||||
|  | 				: this.$store.state.device.inDeckMode ? '128px' : this.$root.isMobile ? '173px' : '287px'; | ||||||
| 		} | 		} | ||||||
| 		//#endregion | 		//#endregion | ||||||
| 	}, | 	}, | ||||||
|   | |||||||
| @@ -139,6 +139,7 @@ export default Vue.extend({ | |||||||
| 					cursor pointer | 					cursor pointer | ||||||
|  |  | ||||||
| 			> .content | 			> .content | ||||||
|  | 				max-width 100% | ||||||
|  |  | ||||||
| 				> .is-deleted | 				> .is-deleted | ||||||
| 					display block | 					display block | ||||||
| @@ -155,6 +156,7 @@ export default Vue.extend({ | |||||||
| 					padding 8px 16px | 					padding 8px 16px | ||||||
| 					overflow hidden | 					overflow hidden | ||||||
| 					overflow-wrap break-word | 					overflow-wrap break-word | ||||||
|  | 					word-break break-word | ||||||
| 					font-size 1em | 					font-size 1em | ||||||
| 					color rgba(#000, 0.8) | 					color rgba(#000, 0.8) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ export default Vue.extend({ | |||||||
| 	}, | 	}, | ||||||
| 	computed: { | 	computed: { | ||||||
| 		items(): any[] { | 		items(): any[] { | ||||||
|  | 			if (this.$store.getters.isSignedIn) { | ||||||
| 				return [{ | 				return [{ | ||||||
| 					icon: 'at', | 					icon: 'at', | ||||||
| 					text: this.$t('mention'), | 					text: this.$t('mention'), | ||||||
| @@ -74,14 +75,42 @@ export default Vue.extend({ | |||||||
| 					action: () => this.togglePin(true) | 					action: () => this.togglePin(true) | ||||||
| 				} : undefined, | 				} : undefined, | ||||||
| 				...(this.note.userId == this.$store.state.i.id || this.$store.state.i.isAdmin || this.$store.state.i.isModerator ? [ | 				...(this.note.userId == this.$store.state.i.id || this.$store.state.i.isAdmin || this.$store.state.i.isModerator ? [ | ||||||
| 				null, { | 					null, | ||||||
|  | 					this.note.userId == this.$store.state.i.id ? { | ||||||
|  | 						icon: 'undo-alt', | ||||||
|  | 						text: this.$t('delete-and-edit'), | ||||||
|  | 						action: this.deleteAndEdit | ||||||
|  | 					} : undefined, | ||||||
|  | 					{ | ||||||
| 						icon: ['far', 'trash-alt'], | 						icon: ['far', 'trash-alt'], | ||||||
| 						text: this.$t('delete'), | 						text: this.$t('delete'), | ||||||
| 						action: this.del | 						action: this.del | ||||||
| 					}] | 					}] | ||||||
| 					: [] | 					: [] | ||||||
| 				)] | 				)] | ||||||
| 			.filter(x => x !== undefined) | 				.filter(x => x !== undefined); | ||||||
|  | 			} else { | ||||||
|  | 				return [{ | ||||||
|  | 					icon: 'info-circle', | ||||||
|  | 					text: this.$t('detail'), | ||||||
|  | 					action: this.detail | ||||||
|  | 				}, { | ||||||
|  | 					icon: faCopy, | ||||||
|  | 					text: this.$t('copy-content'), | ||||||
|  | 					action: this.copyContent | ||||||
|  | 				}, { | ||||||
|  | 					icon: 'link', | ||||||
|  | 					text: this.$t('copy-link'), | ||||||
|  | 					action: this.copyLink | ||||||
|  | 				}, this.note.uri ? { | ||||||
|  | 					icon: 'external-link-square-alt', | ||||||
|  | 					text: this.$t('remote'), | ||||||
|  | 					action: () => { | ||||||
|  | 						window.open(this.note.uri, '_blank'); | ||||||
|  | 					} | ||||||
|  | 				} : undefined] | ||||||
|  | 				.filter(x => x !== undefined); | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -154,6 +183,25 @@ export default Vue.extend({ | |||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | 		deleteAndEdit() { | ||||||
|  | 			this.$root.dialog({ | ||||||
|  | 				type: 'warning', | ||||||
|  | 				text: this.$t('delete-and-edit-confirm'), | ||||||
|  | 				showCancelButton: true | ||||||
|  | 			}).then(({ canceled }) => { | ||||||
|  | 				if (canceled) return; | ||||||
|  | 				this.$root.api('notes/delete', { | ||||||
|  | 					noteId: this.note.id | ||||||
|  | 				}).then(() => { | ||||||
|  | 					this.destroyDom(); | ||||||
|  | 				}); | ||||||
|  | 				this.$post({ | ||||||
|  | 					initialNote: this.note, | ||||||
|  | 					reply: this.note.reply, | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
| 		toggleFavorite(favorite: boolean) { | 		toggleFavorite(favorite: boolean) { | ||||||
| 			this.$root.api(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', { | 			this.$root.api(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', { | ||||||
| 				noteId: this.note.id | 				noteId: this.note.id | ||||||
| @@ -179,6 +227,7 @@ export default Vue.extend({ | |||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		closed() { | 		closed() { | ||||||
|  | 			this.$emit('closed'); | ||||||
| 			this.$nextTick(() => { | 			this.$nextTick(() => { | ||||||
| 				this.destroyDom(); | 				this.destroyDom(); | ||||||
| 			}); | 			}); | ||||||
|   | |||||||
| @@ -52,9 +52,11 @@ | |||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import * as moment from 'moment'; |  | ||||||
| import i18n from '../../../i18n'; | import i18n from '../../../i18n'; | ||||||
| import { erase } from '../../../../../prelude/array'; | import { erase } from '../../../../../prelude/array'; | ||||||
|  | import { addTimespan } from '../../../../../prelude/time'; | ||||||
|  | import { formatDateTimeString } from '../../../../../misc/format-time-string'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('common/views/components/poll-editor.vue'), | 	i18n: i18n('common/views/components/poll-editor.vue'), | ||||||
| 	data() { | 	data() { | ||||||
| @@ -62,7 +64,7 @@ export default Vue.extend({ | |||||||
| 			choices: ['', ''], | 			choices: ['', ''], | ||||||
| 			multiple: false, | 			multiple: false, | ||||||
| 			expiration: 'infinite', | 			expiration: 'infinite', | ||||||
| 			atDate: moment().add(1, 'day').toISOString().split('T')[0], | 			atDate: formatDateTimeString(addTimespan(new Date(), 1, 'days'), 'yyyy-MM-dd'), | ||||||
| 			atTime: '00:00', | 			atTime: '00:00', | ||||||
| 			after: 0, | 			after: 0, | ||||||
| 			unit: 'second' | 			unit: 'second' | ||||||
| @@ -95,7 +97,7 @@ export default Vue.extend({ | |||||||
|  |  | ||||||
| 		get() { | 		get() { | ||||||
| 			const at = () => { | 			const at = () => { | ||||||
| 				return moment(`${this.atDate} ${this.atTime}`).valueOf(); | 				return new Date(`${this.atDate} ${this.atTime}`).getTime(); | ||||||
| 			}; | 			}; | ||||||
|  |  | ||||||
| 			const after = () => { | 			const after = () => { | ||||||
|   | |||||||
| @@ -36,7 +36,7 @@ export default Vue.extend({ | |||||||
| 			required: true | 			required: true | ||||||
| 		}, | 		}, | ||||||
| 		detachMediaFn: { | 		detachMediaFn: { | ||||||
| 			type: Object, | 			type: Function, | ||||||
| 			required: false | 			required: false | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|   | |||||||
| @@ -0,0 +1,110 @@ | |||||||
|  | <template> | ||||||
|  | 	<transition name="zoom-in-top"> | ||||||
|  | 		<div class="buebdbiu" ref="popover" v-if="show"> | ||||||
|  | 			<i18n path="few-users" v-if="users.length <= 10"> | ||||||
|  | 				<span slot="users"> | ||||||
|  | 					<mk-user-name v-for="u in users" :user="u" :nowrap="false" :key="u.id"/> | ||||||
|  | 				</span> | ||||||
|  | 				<mk-reaction-icon slot="reaction" :reaction="reaction" ref="icon" /> | ||||||
|  | 			</i18n> | ||||||
|  | 			<i18n path="many-users" v-if="10 < users.length"> | ||||||
|  | 				<span slot="users">{{ users.slice(0, 10).join(', ') }}</span> | ||||||
|  | 				<span slot="ommited">{{ users.length - 10 }}</span> | ||||||
|  | 				<mk-reaction-icon slot="reaction" :reaction="reaction" ref="icon" /> | ||||||
|  | 			</i18n> | ||||||
|  | 		</div> | ||||||
|  | 	</transition> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import Vue from 'vue'; | ||||||
|  | import i18n from '../../../i18n'; | ||||||
|  |  | ||||||
|  | export default Vue.extend({ | ||||||
|  | 	i18n: i18n('common/views/components/reactions-viewer.details.vue'), | ||||||
|  | 	props: { | ||||||
|  | 		reaction: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: true, | ||||||
|  | 		}, | ||||||
|  | 		users: { | ||||||
|  | 			type: Array, | ||||||
|  | 			required: true, | ||||||
|  | 		}, | ||||||
|  | 		source: { | ||||||
|  | 			required: true, | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			show: false | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
|  | 	mounted() { | ||||||
|  | 		this.show = true; | ||||||
|  |  | ||||||
|  | 		this.$nextTick(() => { | ||||||
|  | 			const popover = this.$refs.popover as any; | ||||||
|  |  | ||||||
|  | 			if (this.source == null) { | ||||||
|  | 				this.destroyDom(); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 			const rect = this.source.getBoundingClientRect(); | ||||||
|  |  | ||||||
|  | 			const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2); | ||||||
|  | 			const y = rect.top + window.pageYOffset + this.source.offsetHeight; | ||||||
|  | 			popover.style.left = (x - 28) + 'px'; | ||||||
|  | 			popover.style.top = (y + 16) + 'px'; | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 	methods: { | ||||||
|  | 		close() { | ||||||
|  | 			this.show = false; | ||||||
|  | 			setTimeout(this.destroyDom, 300); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="stylus" scoped> | ||||||
|  | .buebdbiu | ||||||
|  | 	$bgcolor = var(--popupBg) | ||||||
|  | 	z-index 10000 | ||||||
|  | 	display block | ||||||
|  | 	position absolute | ||||||
|  | 	max-width 240px | ||||||
|  | 	font-size 0.8em | ||||||
|  | 	padding 5px 8px | ||||||
|  | 	background $bgcolor | ||||||
|  | 	text-align center | ||||||
|  | 	color var(--text) | ||||||
|  | 	border-radius 4px | ||||||
|  | 	box-shadow 0 var(--lineWidth) 4px rgba(#000, 0.25) | ||||||
|  | 	pointer-events none | ||||||
|  | 	transform-origin center -16px | ||||||
|  |  | ||||||
|  | 	&:before | ||||||
|  | 		content "" | ||||||
|  | 		pointer-events none | ||||||
|  | 		display block | ||||||
|  | 		position absolute | ||||||
|  | 		top -28px | ||||||
|  | 		left 12px | ||||||
|  | 		border-top solid 14px transparent | ||||||
|  | 		border-right solid 14px transparent | ||||||
|  | 		border-bottom solid 14px rgba(#000, 0.1) | ||||||
|  | 		border-left solid 14px transparent | ||||||
|  |  | ||||||
|  | 	&:after | ||||||
|  | 		content "" | ||||||
|  | 		pointer-events none | ||||||
|  | 		display block | ||||||
|  | 		position absolute | ||||||
|  | 		top -27px | ||||||
|  | 		left 12px | ||||||
|  | 		border-top solid 14px transparent | ||||||
|  | 		border-right solid 14px transparent | ||||||
|  | 		border-bottom solid 14px $bgcolor | ||||||
|  | 		border-left solid 14px transparent | ||||||
|  | </style> | ||||||
| @@ -5,6 +5,9 @@ | |||||||
| 	@click="toggleReaction(reaction)" | 	@click="toggleReaction(reaction)" | ||||||
| 	v-if="count > 0" | 	v-if="count > 0" | ||||||
| 	v-particle="!isMe" | 	v-particle="!isMe" | ||||||
|  | 	@mouseover="onMouseover" | ||||||
|  | 	@mouseleave="onMouseleave" | ||||||
|  | 	ref="reaction" | ||||||
| > | > | ||||||
| 	<mk-reaction-icon :reaction="reaction" ref="icon"/> | 	<mk-reaction-icon :reaction="reaction" ref="icon"/> | ||||||
| 	<span>{{ count }}</span> | 	<span>{{ count }}</span> | ||||||
| @@ -15,6 +18,7 @@ | |||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import Icon from './reaction-icon.vue'; | import Icon from './reaction-icon.vue'; | ||||||
| import anime from 'animejs'; | import anime from 'animejs'; | ||||||
|  | import XDetails from './reactions-viewer.details.vue'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	props: { | 	props: { | ||||||
| @@ -26,6 +30,10 @@ export default Vue.extend({ | |||||||
| 			type: Number, | 			type: Number, | ||||||
| 			required: true, | 			required: true, | ||||||
| 		}, | 		}, | ||||||
|  | 		isInitial: { | ||||||
|  | 			type: Boolean, | ||||||
|  | 			required: true, | ||||||
|  | 		}, | ||||||
| 		note: { | 		note: { | ||||||
| 			type: Object, | 			type: Object, | ||||||
| 			required: true, | 			required: true, | ||||||
| @@ -36,14 +44,25 @@ export default Vue.extend({ | |||||||
| 			default: true, | 			default: true, | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			details: null, | ||||||
|  | 			detailsTimeoutId: null, | ||||||
|  | 			isHovering: false | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
| 	computed: { | 	computed: { | ||||||
| 		isMe(): boolean { | 		isMe(): boolean { | ||||||
| 			return this.$store.getters.isSignedIn && this.$store.state.i.id === this.note.userId; | 			return this.$store.getters.isSignedIn && this.$store.state.i.id === this.note.userId; | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
|  | 	mounted() { | ||||||
|  | 		if (!this.isInitial) this.anime(); | ||||||
|  | 	}, | ||||||
| 	watch: { | 	watch: { | ||||||
| 		count() { | 		count(newCount, oldCount) { | ||||||
| 			this.anime(); | 			if (oldCount < newCount) this.anime(); | ||||||
|  | 			if (this.details != null) this.openDetails(); | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	methods: { | 	methods: { | ||||||
| @@ -70,11 +89,46 @@ export default Vue.extend({ | |||||||
| 				}); | 				}); | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		onMouseover() { | ||||||
|  | 			this.isHovering = true; | ||||||
|  | 			this.detailsTimeoutId = setTimeout(this.openDetails, 300); | ||||||
|  | 		}, | ||||||
|  | 		onMouseleave() { | ||||||
|  | 			this.isHovering = false; | ||||||
|  | 			clearTimeout(this.detailsTimeoutId); | ||||||
|  | 			this.closeDetails(); | ||||||
|  | 		}, | ||||||
|  | 		openDetails() { | ||||||
|  | 			if (this.$root.isMobile) return; | ||||||
|  | 			this.$root.api('notes/reactions', { | ||||||
|  | 				noteId: this.note.id | ||||||
|  | 			}).then((reactions: any[]) => { | ||||||
|  | 				const users = reactions.filter(x => x.type === this.reaction) | ||||||
|  | 					.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()) | ||||||
|  | 					.map(x => x.user); | ||||||
|  |  | ||||||
|  | 				this.closeDetails(); | ||||||
|  | 				if (!this.isHovering) return; | ||||||
|  | 				this.details = this.$root.new(XDetails, { | ||||||
|  | 					reaction: this.reaction, | ||||||
|  | 					users, | ||||||
|  | 					source: this.$refs.reaction | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  | 		closeDetails() { | ||||||
|  | 			if (this.details != null) { | ||||||
|  | 				this.details.close(); | ||||||
|  | 				this.details = null; | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		anime() { | 		anime() { | ||||||
| 			if (this.$store.state.device.reduceMotion) return; | 			if (this.$store.state.device.reduceMotion) return; | ||||||
| 			if (document.hidden) return; | 			if (document.hidden) return; | ||||||
|  |  | ||||||
| 			this.$nextTick(() => { | 			this.$nextTick(() => { | ||||||
|  | 				if (this.$refs.icon == null) return; | ||||||
|  |  | ||||||
| 				const rect = this.$refs.icon.$el.getBoundingClientRect(); | 				const rect = this.$refs.icon.$el.getBoundingClientRect(); | ||||||
|  |  | ||||||
| 				const x = rect.left; | 				const x = rect.left; | ||||||
| @@ -120,6 +174,14 @@ export default Vue.extend({ | |||||||
| 	border-radius 4px | 	border-radius 4px | ||||||
| 	cursor pointer | 	cursor pointer | ||||||
|  |  | ||||||
|  | 	&, * | ||||||
|  | 		-webkit-touch-callout none | ||||||
|  | 		-webkit-user-select none | ||||||
|  | 		-khtml-user-select none | ||||||
|  | 		-moz-user-select none | ||||||
|  | 		-ms-user-select none | ||||||
|  | 		user-select none | ||||||
|  |  | ||||||
| 	* | 	* | ||||||
| 		user-select none | 		user-select none | ||||||
| 		pointer-events none | 		pointer-events none | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <template> | <template> | ||||||
| <div class="mk-reactions-viewer" :class="{ isMe }"> | <div class="mk-reactions-viewer" :class="{ isMe }"> | ||||||
| 	<x-reaction v-for="(count, reaction) in reactions" :reaction="reaction" :count="count" :note="note" :key="reaction"/> | 	<x-reaction v-for="(count, reaction) in note.reactions" :reaction="reaction" :count="count" :is-initial="initialReactions.has(reaction)" :note="note" :key="reaction"/> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| @@ -12,6 +12,11 @@ export default Vue.extend({ | |||||||
| 	components: { | 	components: { | ||||||
| 		XReaction | 		XReaction | ||||||
| 	}, | 	}, | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			initialReactions: new Set(Object.keys(this.note.reactions)) | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
| 	props: { | 	props: { | ||||||
| 		note: { | 		note: { | ||||||
| 			type: Object, | 			type: Object, | ||||||
| @@ -19,9 +24,6 @@ export default Vue.extend({ | |||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	computed: { | 	computed: { | ||||||
| 		reactions(): any { |  | ||||||
| 			return this.note.reactions; |  | ||||||
| 		}, |  | ||||||
| 		isMe(): boolean { | 		isMe(): boolean { | ||||||
| 			return this.$store.getters.isSignedIn && this.$store.state.i.id === this.note.userId; | 			return this.$store.getters.isSignedIn && this.$store.state.i.id === this.note.userId; | ||||||
| 		}, | 		}, | ||||||
|   | |||||||
| @@ -159,6 +159,14 @@ export default Vue.extend({ | |||||||
| 			username: null, | 			username: null, | ||||||
| 			location: null, | 			location: null, | ||||||
| 			description: null, | 			description: null, | ||||||
|  | 			fieldName0: null, | ||||||
|  | 			fieldValue0: null, | ||||||
|  | 			fieldName1: null, | ||||||
|  | 			fieldValue1: null, | ||||||
|  | 			fieldName2: null, | ||||||
|  | 			fieldValue2: null, | ||||||
|  | 			fieldName3: null, | ||||||
|  | 			fieldValue3: null, | ||||||
| 			lang: null, | 			lang: null, | ||||||
| 			birthday: null, | 			birthday: null, | ||||||
| 			avatarId: null, | 			avatarId: null, | ||||||
| @@ -210,16 +218,14 @@ export default Vue.extend({ | |||||||
| 		this.carefulBot = this.$store.state.i.carefulBot; | 		this.carefulBot = this.$store.state.i.carefulBot; | ||||||
| 		this.autoAcceptFollowed = this.$store.state.i.autoAcceptFollowed; | 		this.autoAcceptFollowed = this.$store.state.i.autoAcceptFollowed; | ||||||
|  |  | ||||||
| 		if (this.$store.state.i.fields) { | 		this.fieldName0 = this.$store.state.i.fields[0] ? this.$store.state.i.fields[0].name : null; | ||||||
| 			this.fieldName0 = this.$store.state.i.fields[0].name; | 		this.fieldValue0 = this.$store.state.i.fields[0] ? this.$store.state.i.fields[0].value : null; | ||||||
| 			this.fieldValue0 = this.$store.state.i.fields[0].value; | 		this.fieldName1 = this.$store.state.i.fields[1] ? this.$store.state.i.fields[1].name : null; | ||||||
| 			this.fieldName1 = this.$store.state.i.fields[1].name; | 		this.fieldValue1 = this.$store.state.i.fields[1] ? this.$store.state.i.fields[1].value : null; | ||||||
| 			this.fieldValue1 = this.$store.state.i.fields[1].value; | 		this.fieldName2 = this.$store.state.i.fields[2] ? this.$store.state.i.fields[2].name : null; | ||||||
| 			this.fieldName2 = this.$store.state.i.fields[2].name; | 		this.fieldValue2 = this.$store.state.i.fields[2] ? this.$store.state.i.fields[2].value : null; | ||||||
| 			this.fieldValue2 = this.$store.state.i.fields[2].value; | 		this.fieldName3 = this.$store.state.i.fields[3] ? this.$store.state.i.fields[3].name : null; | ||||||
| 			this.fieldName3 = this.$store.state.i.fields[3].name; | 		this.fieldValue3 = this.$store.state.i.fields[3] ? this.$store.state.i.fields[3].value : null; | ||||||
| 			this.fieldValue3 = this.$store.state.i.fields[3].value; |  | ||||||
| 		} |  | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	methods: { | 	methods: { | ||||||
|   | |||||||
| @@ -47,6 +47,7 @@ | |||||||
| 				<ui-switch v-model="disableAnimatedMfm">{{ $t('@._settings.disable-animated-mfm') }}</ui-switch> | 				<ui-switch v-model="disableAnimatedMfm">{{ $t('@._settings.disable-animated-mfm') }}</ui-switch> | ||||||
| 				<ui-switch v-model="disableShowingAnimatedImages">{{ $t('@._settings.disable-showing-animated-images') }}</ui-switch> | 				<ui-switch v-model="disableShowingAnimatedImages">{{ $t('@._settings.disable-showing-animated-images') }}</ui-switch> | ||||||
| 				<ui-switch v-model="remainDeletedNote">{{ $t('@._settings.remain-deleted-note') }}</ui-switch> | 				<ui-switch v-model="remainDeletedNote">{{ $t('@._settings.remain-deleted-note') }}</ui-switch> | ||||||
|  | 				<ui-switch v-model="enableMobileQuickNotificationView">{{ $t('@._settings.enable-quick-notification-view') }}</ui-switch> | ||||||
| 			</section> | 			</section> | ||||||
| 			<section> | 			<section> | ||||||
| 				<header>{{ $t('@._settings.line-width') }}</header> | 				<header>{{ $t('@._settings.line-width') }}</header> | ||||||
| @@ -158,6 +159,19 @@ | |||||||
| 					<template #desc>{{ $t('@._settings.paste-dialog-desc') }}</template> | 					<template #desc>{{ $t('@._settings.paste-dialog-desc') }}</template> | ||||||
| 				</ui-switch> | 				</ui-switch> | ||||||
| 			</section> | 			</section> | ||||||
|  |  | ||||||
|  | 			<section> | ||||||
|  | 				<header>{{ $t('@._settings.room') }}</header> | ||||||
|  | 				<ui-select v-model="roomGraphicsQuality"> | ||||||
|  | 					<template #label>{{ $t('@._settings._room.graphicsQuality') }}</template> | ||||||
|  | 					<option value="ultra">{{ $t('@._settings._room._graphicsQuality.ultra') }}</option> | ||||||
|  | 					<option value="high">{{ $t('@._settings._room._graphicsQuality.high') }}</option> | ||||||
|  | 					<option value="medium">{{ $t('@._settings._room._graphicsQuality.medium') }}</option> | ||||||
|  | 					<option value="low">{{ $t('@._settings._room._graphicsQuality.low') }}</option> | ||||||
|  | 					<option value="cheep">{{ $t('@._settings._room._graphicsQuality.cheep') }}</option> | ||||||
|  | 				</ui-select> | ||||||
|  | 				<ui-switch v-model="roomUseOrthographicCamera">{{ $t('@._settings._room.useOrthographicCamera') }}</ui-switch> | ||||||
|  | 			</section> | ||||||
| 		</ui-card> | 		</ui-card> | ||||||
|  |  | ||||||
| 		<ui-card> | 		<ui-card> | ||||||
| @@ -502,6 +516,16 @@ export default Vue.extend({ | |||||||
| 			set(value) { this.$store.dispatch('settings/set', { key: 'iLikeSushi', value }); } | 			set(value) { this.$store.dispatch('settings/set', { key: 'iLikeSushi', value }); } | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | 		roomUseOrthographicCamera: { | ||||||
|  | 			get() { return this.$store.state.device.roomUseOrthographicCamera; }, | ||||||
|  | 			set(value) { this.$store.commit('device/set', { key: 'roomUseOrthographicCamera', value }); } | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		roomGraphicsQuality: { | ||||||
|  | 			get() { return this.$store.state.device.roomGraphicsQuality; }, | ||||||
|  | 			set(value) { this.$store.commit('device/set', { key: 'roomGraphicsQuality', value }); } | ||||||
|  | 		}, | ||||||
|  |  | ||||||
| 		games_reversi_showBoardLabels: { | 		games_reversi_showBoardLabels: { | ||||||
| 			get() { return this.$store.state.settings.gamesReversiShowBoardLabels; }, | 			get() { return this.$store.state.settings.gamesReversiShowBoardLabels; }, | ||||||
| 			set(value) { this.$store.dispatch('settings/set', { key: 'gamesReversiShowBoardLabels', value }); } | 			set(value) { this.$store.dispatch('settings/set', { key: 'gamesReversiShowBoardLabels', value }); } | ||||||
| @@ -532,6 +556,11 @@ export default Vue.extend({ | |||||||
| 			set(value) { this.$store.commit('device/set', { key: 'mobileNotificationPosition', value }); } | 			set(value) { this.$store.commit('device/set', { key: 'mobileNotificationPosition', value }); } | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | 		enableMobileQuickNotificationView: { | ||||||
|  | 			get() { return this.$store.state.device.enableMobileQuickNotificationView; }, | ||||||
|  | 			set(value) { this.$store.commit('device/set', { key: 'enableMobileQuickNotificationView', value }); } | ||||||
|  | 		}, | ||||||
|  |  | ||||||
| 		homeProfile: { | 		homeProfile: { | ||||||
| 			get() { return this.$store.state.device.homeProfile; }, | 			get() { return this.$store.state.device.homeProfile; }, | ||||||
| 			set(value) { this.$store.commit('device/set', { key: 'homeProfile', value }); } | 			set(value) { this.$store.commit('device/set', { key: 'homeProfile', value }); } | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import i18n from '../../../../i18n'; | import i18n from '../../../../i18n'; | ||||||
| import * as uuid from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('desktop/views/components/settings.tags.vue'), | 	i18n: i18n('desktop/views/components/settings.tags.vue'), | ||||||
|   | |||||||
| @@ -125,7 +125,7 @@ import Vue from 'vue'; | |||||||
| import i18n from '../../../../i18n'; | import i18n from '../../../../i18n'; | ||||||
| import { lightTheme, darkTheme, builtinThemes, applyTheme, Theme } from '../../../../theme'; | import { lightTheme, darkTheme, builtinThemes, applyTheme, Theme } from '../../../../theme'; | ||||||
| import { Chrome } from 'vue-color'; | import { Chrome } from 'vue-color'; | ||||||
| import * as uuid from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||||
| import * as tinycolor from 'tinycolor2'; | import * as tinycolor from 'tinycolor2'; | ||||||
| import * as JSON5 from 'json5'; | import * as JSON5 from 'json5'; | ||||||
| import { faMoon, faSun } from '@fortawesome/free-regular-svg-icons'; | import { faMoon, faSun } from '@fortawesome/free-regular-svg-icons'; | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ | |||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import * as uuid from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	props: { | 	props: { | ||||||
|   | |||||||
| @@ -64,8 +64,7 @@ export default Vue.extend({ | |||||||
| 	methods: { | 	methods: { | ||||||
| 		onMousedown(e: MouseEvent) { | 		onMousedown(e: MouseEvent) { | ||||||
| 			function distance(p, q) { | 			function distance(p, q) { | ||||||
| 				const sqrt = Math.sqrt, pow = Math.pow; | 				return Math.hypot(p.x - q.x, p.y - q.y); | ||||||
| 				return sqrt(pow(p.x - q.x, 2) + pow(p.y - q.y, 2)); |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			function calcCircleScale(boxW, boxH, circleCenterX, circleCenterY) { | 			function calcCircleScale(boxW, boxH, circleCenterX, circleCenterY) { | ||||||
|   | |||||||
| @@ -49,6 +49,7 @@ export default Vue.extend({ | |||||||
| 	}, | 	}, | ||||||
| 	methods: { | 	methods: { | ||||||
| 		toggle() { | 		toggle() { | ||||||
|  | 			if (this.disabled) return; | ||||||
| 			this.$emit('change', !this.checked); | 			this.$emit('change', !this.checked); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| <template> | <template> | ||||||
| <mfm :text="user.name || user.username" :plain="true" :nowrap="true" :custom-emojis="user.emojis"/> | <mfm :text="user.name || user.username" :plain="true" :nowrap="nowrap" :custom-emojis="user.emojis"/> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| @@ -10,7 +10,11 @@ export default Vue.extend({ | |||||||
| 		user: { | 		user: { | ||||||
| 			type: Object, | 			type: Object, | ||||||
| 			required: true | 			required: true | ||||||
| 		} | 		}, | ||||||
|  | 		nowrap: { | ||||||
|  | 			type: Boolean, | ||||||
|  | 			default: true | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ | |||||||
| 			<template v-if="active"><fa icon="angle-up"/></template> | 			<template v-if="active"><fa icon="angle-up"/></template> | ||||||
| 			<template v-else><fa icon="angle-down"/></template> | 			<template v-else><fa icon="angle-down"/></template> | ||||||
| 		</button> | 		</button> | ||||||
| 		<span><slot name="header"></slot></span> | 		<span class="header"><slot name="header"></slot></span> | ||||||
| 		<span class="count" v-if="count > 0">({{ count }})</span> | 		<span class="count" v-if="count > 0">({{ count }})</span> | ||||||
| 		<button v-if="!isTemporaryColumn" class="menu" ref="menu" @click.stop="showMenu"><fa icon="caret-down"/></button> | 		<button v-if="!isTemporaryColumn" class="menu" ref="menu" @click.stop="showMenu"><fa icon="caret-down"/></button> | ||||||
| 		<button v-else class="close" @click.stop="close"><fa icon="times"/></button> | 		<button v-else class="close" @click.stop="close"><fa icon="times"/></button> | ||||||
| @@ -395,7 +395,13 @@ export default Vue.extend({ | |||||||
| 		&.indicate | 		&.indicate | ||||||
| 			box-shadow 0 3px 0 0 var(--primary) | 			box-shadow 0 3px 0 0 var(--primary) | ||||||
|  |  | ||||||
| 		> span | 		> .header | ||||||
|  | 			display inline-block | ||||||
|  | 			align-items center | ||||||
|  | 			overflow hidden | ||||||
|  | 			text-overflow ellipsis | ||||||
|  | 			white-space nowrap | ||||||
|  |  | ||||||
| 			[data-icon] | 			[data-icon] | ||||||
| 				margin-right 8px | 				margin-right 8px | ||||||
|  |  | ||||||
| @@ -403,6 +409,9 @@ export default Vue.extend({ | |||||||
| 			margin-left 4px | 			margin-left 4px | ||||||
| 			opacity 0.5 | 			opacity 0.5 | ||||||
| 		 | 		 | ||||||
|  | 		> span:only-of-type | ||||||
|  | 			width 100% | ||||||
|  |  | ||||||
| 		> .toggleActive | 		> .toggleActive | ||||||
| 		> .menu | 		> .menu | ||||||
| 		> .close | 		> .close | ||||||
|   | |||||||
| @@ -54,8 +54,8 @@ | |||||||
| 		<div> | 		<div> | ||||||
| 			<header> | 			<header> | ||||||
| 				<fa icon="user-clock" class="icon"/> | 				<fa icon="user-clock" class="icon"/> | ||||||
| 				<router-link :to="notification.user | userPage"> | 				<router-link :to="notification.user | userPage" class="name"> | ||||||
| 					<mk-user-name :user="notification.user" class="name"/> | 					<mk-user-name :user="notification.user"/> | ||||||
| 				</router-link> | 				</router-link> | ||||||
| 				<mk-time :time="notification.createdAt"/> | 				<mk-time :time="notification.createdAt"/> | ||||||
| 			</header> | 			</header> | ||||||
| @@ -67,8 +67,8 @@ | |||||||
| 		<div> | 		<div> | ||||||
| 			<header> | 			<header> | ||||||
| 				<fa icon="chart-pie" class="icon"/> | 				<fa icon="chart-pie" class="icon"/> | ||||||
| 				<router-link :to="notification.user | userPage"> | 				<router-link :to="notification.user | userPage" class="name"> | ||||||
| 					<mk-user-name :user="notification.user" class="name"/> | 					<mk-user-name :user="notification.user"/> | ||||||
| 				</router-link> | 				</router-link> | ||||||
| 				<mk-time :time="notification.createdAt"/> | 				<mk-time :time="notification.createdAt"/> | ||||||
| 			</header> | 			</header> | ||||||
| @@ -167,6 +167,10 @@ export default Vue.extend({ | |||||||
| 					display inline-block | 					display inline-block | ||||||
| 					margin-right 3px | 					margin-right 3px | ||||||
|  |  | ||||||
|  | 		&.reaction | ||||||
|  | 			> div > header | ||||||
|  | 				align-items normal | ||||||
|  |  | ||||||
| 		&.renote | 		&.renote | ||||||
| 			> div > header [data-icon] | 			> div > header [data-icon] | ||||||
| 				color #77B255 | 				color #77B255 | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| <template> | <template> | ||||||
| <x-column :name="name" :column="column" :is-stacked="isStacked"> | <x-column :name="name" :column="column" :is-stacked="isStacked" :menu="menu"> | ||||||
| 	<template #header><fa :icon="['far', 'bell']"/>{{ name }}</template> | 	<template #header><fa :icon="['far', 'bell']"/>{{ name }}</template> | ||||||
|  |  | ||||||
| 	<x-notifications/> | 	<x-notifications :type="column.notificationType === 'all' ? null : column.notificationType"/> | ||||||
| </x-column> | </x-column> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| @@ -30,11 +30,46 @@ export default Vue.extend({ | |||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			menu: null, | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
| 	computed: { | 	computed: { | ||||||
| 		name(): string { | 		name(): string { | ||||||
| 			if (this.column.name) return this.column.name; | 			if (this.column.name) return this.column.name; | ||||||
| 			return this.$t('@deck.notifications'); | 			return this.$t('@deck.notifications'); | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
|  | 	created() { | ||||||
|  | 		if (this.column.notificationType == null) { | ||||||
|  | 			this.column.notificationType = 'all'; | ||||||
|  | 			this.$store.commit('updateDeckColumn', this.column); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		this.menu = [{ | ||||||
|  | 			icon: 'cog', | ||||||
|  | 			text: this.$t('@.notification-type'), | ||||||
|  | 			action: () => { | ||||||
|  | 				this.$root.dialog({ | ||||||
|  | 					title: this.$t('@.notification-type'), | ||||||
|  | 					type: null, | ||||||
|  | 					select: { | ||||||
|  | 						items: ['all', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest'].map(x => ({ | ||||||
|  | 							value: x, text: this.$t('@.notification-types.' + x) | ||||||
|  | 						})) | ||||||
|  | 						default: this.column.notificationType, | ||||||
|  | 					}, | ||||||
|  | 					showCancelButton: true | ||||||
|  | 				}).then(({ canceled, result: type }) => { | ||||||
|  | 					if (canceled) return; | ||||||
|  | 					this.column.notificationType = type; | ||||||
|  | 					this.$store.commit('updateDeckColumn', this.column); | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		}]; | ||||||
|  | 	}, | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -47,12 +47,22 @@ export default Vue.extend({ | |||||||
| 		}), | 		}), | ||||||
| 	], | 	], | ||||||
|  |  | ||||||
|  | 	props: { | ||||||
|  | 		type: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: false | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			connection: null, | 			connection: null, | ||||||
| 			pagination: { | 			pagination: { | ||||||
| 				endpoint: 'i/notifications', | 				endpoint: 'i/notifications', | ||||||
| 				limit: 20, | 				limit: 20, | ||||||
|  | 				params: () => ({ | ||||||
|  | 					includeTypes: this.type ? [this.type] : undefined | ||||||
|  | 				}) | ||||||
| 			} | 			} | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| @@ -69,6 +79,12 @@ export default Vue.extend({ | |||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
|  | 	watch: { | ||||||
|  | 		type() { | ||||||
|  | 			this.reload(); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
| 	mounted() { | 	mounted() { | ||||||
| 		this.connection = this.$root.stream.useSharedConnection('main'); | 		this.connection = this.$root.stream.useSharedConnection('main'); | ||||||
| 		this.connection.on('notification', this.onNotification); | 		this.connection.on('notification', this.onNotification); | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ | |||||||
| 				<mk-follow-button v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" class="follow" mini/> | 				<mk-follow-button v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" class="follow" mini/> | ||||||
| 				<mk-avatar class="avatar" :user="user" :disable-preview="true" :key="user.id"/> | 				<mk-avatar class="avatar" :user="user" :disable-preview="true" :key="user.id"/> | ||||||
| 				<router-link class="name" :to="user | userPage()"> | 				<router-link class="name" :to="user | userPage()"> | ||||||
| 					<mk-user-name :user="user" :key="user.id"/> | 					<mk-user-name :user="user" :key="user.id" :nowrap="false"/> | ||||||
| 				</router-link> | 				</router-link> | ||||||
| 				<span class="acct">@{{ user | acct }} <fa v-if="user.isLocked == true" class="locked" icon="lock" fixed-width/></span> | 				<span class="acct">@{{ user | acct }} <fa v-if="user.isLocked == true" class="locked" icon="lock" fixed-width/></span> | ||||||
| 				<span class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</span> | 				<span class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</span> | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ import i18n from '../../../i18n'; | |||||||
| import XColumnCore from './deck.column-core.vue'; | import XColumnCore from './deck.column-core.vue'; | ||||||
| import Menu from '../../../common/views/components/menu.vue'; | import Menu from '../../../common/views/components/menu.vue'; | ||||||
|  |  | ||||||
| import * as uuid from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('deck'), | 	i18n: i18n('deck'), | ||||||
|   | |||||||
| @@ -54,7 +54,7 @@ import Vue from 'vue'; | |||||||
| import i18n from '../../../i18n'; | import i18n from '../../../i18n'; | ||||||
| import XColumn from './deck.column.vue'; | import XColumn from './deck.column.vue'; | ||||||
| import * as XDraggable from 'vuedraggable'; | import * as XDraggable from 'vuedraggable'; | ||||||
| import * as uuid from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n(), | 	i18n: i18n(), | ||||||
|   | |||||||
| @@ -143,7 +143,11 @@ export default Vue.extend({ | |||||||
| 		this.$root.getMeta().then(meta => { | 		this.$root.getMeta().then(meta => { | ||||||
| 			this.meta = meta; | 			this.meta = meta; | ||||||
| 		}); | 		}); | ||||||
| 	} | 	}, | ||||||
|  |  | ||||||
|  | 	mounted() { | ||||||
|  | 		document.title = this.$root.instanceName; | ||||||
|  | 	}, | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -40,5 +40,9 @@ export default Vue.extend({ | |||||||
| 			icon: faStar | 			icon: faStar | ||||||
| 		}); | 		}); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
|  | 	mounted() { | ||||||
|  | 		document.title = this.$root.instanceName; | ||||||
|  | 	}, | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -40,5 +40,9 @@ export default Vue.extend({ | |||||||
| 			icon: faNewspaper | 			icon: faNewspaper | ||||||
| 		}); | 		}); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
|  | 	mounted() { | ||||||
|  | 		document.title = this.$root.instanceName; | ||||||
|  | 	}, | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ | |||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import * as uuid from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||||
| import { faPlus, faQuestion } from '@fortawesome/free-solid-svg-icons'; | import { faPlus, faQuestion } from '@fortawesome/free-solid-svg-icons'; | ||||||
| import i18n from '../../../../../i18n'; | import i18n from '../../../../../i18n'; | ||||||
| import XContainer from '../page-editor.container.vue'; | import XContainer from '../page-editor.container.vue'; | ||||||
| @@ -76,7 +76,7 @@ export default Vue.extend({ | |||||||
| 			}); | 			}); | ||||||
| 			if (canceled) return; | 			if (canceled) return; | ||||||
|  |  | ||||||
| 			const id = uuid.v4(); | 			const id = uuid(); | ||||||
| 			this.value.children.push({ id, type }); | 			this.value.children.push({ id, type }); | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ | |||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import * as uuid from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||||
| import { faPlus, faPencilAlt } from '@fortawesome/free-solid-svg-icons'; | import { faPlus, faPencilAlt } from '@fortawesome/free-solid-svg-icons'; | ||||||
| import { faStickyNote } from '@fortawesome/free-regular-svg-icons'; | import { faStickyNote } from '@fortawesome/free-regular-svg-icons'; | ||||||
| import i18n from '../../../../../i18n'; | import i18n from '../../../../../i18n'; | ||||||
| @@ -88,7 +88,7 @@ export default Vue.extend({ | |||||||
| 			}); | 			}); | ||||||
| 			if (canceled) return; | 			if (canceled) return; | ||||||
|  |  | ||||||
| 			const id = uuid.v4(); | 			const id = uuid(); | ||||||
| 			this.value.children.push({ id, type }); | 			this.value.children.push({ id, type }); | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -58,7 +58,7 @@ import i18n from '../../../../i18n'; | |||||||
| import XContainer from './page-editor.container.vue'; | import XContainer from './page-editor.container.vue'; | ||||||
| import { faPencilAlt, faPlug } from '@fortawesome/free-solid-svg-icons'; | import { faPencilAlt, faPlug } from '@fortawesome/free-solid-svg-icons'; | ||||||
| import { isLiteralBlock, funcDefs, blockDefs } from '../../../../../../misc/aiscript/index'; | import { isLiteralBlock, funcDefs, blockDefs } from '../../../../../../misc/aiscript/index'; | ||||||
| import * as uuid from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('pages'), | 	i18n: i18n('pages'), | ||||||
| @@ -143,7 +143,7 @@ export default Vue.extend({ | |||||||
| 			this.warn = null; | 			this.warn = null; | ||||||
|  |  | ||||||
| 			if (this.value.type === 'fn') { | 			if (this.value.type === 'fn') { | ||||||
| 				const id = uuid.v4(); | 				const id = uuid(); | ||||||
| 				this.value.value = {}; | 				this.value.value = {}; | ||||||
| 				Vue.set(this.value.value, 'slots', []); | 				Vue.set(this.value.value, 'slots', []); | ||||||
| 				Vue.set(this.value.value, 'expression', { id, type: null }); | 				Vue.set(this.value.value, 'expression', { id, type: null }); | ||||||
| @@ -156,7 +156,7 @@ export default Vue.extend({ | |||||||
|  |  | ||||||
| 				const empties = []; | 				const empties = []; | ||||||
| 				for (let i = 0; i < fn.value.slots.length; i++) { | 				for (let i = 0; i < fn.value.slots.length; i++) { | ||||||
| 					const id = uuid.v4(); | 					const id = uuid(); | ||||||
| 					empties.push({ id, type: null }); | 					empties.push({ id, type: null }); | ||||||
| 				} | 				} | ||||||
| 				Vue.set(this.value, 'args', empties); | 				Vue.set(this.value, 'args', empties); | ||||||
| @@ -167,7 +167,7 @@ export default Vue.extend({ | |||||||
|  |  | ||||||
| 			const empties = []; | 			const empties = []; | ||||||
| 			for (let i = 0; i < funcDefs[this.value.type].in.length; i++) { | 			for (let i = 0; i < funcDefs[this.value.type].in.length; i++) { | ||||||
| 				const id = uuid.v4(); | 				const id = uuid(); | ||||||
| 				empties.push({ id, type: null }); | 				empties.push({ id, type: null }); | ||||||
| 			} | 			} | ||||||
| 			Vue.set(this.value, 'args', empties); | 			Vue.set(this.value, 'args', empties); | ||||||
|   | |||||||
| @@ -99,7 +99,7 @@ import { faSave, faStickyNote, faTrashAlt } from '@fortawesome/free-regular-svg- | |||||||
| import i18n from '../../../../i18n'; | import i18n from '../../../../i18n'; | ||||||
| import XVariable from './page-editor.script-block.vue'; | import XVariable from './page-editor.script-block.vue'; | ||||||
| import XBlocks from './page-editor.blocks.vue'; | import XBlocks from './page-editor.blocks.vue'; | ||||||
| import * as uuid from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||||
| import { blockDefs } from '../../../../../../misc/aiscript/index'; | import { blockDefs } from '../../../../../../misc/aiscript/index'; | ||||||
| import { ASTypeChecker } from '../../../../../../misc/aiscript/type-checker'; | import { ASTypeChecker } from '../../../../../../misc/aiscript/type-checker'; | ||||||
| import { url } from '../../../../config'; | import { url } from '../../../../config'; | ||||||
| @@ -201,7 +201,7 @@ export default Vue.extend({ | |||||||
| 			this.variables = this.page.variables; | 			this.variables = this.page.variables; | ||||||
| 			this.eyeCatchingImageId = this.page.eyeCatchingImageId; | 			this.eyeCatchingImageId = this.page.eyeCatchingImageId; | ||||||
| 		} else { | 		} else { | ||||||
| 			const id = uuid.v4(); | 			const id = uuid(); | ||||||
| 			this.content = [{ | 			this.content = [{ | ||||||
| 				id, | 				id, | ||||||
| 				type: 'text', | 				type: 'text', | ||||||
| @@ -220,9 +220,7 @@ export default Vue.extend({ | |||||||
|  |  | ||||||
| 	methods: { | 	methods: { | ||||||
| 		save() { | 		save() { | ||||||
| 			if (this.pageId) { | 			const options = { | ||||||
| 				this.$root.api('pages/update', { |  | ||||||
| 					pageId: this.pageId, |  | ||||||
| 				title: this.title.trim(), | 				title: this.title.trim(), | ||||||
| 				name: this.name.trim(), | 				name: this.name.trim(), | ||||||
| 				summary: this.summary, | 				summary: this.summary, | ||||||
| @@ -232,25 +230,38 @@ export default Vue.extend({ | |||||||
| 				content: this.content, | 				content: this.content, | ||||||
| 				variables: this.variables, | 				variables: this.variables, | ||||||
| 				eyeCatchingImageId: this.eyeCatchingImageId, | 				eyeCatchingImageId: this.eyeCatchingImageId, | ||||||
| 				}).then(page => { | 			}; | ||||||
|  |  | ||||||
|  | 			const onError = err => { | ||||||
|  | 				if (err.id == '3d81ceae-475f-4600-b2a8-2bc116157532') { | ||||||
|  | 					if (err.info.param == 'name') { | ||||||
|  | 						this.$root.dialog({ | ||||||
|  | 							type: 'error', | ||||||
|  | 							title: this.$t('title-invalid-name'), | ||||||
|  | 							text: this.$t('text-invalid-name') | ||||||
|  | 						}); | ||||||
|  | 					} | ||||||
|  | 				} else if (err.code == 'NAME_ALREADY_EXISTS') { | ||||||
|  | 					this.$root.dialog({ | ||||||
|  | 						type: 'error', | ||||||
|  | 						text: this.$t('name-already-exists') | ||||||
|  | 					}); | ||||||
|  | 				} | ||||||
|  | 			}; | ||||||
|  |  | ||||||
|  | 			if (this.pageId) { | ||||||
|  | 				options.pageId = this.pageId; | ||||||
|  | 				this.$root.api('pages/update', options) | ||||||
|  | 				.then(page => { | ||||||
| 					this.currentName = this.name.trim(); | 					this.currentName = this.name.trim(); | ||||||
| 					this.$root.dialog({ | 					this.$root.dialog({ | ||||||
| 						type: 'success', | 						type: 'success', | ||||||
| 						text: this.$t('page-updated') | 						text: this.$t('page-updated') | ||||||
| 					}); | 					}); | ||||||
| 				}); | 				}).catch(onError); | ||||||
| 			} else { | 			} else { | ||||||
| 				this.$root.api('pages/create', { | 				this.$root.api('pages/create', options) | ||||||
| 					title: this.title.trim(), | 				.then(page => { | ||||||
| 					name: this.name.trim(), |  | ||||||
| 					summary: this.summary, |  | ||||||
| 					font: this.font, |  | ||||||
| 					hideTitleWhenPinned: this.hideTitleWhenPinned, |  | ||||||
| 					alignCenter: this.alignCenter, |  | ||||||
| 					content: this.content, |  | ||||||
| 					variables: this.variables, |  | ||||||
| 					eyeCatchingImageId: this.eyeCatchingImageId, |  | ||||||
| 				}).then(page => { |  | ||||||
| 					this.pageId = page.id; | 					this.pageId = page.id; | ||||||
| 					this.currentName = this.name.trim(); | 					this.currentName = this.name.trim(); | ||||||
| 					this.$root.dialog({ | 					this.$root.dialog({ | ||||||
| @@ -258,7 +269,7 @@ export default Vue.extend({ | |||||||
| 						text: this.$t('page-created') | 						text: this.$t('page-created') | ||||||
| 					}); | 					}); | ||||||
| 					this.$router.push(`/i/pages/edit/${this.pageId}`); | 					this.$router.push(`/i/pages/edit/${this.pageId}`); | ||||||
| 				}); | 				}).catch(onError); | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| @@ -292,7 +303,7 @@ export default Vue.extend({ | |||||||
| 			}); | 			}); | ||||||
| 			if (canceled) return; | 			if (canceled) return; | ||||||
|  |  | ||||||
| 			const id = uuid.v4(); | 			const id = uuid(); | ||||||
| 			this.content.push({ id, type }); | 			this.content.push({ id, type }); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| @@ -316,7 +327,7 @@ export default Vue.extend({ | |||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			const id = uuid.v4(); | 			const id = uuid(); | ||||||
| 			this.variables.push({ id, name, type: null }); | 			this.variables.push({ id, name, type: null }); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -52,6 +52,9 @@ export default Vue.extend({ | |||||||
| 			icon: faStickyNote | 			icon: faStickyNote | ||||||
| 		}); | 		}); | ||||||
| 	}, | 	}, | ||||||
|  | 	mounted() { | ||||||
|  | 		document.title = this.$root.instanceName; | ||||||
|  | 	}, | ||||||
| 	methods: { | 	methods: { | ||||||
| 		create() { | 		create() { | ||||||
| 			this.$router.push(`/i/pages/new`); | 			this.$router.push(`/i/pages/new`); | ||||||
|   | |||||||
							
								
								
									
										106
									
								
								src/client/app/common/views/pages/room/preview.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								src/client/app/common/views/pages/room/preview.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | |||||||
|  | <template> | ||||||
|  | <canvas width=224 height=128></canvas> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import Vue from 'vue'; | ||||||
|  | import * as THREE from 'three'; | ||||||
|  |  | ||||||
|  | export default Vue.extend({ | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			selected: null, | ||||||
|  | 			objectHeight: 0, | ||||||
|  | 			orbitRadius: 5 | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	mounted() { | ||||||
|  | 		const canvas = this.$el; | ||||||
|  |  | ||||||
|  | 		const width = canvas.width; | ||||||
|  | 		const height = canvas.height; | ||||||
|  |  | ||||||
|  | 		const scene = new THREE.Scene(); | ||||||
|  |  | ||||||
|  | 		const renderer = new THREE.WebGLRenderer({ | ||||||
|  | 			canvas: canvas, | ||||||
|  | 			antialias: true, | ||||||
|  | 			alpha: false | ||||||
|  | 		}); | ||||||
|  | 		renderer.setPixelRatio(window.devicePixelRatio); | ||||||
|  | 		renderer.setSize(width, height); | ||||||
|  | 		renderer.setClearColor(0x000000); | ||||||
|  | 		renderer.autoClear = false; | ||||||
|  | 		renderer.shadowMap.enabled = true; | ||||||
|  | 		renderer.shadowMap.cullFace = THREE.CullFaceBack; | ||||||
|  |  | ||||||
|  | 		const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 100); | ||||||
|  | 		camera.zoom = 10; | ||||||
|  | 		camera.position.x = 0; | ||||||
|  | 		camera.position.y = 2; | ||||||
|  | 		camera.position.z = 0; | ||||||
|  | 		camera.updateProjectionMatrix(); | ||||||
|  | 		scene.add(camera); | ||||||
|  |  | ||||||
|  | 		const ambientLight = new THREE.AmbientLight(0xffffff, 1); | ||||||
|  | 		ambientLight.castShadow = false; | ||||||
|  | 		scene.add(ambientLight); | ||||||
|  |  | ||||||
|  | 		const light = new THREE.PointLight(0xffffff, 1, 100); | ||||||
|  | 		light.position.set(3, 3, 3); | ||||||
|  | 		scene.add(light); | ||||||
|  |  | ||||||
|  | 		const grid = new THREE.GridHelper(5, 16, 0x444444, 0x222222); | ||||||
|  | 		scene.add(grid); | ||||||
|  |  | ||||||
|  | 		const render = () => { | ||||||
|  | 			const timer = Date.now() * 0.0004; | ||||||
|  | 			requestAnimationFrame(render); | ||||||
|  | 			 | ||||||
|  | 			camera.position.y = Math.sin(Math.PI / 6) * this.orbitRadius;	// Math.PI / 6 => 30deg | ||||||
|  | 			camera.position.z = Math.cos(timer) * this.orbitRadius; | ||||||
|  | 			camera.position.x = Math.sin(timer) * this.orbitRadius; | ||||||
|  | 			camera.lookAt(new THREE.Vector3(0, this.objectHeight / 2, 0)); | ||||||
|  | 			renderer.render(scene, camera); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		this.selected = selected => { | ||||||
|  | 			const obj = selected.clone(); | ||||||
|  |  | ||||||
|  | 			// Remove current object | ||||||
|  | 			const current = scene.getObjectByName('obj'); | ||||||
|  | 			if (current != null) { | ||||||
|  | 				scene.remove(current); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Add new object | ||||||
|  | 			obj.name = 'obj'; | ||||||
|  | 			obj.position.x = 0; | ||||||
|  | 			obj.position.y = 0; | ||||||
|  | 			obj.position.z = 0; | ||||||
|  | 			obj.rotation.x = 0; | ||||||
|  | 			obj.rotation.y = 0; | ||||||
|  | 			obj.rotation.z = 0; | ||||||
|  | 			obj.traverse(child => { | ||||||
|  | 				if (child instanceof THREE.Mesh) { | ||||||
|  | 					child.material = child.material.clone(); | ||||||
|  | 					return child.material.emissive.setHex(0x000000); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 			const objectBoundingBox = new THREE.Box3().setFromObject(obj); | ||||||
|  | 			this.objectHeight = objectBoundingBox.max.y - objectBoundingBox.min.y; | ||||||
|  |  | ||||||
|  | 			const objectWidth = objectBoundingBox.max.x - objectBoundingBox.min.x; | ||||||
|  | 			const objectDepth = objectBoundingBox.max.z - objectBoundingBox.min.z; | ||||||
|  |  | ||||||
|  | 			const horizontal = Math.hypot(objectWidth, objectDepth) / camera.aspect; | ||||||
|  | 			this.orbitRadius = Math.max(horizontal, this.objectHeight) * camera.zoom * 0.625 / Math.tan(camera.fov * 0.5 * (Math.PI / 180)); | ||||||
|  | 		 | ||||||
|  | 			scene.add(obj); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		render(); | ||||||
|  | 	}, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
							
								
								
									
										310
									
								
								src/client/app/common/views/pages/room/room.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										310
									
								
								src/client/app/common/views/pages/room/room.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,310 @@ | |||||||
|  | <template> | ||||||
|  | <div class="hveuntkp"> | ||||||
|  | 	<div class="controller" v-if="objectSelected"> | ||||||
|  | 		<section> | ||||||
|  | 			<p class="name">{{ selectedFurnitureName }}</p> | ||||||
|  | 			<x-preview ref="preview"/> | ||||||
|  | 			<template v-if="selectedFurnitureInfo.props"> | ||||||
|  | 				<div v-for="k in Object.keys(selectedFurnitureInfo.props)" :key="k"> | ||||||
|  | 					<p>{{ k }}</p> | ||||||
|  | 					<template v-if="selectedFurnitureInfo.props[k] === 'image'"> | ||||||
|  | 						<ui-button @click="chooseImage(k)">{{ $t('chooseImage') }}</ui-button> | ||||||
|  | 					</template> | ||||||
|  | 					<template v-else-if="selectedFurnitureInfo.props[k] === 'color'"> | ||||||
|  | 						<input type="color" :value="selectedFurnitureProps ? selectedFurnitureProps[k] : null" @change="updateColor(k, $event)"/> | ||||||
|  | 					</template> | ||||||
|  | 				</div> | ||||||
|  | 			</template> | ||||||
|  | 		</section> | ||||||
|  | 		<section> | ||||||
|  | 			<ui-button @click="translate()" :primary="isTranslateMode"><fa :icon="faArrowsAlt"/> {{ $t('translate') }}</ui-button> | ||||||
|  | 			<ui-button @click="rotate()" :primary="isRotateMode"><fa :icon="faUndo"/> {{ $t('rotate') }}</ui-button> | ||||||
|  | 			<ui-button v-if="isTranslateMode || isRotateMode" @click="exit()"><fa :icon="faBan"/> {{ $t('exit') }}</ui-button> | ||||||
|  | 		</section> | ||||||
|  | 		<section> | ||||||
|  | 			<ui-button @click="remove()"><fa :icon="faTrashAlt"/> {{ $t('remove') }}</ui-button> | ||||||
|  | 		</section> | ||||||
|  | 	</div> | ||||||
|  |  | ||||||
|  | 	<div class="menu" v-if="isMyRoom"> | ||||||
|  | 		<section> | ||||||
|  | 			<ui-button @click="add()"><fa :icon="faBoxOpen"/> {{ $t('add-furniture') }}</ui-button> | ||||||
|  | 		</section> | ||||||
|  | 		<section> | ||||||
|  | 			<ui-select :value="roomType" @input="updateRoomType($event)"> | ||||||
|  | 				<template #label>{{ $t('room-type') }}</template> | ||||||
|  | 				<option value="default">{{ $t('rooms.default') }}</option> | ||||||
|  | 				<option value="washitsu">{{ $t('rooms.washitsu') }}</option> | ||||||
|  | 			</ui-select> | ||||||
|  | 			<label v-if="roomType === 'default'"> | ||||||
|  | 				<span>{{ $t('carpet-color') }}</span> | ||||||
|  | 				<input type="color" :value="carpetColor" @change="updateCarpetColor($event)"/> | ||||||
|  | 			</label> | ||||||
|  | 		</section> | ||||||
|  | 		<section> | ||||||
|  | 			<ui-button :primary="changed" @click="save()"><fa :icon="faSave"/> {{ $t('save') }}</ui-button> | ||||||
|  | 			<ui-button @click="clear()"><fa :icon="faBroom"/> {{ $t('clear') }}</ui-button> | ||||||
|  | 		</section> | ||||||
|  | 	</div> | ||||||
|  | </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import Vue from 'vue'; | ||||||
|  | import i18n from '../../../../i18n'; | ||||||
|  | import { Room } from '../../../scripts/room/room'; | ||||||
|  | import parseAcct from '../../../../../../misc/acct/parse'; | ||||||
|  | import XPreview from './preview.vue'; | ||||||
|  | const storeItems = require('../../../scripts/room/furnitures.json5'); | ||||||
|  | import { faBoxOpen, faUndo, faArrowsAlt, faBan, faBroom } from '@fortawesome/free-solid-svg-icons'; | ||||||
|  | import { faSave, faTrashAlt } from '@fortawesome/free-regular-svg-icons'; | ||||||
|  | import { query as urlQuery } from '../../../../../../prelude/url'; | ||||||
|  |  | ||||||
|  | let room: Room; | ||||||
|  |  | ||||||
|  | export default Vue.extend({ | ||||||
|  | 	i18n: i18n('room'), | ||||||
|  |  | ||||||
|  | 	components: { | ||||||
|  | 		XPreview | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	props: { | ||||||
|  | 		acct: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: true | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			objectSelected: false, | ||||||
|  | 			selectedFurnitureName: null, | ||||||
|  | 			selectedFurnitureInfo: null, | ||||||
|  | 			selectedFurnitureProps: null, | ||||||
|  | 			roomType: null, | ||||||
|  | 			carpetColor: null, | ||||||
|  | 			isTranslateMode: false, | ||||||
|  | 			isRotateMode: false, | ||||||
|  | 			isMyRoom: false, | ||||||
|  | 			changed: false, | ||||||
|  | 			faBoxOpen, faSave, faTrashAlt, faUndo, faArrowsAlt, faBan, faBroom, | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	async mounted() { | ||||||
|  | 		window.addEventListener('beforeunload', this.beforeunload); | ||||||
|  |  | ||||||
|  | 		const user = await this.$root.api('users/show', { | ||||||
|  | 			...parseAcct(this.acct) | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		this.isMyRoom = this.$store.getters.isSignedIn && this.$store.state.i.id === user.id; | ||||||
|  |  | ||||||
|  | 		const roomInfo = await this.$root.api('room/show', { | ||||||
|  | 			userId: user.id | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		this.roomType = roomInfo.roomType; | ||||||
|  | 		this.carpetColor = roomInfo.carpetColor; | ||||||
|  |  | ||||||
|  | 		room = new Room(user, this.isMyRoom, roomInfo, this.$el, { | ||||||
|  | 			graphicsQuality: this.$store.state.device.roomGraphicsQuality, | ||||||
|  | 			onChangeSelect: obj => { | ||||||
|  | 				this.objectSelected = obj != null; | ||||||
|  | 				if (obj) { | ||||||
|  | 					const f = room.findFurnitureById(obj.name); | ||||||
|  | 					this.selectedFurnitureName = this.$t('furnitures.' + f.type); | ||||||
|  | 					this.selectedFurnitureInfo = storeItems.find(x => x.id === f.type); | ||||||
|  | 					this.selectedFurnitureProps = f.props | ||||||
|  | 						? JSON.parse(JSON.stringify(f.props)) // Disable reactivity | ||||||
|  | 						: null; | ||||||
|  | 					this.$nextTick(() => { | ||||||
|  | 						this.$refs.preview.selected(obj); | ||||||
|  | 					}); | ||||||
|  | 				} | ||||||
|  | 			}, | ||||||
|  | 			useOrthographicCamera: this.$store.state.device.roomUseOrthographicCamera | ||||||
|  | 		}); | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	beforeRouteLeave(to, from, next) { | ||||||
|  | 		if (this.changed) { | ||||||
|  | 			this.$root.dialog({ | ||||||
|  | 				type: 'warning', | ||||||
|  | 				text: this.$t('leave-confirm'), | ||||||
|  | 				showCancelButton: true | ||||||
|  | 			}).then(({ canceled }) => { | ||||||
|  | 				if (canceled) { | ||||||
|  | 					next(false); | ||||||
|  | 				} else { | ||||||
|  | 					next(); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 		} else { | ||||||
|  | 			next(); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	beforeDestroy() { | ||||||
|  | 		room.destroy(); | ||||||
|  | 		window.removeEventListener('beforeunload', this.beforeunload); | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	methods: { | ||||||
|  | 		beforeunload(e: BeforeUnloadEvent) { | ||||||
|  | 			if (this.changed) { | ||||||
|  | 				e.preventDefault(); | ||||||
|  | 				e.returnValue = ''; | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		async add() { | ||||||
|  | 			const { canceled, result: id } = await this.$root.dialog({ | ||||||
|  | 				type: null, | ||||||
|  | 				title: this.$t('add-furniture'), | ||||||
|  | 				select: { | ||||||
|  | 					items: storeItems.map(item => ({ | ||||||
|  | 						value: item.id, text: this.$t('furnitures.' + item.id) | ||||||
|  | 					})) | ||||||
|  | 				}, | ||||||
|  | 				showCancelButton: true | ||||||
|  | 			}); | ||||||
|  | 			if (canceled) return; | ||||||
|  | 			room.addFurniture(id); | ||||||
|  | 			this.changed = true; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		remove() { | ||||||
|  | 			this.isTranslateMode = false; | ||||||
|  | 			this.isRotateMode = false; | ||||||
|  | 			room.removeFurniture(); | ||||||
|  | 			this.changed = true; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		save() { | ||||||
|  | 			this.$root.api('room/update', { | ||||||
|  | 				room: room.getRoomInfo() | ||||||
|  | 			}).then(() => { | ||||||
|  | 				this.changed = false; | ||||||
|  | 				this.$root.dialog({ | ||||||
|  | 					type: 'success', | ||||||
|  | 					text: this.$t('saved') | ||||||
|  | 				}); | ||||||
|  | 			}).catch((e: any) => { | ||||||
|  | 				this.$root.dialog({ | ||||||
|  | 					type: 'error', | ||||||
|  | 					text: e.message | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		clear() { | ||||||
|  | 			this.$root.dialog({ | ||||||
|  | 				type: 'warning', | ||||||
|  | 				text: this.$t('clear-confirm'), | ||||||
|  | 				showCancelButton: true | ||||||
|  | 			}).then(({ canceled }) => { | ||||||
|  | 				if (canceled) return; | ||||||
|  | 				room.removeAllFurnitures(); | ||||||
|  | 				this.changed = true; | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		chooseImage(key) { | ||||||
|  | 			this.$chooseDriveFile({ | ||||||
|  | 				multiple: false | ||||||
|  | 			}).then(file => { | ||||||
|  | 				room.updateProp(key, `/proxy/?${urlQuery({ url: file.thumbnailUrl })}`); | ||||||
|  | 				this.$refs.preview.selected(room.getSelectedObject()); | ||||||
|  | 				this.changed = true; | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		updateColor(key, ev) { | ||||||
|  | 			room.updateProp(key, ev.target.value); | ||||||
|  | 			this.$refs.preview.selected(room.getSelectedObject()); | ||||||
|  | 			this.changed = true; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		updateCarpetColor(ev) { | ||||||
|  | 			room.updateCarpetColor(ev.target.value); | ||||||
|  | 			this.carpetColor = ev.target.value; | ||||||
|  | 			this.changed = true; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		updateRoomType(type) { | ||||||
|  | 			room.changeRoomType(type); | ||||||
|  | 			this.roomType = type; | ||||||
|  | 			this.changed = true; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		translate() { | ||||||
|  | 			if (this.isTranslateMode) { | ||||||
|  | 				this.exit(); | ||||||
|  | 			} else { | ||||||
|  | 				this.isRotateMode = false; | ||||||
|  | 				this.isTranslateMode = true; | ||||||
|  | 				room.enterTransformMode('translate'); | ||||||
|  | 			} | ||||||
|  | 			this.changed = true; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		rotate() { | ||||||
|  | 			if (this.isRotateMode) { | ||||||
|  | 				this.exit(); | ||||||
|  | 			} else { | ||||||
|  | 				this.isTranslateMode = false; | ||||||
|  | 				this.isRotateMode = true; | ||||||
|  | 				room.enterTransformMode('rotate'); | ||||||
|  | 			} | ||||||
|  | 			this.changed = true; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		exit() { | ||||||
|  | 			this.isTranslateMode = false; | ||||||
|  | 			this.isRotateMode = false; | ||||||
|  | 			room.exitTransformMode(); | ||||||
|  | 			this.changed = true; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="stylus" scoped> | ||||||
|  | .hveuntkp | ||||||
|  | 	> .controller | ||||||
|  | 	> .menu | ||||||
|  | 		position fixed | ||||||
|  | 		z-index 1 | ||||||
|  | 		padding 16px | ||||||
|  | 		background var(--face) | ||||||
|  | 		color var(--text) | ||||||
|  |  | ||||||
|  | 		> section | ||||||
|  | 			padding 16px 0 | ||||||
|  |  | ||||||
|  | 			&:first-child | ||||||
|  | 				padding-top 0 | ||||||
|  |  | ||||||
|  | 			&:last-child | ||||||
|  | 				padding-bottom 0 | ||||||
|  |  | ||||||
|  | 			&:not(:last-child) | ||||||
|  | 				border-bottom solid 1px var(--faceDivider) | ||||||
|  |  | ||||||
|  | 	> .controller | ||||||
|  | 		top 16px | ||||||
|  | 		left 16px | ||||||
|  | 		width 256px | ||||||
|  |  | ||||||
|  | 		> section | ||||||
|  | 			> .name | ||||||
|  | 				margin 0 | ||||||
|  |  | ||||||
|  | 	> .menu | ||||||
|  | 		top 16px | ||||||
|  | 		right 16px | ||||||
|  | 		width 256px | ||||||
|  | 	 | ||||||
|  | </style> | ||||||
| @@ -62,6 +62,8 @@ export default Vue.extend({ | |||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| 	mounted() { | 	mounted() { | ||||||
|  | 		document.title = this.$root.instanceName; | ||||||
|  |  | ||||||
| 		this.$root.api('users/groups/owned').then(groups => { | 		this.$root.api('users/groups/owned').then(groups => { | ||||||
| 			this.ownedGroups = groups; | 			this.ownedGroups = groups; | ||||||
| 		}); | 		}); | ||||||
|   | |||||||
| @@ -33,6 +33,8 @@ export default Vue.extend({ | |||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| 	mounted() { | 	mounted() { | ||||||
|  | 		document.title = this.$root.instanceName; | ||||||
|  | 		 | ||||||
| 		this.$root.api('users/lists/list').then(lists => { | 		this.$root.api('users/lists/list').then(lists => { | ||||||
| 			this.fetching = false; | 			this.fetching = false; | ||||||
| 			this.lists = lists; | 			this.lists = lists; | ||||||
|   | |||||||
| @@ -25,7 +25,8 @@ export default define({ | |||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			text: null, | 			text: null, | ||||||
| 			changed: false | 			changed: false, | ||||||
|  | 			timeoutId: null | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -45,6 +46,8 @@ export default define({ | |||||||
|  |  | ||||||
| 		onChange() { | 		onChange() { | ||||||
| 			this.changed = true; | 			this.changed = true; | ||||||
|  | 			clearTimeout(this.timeoutId); | ||||||
|  | 			this.timeoutId = setTimeout(this.saveMemo, 1000); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		saveMemo() { | 		saveMemo() { | ||||||
|   | |||||||
| @@ -71,7 +71,7 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import define from '../../../common/define-widget'; | import define from '../../../common/define-widget'; | ||||||
| import i18n from '../../../i18n'; | import i18n from '../../../i18n'; | ||||||
| import * as uuid from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||||
|  |  | ||||||
| export default define({ | export default define({ | ||||||
| 	name: 'posts-monitor', | 	name: 'posts-monitor', | ||||||
|   | |||||||
| @@ -63,7 +63,7 @@ | |||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import * as uuid from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	props: ['connection'], | 	props: ['connection'], | ||||||
|   | |||||||
| @@ -63,7 +63,10 @@ init(async (launch, os) => { | |||||||
| 					this.$root.newAsync(() => import('./views/components/post-form-window.vue').then(m => m.default), { | 					this.$root.newAsync(() => import('./views/components/post-form-window.vue').then(m => m.default), { | ||||||
| 						reply: o.reply, | 						reply: o.reply, | ||||||
| 						mention: o.mention, | 						mention: o.mention, | ||||||
| 						animation: o.animation == null ? true : o.animation | 						animation: o.animation == null ? true : o.animation, | ||||||
|  | 						initialText: o.initialText, | ||||||
|  | 						instant: o.instant, | ||||||
|  | 						initialNote: o.initialNote, | ||||||
| 					}).then(vm => { | 					}).then(vm => { | ||||||
| 						if (o.cb) vm.$once('closed', o.cb); | 						if (o.cb) vm.$once('closed', o.cb); | ||||||
| 					}); | 					}); | ||||||
| @@ -182,8 +185,10 @@ init(async (launch, os) => { | |||||||
| 			{ path: '/i/messaging/:user', component: MkMessagingRoom }, | 			{ path: '/i/messaging/:user', component: MkMessagingRoom }, | ||||||
| 			{ path: '/i/drive', component: MkDrive }, | 			{ path: '/i/drive', component: MkDrive }, | ||||||
| 			{ path: '/i/drive/folder/:folder', component: MkDrive }, | 			{ path: '/i/drive/folder/:folder', component: MkDrive }, | ||||||
| 			{ path: '/i/settings', component: MkSettings }, | 			{ path: '/i/settings', redirect: '/i/settings/profile' }, | ||||||
|  | 			{ path: '/i/settings/:page', component: MkSettings }, | ||||||
| 			{ path: '/selectdrive', component: MkSelectDrive }, | 			{ path: '/selectdrive', component: MkSelectDrive }, | ||||||
|  | 			{ path: '/@:acct/room', props: true, component: () => import('../common/views/pages/room/room.vue').then(m => m.default) }, | ||||||
| 			{ path: '/share', component: MkShare }, | 			{ path: '/share', component: MkShare }, | ||||||
| 			{ path: '/games/reversi/:game?', component: MkReversi }, | 			{ path: '/games/reversi/:game?', component: MkReversi }, | ||||||
| 			{ path: '/authorize-follow', component: MkFollow }, | 			{ path: '/authorize-follow', component: MkFollow }, | ||||||
|   | |||||||
| @@ -11,17 +11,16 @@ | |||||||
| 		<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition" tag="div"> | 		<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition" tag="div"> | ||||||
| 			<template v-for="(notification, i) in _notifications"> | 			<template v-for="(notification, i) in _notifications"> | ||||||
| 				<div class="notification" :class="notification.type" :key="notification.id"> | 				<div class="notification" :class="notification.type" :key="notification.id"> | ||||||
| 					<mk-time :time="notification.createdAt"/> |  | ||||||
|  |  | ||||||
| 					<template v-if="notification.type == 'reaction'"> | 					<template v-if="notification.type == 'reaction'"> | ||||||
| 						<mk-avatar class="avatar" :user="notification.user"/> | 						<mk-avatar class="avatar" :user="notification.user"/> | ||||||
| 						<div class="text"> | 						<div class="text"> | ||||||
| 							<p> | 							<header> | ||||||
| 								<mk-reaction-icon :reaction="notification.reaction"/> | 								<mk-reaction-icon :reaction="notification.reaction" class="icon"/> | ||||||
| 								<router-link :to="notification.user | userPage" v-user-preview="notification.user.id"> | 								<router-link :to="notification.user | userPage" v-user-preview="notification.user.id" class="name"> | ||||||
| 									<mk-user-name :user="notification.user"/> | 									<mk-user-name :user="notification.user"/> | ||||||
| 								</router-link> | 								</router-link> | ||||||
| 							</p> | 								<mk-time :time="notification.createdAt"/> | ||||||
|  | 							</header> | ||||||
| 							<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)"> | 							<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)"> | ||||||
| 								<fa icon="quote-left"/> | 								<fa icon="quote-left"/> | ||||||
| 									<mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :custom-emojis="notification.note.emojis"/> | 									<mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :custom-emojis="notification.note.emojis"/> | ||||||
| @@ -33,11 +32,13 @@ | |||||||
| 					<template v-if="notification.type == 'renote'"> | 					<template v-if="notification.type == 'renote'"> | ||||||
| 						<mk-avatar class="avatar" :user="notification.note.user"/> | 						<mk-avatar class="avatar" :user="notification.note.user"/> | ||||||
| 						<div class="text"> | 						<div class="text"> | ||||||
| 							<p><fa icon="retweet"/> | 							<header> | ||||||
| 								<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId"> | 								<fa icon="retweet" class="icon"/> | ||||||
|  | 								<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId" class="name"> | ||||||
| 									<mk-user-name :user="notification.note.user"/> | 									<mk-user-name :user="notification.note.user"/> | ||||||
| 								</router-link> | 								</router-link> | ||||||
| 							</p> | 								<mk-time :time="notification.createdAt"/> | ||||||
|  | 							</header> | ||||||
| 							<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note.renote)"> | 							<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note.renote)"> | ||||||
| 								<fa icon="quote-left"/> | 								<fa icon="quote-left"/> | ||||||
| 									<mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="true" :custom-emojis="notification.note.renote.emojis"/> | 									<mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="true" :custom-emojis="notification.note.renote.emojis"/> | ||||||
| @@ -49,11 +50,13 @@ | |||||||
| 					<template v-if="notification.type == 'quote'"> | 					<template v-if="notification.type == 'quote'"> | ||||||
| 						<mk-avatar class="avatar" :user="notification.note.user"/> | 						<mk-avatar class="avatar" :user="notification.note.user"/> | ||||||
| 						<div class="text"> | 						<div class="text"> | ||||||
| 							<p><fa icon="quote-left"/> | 							<header> | ||||||
| 								<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId"> | 								<fa icon="quote-left" class="icon"/> | ||||||
|  | 								<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId" class="name"> | ||||||
| 									<mk-user-name :user="notification.note.user"/> | 									<mk-user-name :user="notification.note.user"/> | ||||||
| 								</router-link> | 								</router-link> | ||||||
| 							</p> | 								<mk-time :time="notification.createdAt"/> | ||||||
|  | 							</header> | ||||||
| 							<router-link class="note-preview" :to="notification.note | notePage" :title="getNoteSummary(notification.note)"> | 							<router-link class="note-preview" :to="notification.note | notePage" :title="getNoteSummary(notification.note)"> | ||||||
| 								<mfm :text="getNoteSummary(notification.note)" :plain="true" :custom-emojis="notification.note.emojis"/> | 								<mfm :text="getNoteSummary(notification.note)" :plain="true" :custom-emojis="notification.note.emojis"/> | ||||||
| 							</router-link> | 							</router-link> | ||||||
| @@ -63,33 +66,39 @@ | |||||||
| 					<template v-if="notification.type == 'follow'"> | 					<template v-if="notification.type == 'follow'"> | ||||||
| 						<mk-avatar class="avatar" :user="notification.user"/> | 						<mk-avatar class="avatar" :user="notification.user"/> | ||||||
| 						<div class="text"> | 						<div class="text"> | ||||||
| 							<p><fa icon="user-plus"/> | 							<header> | ||||||
| 								<router-link :to="notification.user | userPage" v-user-preview="notification.user.id"> | 								<fa icon="user-plus" class="icon"/> | ||||||
|  | 								<router-link :to="notification.user | userPage" v-user-preview="notification.user.id" class="name"> | ||||||
| 									<mk-user-name :user="notification.user"/> | 									<mk-user-name :user="notification.user"/> | ||||||
| 								</router-link> | 								</router-link> | ||||||
| 							</p> | 								<mk-time :time="notification.createdAt"/> | ||||||
|  | 							</header> | ||||||
| 						</div> | 						</div> | ||||||
| 					</template> | 					</template> | ||||||
|  |  | ||||||
| 					<template v-if="notification.type == 'receiveFollowRequest'"> | 					<template v-if="notification.type == 'receiveFollowRequest'"> | ||||||
| 						<mk-avatar class="avatar" :user="notification.user"/> | 						<mk-avatar class="avatar" :user="notification.user"/> | ||||||
| 						<div class="text"> | 						<div class="text"> | ||||||
| 							<p><fa icon="user-clock"/> | 							<header> | ||||||
| 								<router-link :to="notification.user | userPage" v-user-preview="notification.user.id"> | 								<fa icon="user-clock" class="icon"/> | ||||||
|  | 								<router-link :to="notification.user | userPage" v-user-preview="notification.user.id" class="name"> | ||||||
| 									<mk-user-name :user="notification.user"/> | 									<mk-user-name :user="notification.user"/> | ||||||
| 								</router-link> | 								</router-link> | ||||||
| 							</p> | 								<mk-time :time="notification.createdAt"/> | ||||||
|  | 							</header> | ||||||
| 						</div> | 						</div> | ||||||
| 					</template> | 					</template> | ||||||
|  |  | ||||||
| 					<template v-if="notification.type == 'reply'"> | 					<template v-if="notification.type == 'reply'"> | ||||||
| 						<mk-avatar class="avatar" :user="notification.note.user"/> | 						<mk-avatar class="avatar" :user="notification.note.user"/> | ||||||
| 						<div class="text"> | 						<div class="text"> | ||||||
| 							<p><fa icon="reply"/> | 							<header> | ||||||
| 								<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId"> | 								<fa icon="reply" class="icon"/> | ||||||
|  | 								<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId" class="name"> | ||||||
| 									<mk-user-name :user="notification.note.user"/> | 									<mk-user-name :user="notification.note.user"/> | ||||||
| 								</router-link> | 								</router-link> | ||||||
| 							</p> | 								<mk-time :time="notification.createdAt"/> | ||||||
|  | 							</header> | ||||||
| 							<router-link class="note-preview" :to="notification.note | notePage" :title="getNoteSummary(notification.note)"> | 							<router-link class="note-preview" :to="notification.note | notePage" :title="getNoteSummary(notification.note)"> | ||||||
| 								<mfm :text="getNoteSummary(notification.note)" :plain="true" :custom-emojis="notification.note.emojis"/> | 								<mfm :text="getNoteSummary(notification.note)" :plain="true" :custom-emojis="notification.note.emojis"/> | ||||||
| 							</router-link> | 							</router-link> | ||||||
| @@ -99,11 +108,13 @@ | |||||||
| 					<template v-if="notification.type == 'mention'"> | 					<template v-if="notification.type == 'mention'"> | ||||||
| 						<mk-avatar class="avatar" :user="notification.note.user"/> | 						<mk-avatar class="avatar" :user="notification.note.user"/> | ||||||
| 						<div class="text"> | 						<div class="text"> | ||||||
| 							<p><fa icon="at"/> | 							<header> | ||||||
| 								<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId"> | 								<fa icon="at" class="icon"/> | ||||||
|  | 								<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId" class="name"> | ||||||
| 									<mk-user-name :user="notification.note.user"/> | 									<mk-user-name :user="notification.note.user"/> | ||||||
| 								</router-link> | 								</router-link> | ||||||
| 							</p> | 								<mk-time :time="notification.createdAt"/> | ||||||
|  | 							</header> | ||||||
| 							<router-link class="note-preview" :to="notification.note | notePage" :title="getNoteSummary(notification.note)"> | 							<router-link class="note-preview" :to="notification.note | notePage" :title="getNoteSummary(notification.note)"> | ||||||
| 								<mfm :text="getNoteSummary(notification.note)" :plain="true" :custom-emojis="notification.note.emojis"/> | 								<mfm :text="getNoteSummary(notification.note)" :plain="true" :custom-emojis="notification.note.emojis"/> | ||||||
| 							</router-link> | 							</router-link> | ||||||
| @@ -113,9 +124,13 @@ | |||||||
| 					<template v-if="notification.type == 'pollVote'"> | 					<template v-if="notification.type == 'pollVote'"> | ||||||
| 						<mk-avatar class="avatar" :user="notification.user"/> | 						<mk-avatar class="avatar" :user="notification.user"/> | ||||||
| 						<div class="text"> | 						<div class="text"> | ||||||
| 							<p><fa icon="chart-pie"/><router-link :to="notification.user | userPage" v-user-preview="notification.user.id"> | 							<header> | ||||||
|  | 								<fa icon="chart-pie" class="icon"/> | ||||||
|  | 								<router-link :to="notification.user | userPage" v-user-preview="notification.user.id" class="name"> | ||||||
| 									<mk-user-name :user="notification.user"/> | 									<mk-user-name :user="notification.user"/> | ||||||
| 							</router-link></p> | 								</router-link> | ||||||
|  | 								<mk-time :time="notification.createdAt"/> | ||||||
|  | 							</header> | ||||||
| 							<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)"> | 							<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)"> | ||||||
| 								<fa icon="quote-left"/> | 								<fa icon="quote-left"/> | ||||||
| 									<mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :custom-emojis="notification.note.emojis"/> | 									<mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :custom-emojis="notification.note.emojis"/> | ||||||
| @@ -150,9 +165,18 @@ export default Vue.extend({ | |||||||
| 	i18n: i18n(), | 	i18n: i18n(), | ||||||
|  |  | ||||||
| 	mixins: [ | 	mixins: [ | ||||||
| 		paging({}), | 		paging({ | ||||||
|  | 			isContainer: true | ||||||
|  | 		}), | ||||||
| 	], | 	], | ||||||
|  |  | ||||||
|  | 	props: { | ||||||
|  | 		type: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: false | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			connection: null, | 			connection: null, | ||||||
| @@ -160,6 +184,9 @@ export default Vue.extend({ | |||||||
| 			pagination: { | 			pagination: { | ||||||
| 				endpoint: 'i/notifications', | 				endpoint: 'i/notifications', | ||||||
| 				limit: 10, | 				limit: 10, | ||||||
|  | 				params: () => ({ | ||||||
|  | 					includeTypes: this.type ? [this.type] : undefined | ||||||
|  | 				}) | ||||||
| 			} | 			} | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| @@ -176,6 +203,12 @@ export default Vue.extend({ | |||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
|  | 	watch: { | ||||||
|  | 		type() { | ||||||
|  | 			this.reload(); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
| 	mounted() { | 	mounted() { | ||||||
| 		this.connection = this.$root.stream.useSharedConnection('main'); | 		this.connection = this.$root.stream.useSharedConnection('main'); | ||||||
| 		this.connection.on('notification', this.onNotification); | 		this.connection.on('notification', this.onNotification); | ||||||
| @@ -225,15 +258,6 @@ export default Vue.extend({ | |||||||
| 				&:last-child | 				&:last-child | ||||||
| 					border-bottom none | 					border-bottom none | ||||||
|  |  | ||||||
| 				> .mk-time |  | ||||||
| 					display inline |  | ||||||
| 					position absolute |  | ||||||
| 					top 16px |  | ||||||
| 					right 12px |  | ||||||
| 					vertical-align top |  | ||||||
| 					color var(--noteHeaderInfo) |  | ||||||
| 					font-size small |  | ||||||
|  |  | ||||||
| 				&:after | 				&:after | ||||||
| 					content "" | 					content "" | ||||||
| 					display block | 					display block | ||||||
| @@ -254,12 +278,23 @@ export default Vue.extend({ | |||||||
| 					width calc(100% - 36px) | 					width calc(100% - 36px) | ||||||
| 					padding-left 8px | 					padding-left 8px | ||||||
|  |  | ||||||
| 					p | 					> header | ||||||
| 						margin 0 | 						display flex | ||||||
|  | 						align-items baseline | ||||||
|  | 						white-space nowrap | ||||||
|  |  | ||||||
| 						[data-icon], .mk-reaction-icon | 						> .icon | ||||||
| 							margin-right 4px | 							margin-right 4px | ||||||
|  |  | ||||||
|  | 						> .name | ||||||
|  | 							overflow hidden | ||||||
|  | 							text-overflow ellipsis | ||||||
|  |  | ||||||
|  | 						> .mk-time | ||||||
|  | 							margin-left auto | ||||||
|  | 							color var(--noteHeaderInfo) | ||||||
|  | 							font-size 0.9em | ||||||
|  |  | ||||||
| 				.note-preview | 				.note-preview | ||||||
| 					color var(--noteText) | 					color var(--noteText) | ||||||
| 					display inline-block | 					display inline-block | ||||||
| @@ -280,20 +315,24 @@ export default Vue.extend({ | |||||||
| 						display inline-block | 						display inline-block | ||||||
| 						margin-right 3px | 						margin-right 3px | ||||||
|  |  | ||||||
|  | 				&.reaction | ||||||
|  | 					.text header | ||||||
|  | 						align-items normal | ||||||
|  |  | ||||||
| 				&.renote, &.quote | 				&.renote, &.quote | ||||||
| 					.text p [data-icon] | 					.text header [data-icon] | ||||||
| 						color #77B255 | 						color #77B255 | ||||||
|  |  | ||||||
| 				&.follow | 				&.follow | ||||||
| 					.text p [data-icon] | 					.text header [data-icon] | ||||||
| 						color #53c7ce | 						color #53c7ce | ||||||
|  |  | ||||||
| 				&.receiveFollowRequest | 				&.receiveFollowRequest | ||||||
| 					.text p [data-icon] | 					.text header [data-icon] | ||||||
| 						color #888 | 						color #888 | ||||||
|  |  | ||||||
| 				&.reply, &.mention | 				&.reply, &.mention | ||||||
| 					.text p [data-icon] | 					.text header [data-icon] | ||||||
| 						color #555 | 						color #555 | ||||||
|  |  | ||||||
| 			> .date | 			> .date | ||||||
|   | |||||||
| @@ -5,8 +5,8 @@ | |||||||
| 			<span class="icon" v-if="geo"><fa icon="map-marker-alt"/></span> | 			<span class="icon" v-if="geo"><fa icon="map-marker-alt"/></span> | ||||||
| 			<span v-if="!reply">{{ $t('note') }}</span> | 			<span v-if="!reply">{{ $t('note') }}</span> | ||||||
| 			<span v-if="reply">{{ $t('reply') }}</span> | 			<span v-if="reply">{{ $t('reply') }}</span> | ||||||
| 			<span class="count" v-if="files.length != 0">{{ this.$t('attaches').replace('{}', files.length) }}</span> | 			<span class="count" v-if="files.length != 0">{{ $t('attaches').replace('{}', files.length) }}</span> | ||||||
| 			<span class="count" v-if="uploadings.length != 0">{{ this.$t('uploading-media').replace('{}', uploadings.length) }}<mk-ellipsis/></span> | 			<span class="count" v-if="uploadings.length != 0">{{ $t('uploading-media').replace('{}', uploadings.length) }}<mk-ellipsis/></span> | ||||||
| 		</span> | 		</span> | ||||||
| 	</template> | 	</template> | ||||||
|  |  | ||||||
| @@ -15,6 +15,10 @@ | |||||||
| 		<x-post-form ref="form" | 		<x-post-form ref="form" | ||||||
| 			:reply="reply" | 			:reply="reply" | ||||||
| 			:mention="mention" | 			:mention="mention" | ||||||
|  | 			:initial-text="initialText" | ||||||
|  | 			:initial-note="initialNote" | ||||||
|  | 			:instant="instant" | ||||||
|  |  | ||||||
| 			@posted="onPosted" | 			@posted="onPosted" | ||||||
| 			@change-uploadings="onChangeUploadings" | 			@change-uploadings="onChangeUploadings" | ||||||
| 			@change-attached-files="onChangeFiles" | 			@change-attached-files="onChangeFiles" | ||||||
| @@ -50,7 +54,23 @@ export default Vue.extend({ | |||||||
| 			type: Boolean, | 			type: Boolean, | ||||||
| 			required: false, | 			required: false, | ||||||
| 			default: true | 			default: true | ||||||
| 		} | 		}, | ||||||
|  |  | ||||||
|  | 		initialText: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: false | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		initialNote: { | ||||||
|  | 			type: Object, | ||||||
|  | 			required: false | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		instant: { | ||||||
|  | 			type: Boolean, | ||||||
|  | 			required: false, | ||||||
|  | 			default: false | ||||||
|  | 		}, | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	data() { | 	data() { | ||||||
|   | |||||||
| @@ -10,14 +10,18 @@ | |||||||
| 			<b>{{ $t('@.post-form.recent-tags') }}:</b> | 			<b>{{ $t('@.post-form.recent-tags') }}:</b> | ||||||
| 			<a v-for="tag in recentHashtags.slice(0, 5)" @click="addTag(tag)" :title="$t('@.post-form.click-to-tagging')">#{{ tag }}</a> | 			<a v-for="tag in recentHashtags.slice(0, 5)" @click="addTag(tag)" :title="$t('@.post-form.click-to-tagging')">#{{ tag }}</a> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div class="with-quote" v-if="quoteId">{{ $t('@.post-form.quote-attached') }} <a @click="quoteId = null">[x]</a></div> | 		<div class="with-quote" v-if="quoteId"><fa icon="quote-left"/> {{ $t('@.post-form.quote-attached') }}<button @click="quoteId = null"><fa icon="times"/></button></div> | ||||||
| 		<div v-if="visibility === 'specified'" class="visibleUsers"> | 		<div v-if="visibility === 'specified'" class="to-specified"> | ||||||
|  | 			<fa icon="envelope"/> {{ $t('@.post-form.specified-recipient') }} | ||||||
|  | 			<div class="visibleUsers"> | ||||||
| 				<span v-for="u in visibleUsers"> | 				<span v-for="u in visibleUsers"> | ||||||
| 				<mk-user-name :user="u"/><a @click="removeVisibleUser(u)">[x]</a> | 					<mk-user-name :user="u"/> | ||||||
|  | 					<button @click="removeVisibleUser(u)"><fa icon="times"/></button> | ||||||
| 				</span> | 				</span> | ||||||
| 			<a @click="addVisibleUser">{{ $t('@.post-form.add-visible-user') }}</a> | 				<button @click="addVisibleUser">{{ $t('@.post-form.add-visible-user') }}</button> | ||||||
| 			</div> | 			</div> | ||||||
| 		<div class="local-only" v-if="localOnly === true">{{ $t('@.post-form.local-only-message') }}</div> | 		</div> | ||||||
|  | 		<div class="local-only" v-if="localOnly === true"><fa icon="heart"/> {{ $t('@.post-form.local-only-message') }}</div> | ||||||
| 		<input v-show="useCw" ref="cw" v-model="cw" :placeholder="$t('@.post-form.cw-placeholder')" v-autocomplete="{ model: 'cw' }"> | 		<input v-show="useCw" ref="cw" v-model="cw" :placeholder="$t('@.post-form.cw-placeholder')" v-autocomplete="{ model: 'cw' }"> | ||||||
| 		<div class="textarea"> | 		<div class="textarea"> | ||||||
| 			<textarea :class="{ with: (files.length != 0 || poll) }" | 			<textarea :class="{ with: (files.length != 0 || poll) }" | ||||||
| @@ -207,13 +211,37 @@ export default Vue.extend({ | |||||||
| 			margin 0 0 8px 0 | 			margin 0 0 8px 0 | ||||||
| 			color var(--primary) | 			color var(--primary) | ||||||
|  |  | ||||||
|  | 			> button | ||||||
|  | 				padding 4px 8px | ||||||
|  | 				color var(--primaryAlpha04) | ||||||
|  |  | ||||||
|  | 				&:hover | ||||||
|  | 					color var(--primaryAlpha06) | ||||||
|  |  | ||||||
|  | 				&:active | ||||||
|  | 					color var(--primaryDarken30) | ||||||
|  |  | ||||||
|  | 		> .to-specified | ||||||
|  | 			margin 0 0 8px 0 | ||||||
|  | 			color var(--primary) | ||||||
|  |  | ||||||
| 			> .visibleUsers | 			> .visibleUsers | ||||||
| 			margin-bottom 8px | 				display inline | ||||||
|  | 				top -1px | ||||||
| 				font-size 14px | 				font-size 14px | ||||||
|  |  | ||||||
| 				> span | 				> span | ||||||
| 				margin-right 16px | 					margin-left 14px | ||||||
| 				color var(--primary) |  | ||||||
|  | 					> button | ||||||
|  | 						padding 4px 8px | ||||||
|  | 						color var(--primaryAlpha04) | ||||||
|  |  | ||||||
|  | 						&:hover | ||||||
|  | 							color var(--primaryAlpha06) | ||||||
|  |  | ||||||
|  | 						&:active | ||||||
|  | 							color var(--primaryDarken30) | ||||||
|  |  | ||||||
| 		> .local-only | 		> .local-only | ||||||
| 			margin 0 0 8px 0 | 			margin 0 0 8px 0 | ||||||
|   | |||||||
| @@ -1,17 +1,17 @@ | |||||||
| <template> | <template> | ||||||
| <div class="mk-settings"> | <div class="mk-settings"> | ||||||
| 	<div class="nav" :class="{ inWindow }"> | 	<div class="nav" :class="{ inWindow }"> | ||||||
| 		<p :class="{ active: page == 'profile' }" @mousedown="page = 'profile'"><fa icon="user" fixed-width/>{{ $t('@._settings.profile') }}</p> | 		<router-link to="/i/settings/profile" active-class="active"><fa icon="user" fixed-width/>{{ $t('@._settings.profile') }}</router-link> | ||||||
| 		<p :class="{ active: page == 'appearance' }" @mousedown="page = 'appearance'"><fa icon="palette" fixed-width/>{{ $t('@._settings.appearance') }}</p> | 		<router-link to="/i/settings/appearance" active-class="active"><fa icon="palette" fixed-width/>{{ $t('@._settings.appearance') }}</router-link> | ||||||
| 		<p :class="{ active: page == 'behavior' }" @mousedown="page = 'behavior'"><fa icon="desktop" fixed-width/>{{ $t('@._settings.behavior') }}</p> | 		<router-link to="/i/settings/behavior" active-class="active"><fa icon="desktop" fixed-width/>{{ $t('@._settings.behavior') }}</router-link> | ||||||
| 		<p :class="{ active: page == 'notification' }" @mousedown="page = 'notification'"><fa :icon="['far', 'bell']" fixed-width/>{{ $t('@._settings.notification') }}</p> | 		<router-link to="/i/settings/notification" active-class="active"><fa :icon="['far', 'bell']" fixed-width/>{{ $t('@._settings.notification') }}</router-link> | ||||||
| 		<p :class="{ active: page == 'drive' }" @mousedown="page = 'drive'"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</p> | 		<router-link to="/i/settings/drive" active-class="active"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</router-link> | ||||||
| 		<p :class="{ active: page == 'hashtags' }" @mousedown="page = 'hashtags'"><fa icon="hashtag" fixed-width/>{{ $t('@._settings.tags') }}</p> | 		<router-link to="/i/settings/hashtags" active-class="active"><fa icon="hashtag" fixed-width/>{{ $t('@._settings.tags') }}</router-link> | ||||||
| 		<p :class="{ active: page == 'muteAndBlock' }" @mousedown="page = 'muteAndBlock'"><fa icon="ban" fixed-width/>{{ $t('@._settings.mute-and-block') }}</p> | 		<router-link to="/i/settings/muteAndBlock" active-class="active"><fa icon="ban" fixed-width/>{{ $t('@._settings.mute-and-block') }}</router-link> | ||||||
| 		<p :class="{ active: page == 'apps' }" @mousedown="page = 'apps'"><fa icon="puzzle-piece" fixed-width/>{{ $t('@._settings.apps') }}</p> | 		<router-link to="/i/settings/apps" active-class="active"><fa icon="puzzle-piece" fixed-width/>{{ $t('@._settings.apps') }}</router-link> | ||||||
| 		<p :class="{ active: page == 'security' }" @mousedown="page = 'security'"><fa icon="unlock-alt" fixed-width/>{{ $t('@._settings.security') }}</p> | 		<router-link to="/i/settings/security" active-class="active"><fa icon="unlock-alt" fixed-width/>{{ $t('@._settings.security') }}</router-link> | ||||||
| 		<p :class="{ active: page == 'api' }" @mousedown="page = 'api'"><fa icon="key" fixed-width/>API</p> | 		<router-link to="/i/settings/api" active-class="active"><fa icon="key" fixed-width/>API</router-link> | ||||||
| 		<p :class="{ active: page == 'other' }" @mousedown="page = 'other'"><fa icon="cogs" fixed-width/>{{ $t('@._settings.other') }}</p> | 		<router-link to="/i/settings/other" active-class="active"><fa icon="cogs" fixed-width/>{{ $t('@._settings.other') }}</router-link> | ||||||
| 	</div> | 	</div> | ||||||
| 	<div class="pages"> | 	<div class="pages"> | ||||||
| 		<x-settings :page="page"/> | 		<x-settings :page="page"/> | ||||||
| @@ -30,9 +30,9 @@ export default Vue.extend({ | |||||||
| 		XSettings, | 		XSettings, | ||||||
| 	}, | 	}, | ||||||
| 	props: { | 	props: { | ||||||
| 		initialPage: { | 		page: { | ||||||
| 			type: String, | 			type: String, | ||||||
| 			required: false | 			required: true, | ||||||
| 		}, | 		}, | ||||||
| 		inWindow: { | 		inWindow: { | ||||||
| 			type: Boolean, | 			type: Boolean, | ||||||
| @@ -40,11 +40,6 @@ export default Vue.extend({ | |||||||
| 			default: true | 			default: true | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 	data() { |  | ||||||
| 		return { |  | ||||||
| 			page: this.initialPage || 'profile', |  | ||||||
| 		}; |  | ||||||
| 	}, |  | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| @@ -63,7 +58,7 @@ export default Vue.extend({ | |||||||
| 		z-index 1 | 		z-index 1 | ||||||
| 		font-size 15px | 		font-size 15px | ||||||
|  |  | ||||||
| 		> p | 		> a | ||||||
| 			display block | 			display block | ||||||
| 			padding 10px 16px | 			padding 10px 16px | ||||||
| 			margin 0 | 			margin 0 | ||||||
|   | |||||||
| @@ -56,6 +56,13 @@ | |||||||
| 						<i><fa icon="angle-right"/></i> | 						<i><fa icon="angle-right"/></i> | ||||||
| 					</router-link> | 					</router-link> | ||||||
| 				</li> | 				</li> | ||||||
|  | 				<li> | ||||||
|  | 					<router-link :to="`/@${ $store.state.i.username }/room`"> | ||||||
|  | 						<i><fa :icon="faDoorOpen" fixed-width/></i> | ||||||
|  | 						<span>{{ $t('room') }}</span> | ||||||
|  | 						<i><fa icon="angle-right"/></i> | ||||||
|  | 					</router-link> | ||||||
|  | 				</li> | ||||||
| 			</ul> | 			</ul> | ||||||
| 			<ul> | 			<ul> | ||||||
| 				<li> | 				<li> | ||||||
| @@ -106,7 +113,7 @@ import i18n from '../../../i18n'; | |||||||
| // import MkSettingsWindow from './settings-window.vue'; | // import MkSettingsWindow from './settings-window.vue'; | ||||||
| import MkDriveWindow from './drive-window.vue'; | import MkDriveWindow from './drive-window.vue'; | ||||||
| import contains from '../../../common/scripts/contains'; | import contains from '../../../common/scripts/contains'; | ||||||
| import { faHome, faColumns, faUsers } from '@fortawesome/free-solid-svg-icons'; | import { faHome, faColumns, faUsers, faDoorOpen } from '@fortawesome/free-solid-svg-icons'; | ||||||
| import { faMoon, faSun, faStickyNote } from '@fortawesome/free-regular-svg-icons'; | import { faMoon, faSun, faStickyNote } from '@fortawesome/free-regular-svg-icons'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| @@ -114,7 +121,7 @@ export default Vue.extend({ | |||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			isOpen: false, | 			isOpen: false, | ||||||
| 			faHome, faColumns, faMoon, faSun, faStickyNote, faUsers | 			faHome, faColumns, faMoon, faSun, faStickyNote, faUsers, faDoorOpen | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| 	computed: { | 	computed: { | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ | |||||||
| 		<div class="banner" :style="u.bannerUrl ? `background-image: url(${u.bannerUrl})` : ''"></div> | 		<div class="banner" :style="u.bannerUrl ? `background-image: url(${u.bannerUrl})` : ''"></div> | ||||||
| 		<mk-avatar class="avatar" :user="u" :disable-preview="true"/> | 		<mk-avatar class="avatar" :user="u" :disable-preview="true"/> | ||||||
| 		<div class="title"> | 		<div class="title"> | ||||||
| 			<router-link class="name" :to="u | userPage"><mk-user-name :user="u"/></router-link> | 			<router-link class="name" :to="u | userPage"><mk-user-name :user="u" :nowrap="false"/></router-link> | ||||||
| 			<p class="username"><mk-acct :user="u"/></p> | 			<p class="username"><mk-acct :user="u"/></p> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div class="description"> | 		<div class="description"> | ||||||
|   | |||||||
| @@ -79,7 +79,7 @@ | |||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import i18n from '../../../i18n'; | import i18n from '../../../i18n'; | ||||||
| import * as XDraggable from 'vuedraggable'; | import * as XDraggable from 'vuedraggable'; | ||||||
| import * as uuid from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||||
| import XWelcome from '../pages/welcome.vue'; | import XWelcome from '../pages/welcome.vue'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| @@ -224,6 +224,8 @@ export default Vue.extend({ | |||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		addWidget() { | 		addWidget() { | ||||||
|  | 			if(this.widgetAdderSelected == null) return; | ||||||
|  |  | ||||||
| 			this.$store.commit('addHomeWidget', { | 			this.$store.commit('addHomeWidget', { | ||||||
| 				name: this.widgetAdderSelected, | 				name: this.widgetAdderSelected, | ||||||
| 				id: uuid(), | 				id: uuid(), | ||||||
|   | |||||||
| @@ -94,6 +94,8 @@ export default Vue.extend({ | |||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	mounted() { | 	mounted() { | ||||||
|  | 		document.title = this.$root.instanceName; | ||||||
|  |  | ||||||
| 		(this.$refs.tl as any).$once('loaded', () => { | 		(this.$refs.tl as any).$once('loaded', () => { | ||||||
| 			this.$emit('loaded'); | 			this.$emit('loaded'); | ||||||
| 		}); | 		}); | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
| 		<div class="fade"></div> | 		<div class="fade"></div> | ||||||
| 		<div class="title"> | 		<div class="title"> | ||||||
| 			<p class="name"> | 			<p class="name"> | ||||||
| 				<mk-user-name :user="user"/> | 				<mk-user-name :user="user" :nowrap="false"/> | ||||||
| 			</p> | 			</p> | ||||||
| 			<div> | 			<div> | ||||||
| 				<span class="username"><mk-acct :user="user" :detail="true" /></span> | 				<span class="username"><mk-acct :user="user" :detail="true" /></span> | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| <template> | <template> | ||||||
| <mk-ui> | <mk-ui> | ||||||
| 	<main> | 	<main> | ||||||
| 		<x-settings :in-window="false"/> | 		<x-settings :in-window="false" :page="$route.params.page" /> | ||||||
| 	</main> | 	</main> | ||||||
| </mk-ui> | </mk-ui> | ||||||
| </template> | </template> | ||||||
| @@ -13,6 +13,9 @@ export default Vue.extend({ | |||||||
| 	components: { | 	components: { | ||||||
| 		XSettings: () => import('../components/settings.vue').then(m => m.default) | 		XSettings: () => import('../components/settings.vue').then(m => m.default) | ||||||
| 	}, | 	}, | ||||||
|  | 	mounted() { | ||||||
|  | 		document.title = this.$root.instanceName; | ||||||
|  | 	}, | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| <template> | <template> | ||||||
| <div class="mkw-notifications"> | <div class="mkw-notifications"> | ||||||
| 	<ui-container :show-header="!props.compact"> | 	<ui-container :show-header="!props.compact"> | ||||||
| 		<template #header><fa :icon="['far', 'bell']"/>{{ $t('title') }}</template> | 		<template #header><fa :icon="['far', 'bell']"/>{{ props.type === 'all' ? $t('title') : $t('@.notification-types.' + props.type) }}</template> | ||||||
| 		<!-- <button #func :title="$t('title')" @click="settings"><fa icon="cog"/></button> --> | 		<template #func><button :title="$t('@.notification-type')" @click="settings"><fa icon="cog"/></button></template> | ||||||
|  |  | ||||||
| 		<mk-notifications :class="$style.notifications"/> | 		<mk-notifications :class="$style.notifications" :type="props.type === 'all' ? null : props.type"/> | ||||||
| 	</ui-container> | 	</ui-container> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
| @@ -16,13 +16,28 @@ import i18n from '../../../i18n'; | |||||||
| export default define({ | export default define({ | ||||||
| 	name: 'notifications', | 	name: 'notifications', | ||||||
| 	props: () => ({ | 	props: () => ({ | ||||||
| 		compact: false | 		compact: false, | ||||||
|  | 		type: 'all' | ||||||
| 	}) | 	}) | ||||||
| }).extend({ | }).extend({ | ||||||
| 	i18n: i18n('desktop/views/widgets/notifications.vue'), | 	i18n: i18n('desktop/views/widgets/notifications.vue'), | ||||||
| 	methods: { | 	methods: { | ||||||
| 		settings() { | 		settings() { | ||||||
| 			alert('not implemented yet'); | 			this.$root.dialog({ | ||||||
|  | 				title: this.$t('@.notification-type'), | ||||||
|  | 				type: null, | ||||||
|  | 				select: { | ||||||
|  | 					items: ['all', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest'].map(x => ({ | ||||||
|  | 						value: x, text: this.$t('@.notification-types.' + x) | ||||||
|  | 					})) | ||||||
|  | 					default: this.props.type, | ||||||
|  | 				}, | ||||||
|  | 				showCancelButton: true | ||||||
|  | 			}).then(({ canceled, result: type }) => { | ||||||
|  | 				if (canceled) return; | ||||||
|  | 				this.props.type = type; | ||||||
|  | 				this.save(); | ||||||
|  | 			}); | ||||||
| 		}, | 		}, | ||||||
| 		func() { | 		func() { | ||||||
| 			this.props.compact = !this.props.compact; | 			this.props.compact = !this.props.compact; | ||||||
|   | |||||||
| @@ -125,7 +125,8 @@ import { | |||||||
| 	faMapMarker, | 	faMapMarker, | ||||||
| 	faRobot, | 	faRobot, | ||||||
| 	faHourglassHalf, | 	faHourglassHalf, | ||||||
| 	faGavel | 	faGavel, | ||||||
|  | 	faUndoAlt, | ||||||
| } from '@fortawesome/free-solid-svg-icons'; | } from '@fortawesome/free-solid-svg-icons'; | ||||||
|  |  | ||||||
| import { | import { | ||||||
| @@ -258,6 +259,7 @@ library.add( | |||||||
| 	faRobot, | 	faRobot, | ||||||
| 	faHourglassHalf, | 	faHourglassHalf, | ||||||
| 	faGavel, | 	faGavel, | ||||||
|  | 	faUndoAlt, | ||||||
|  |  | ||||||
| 	farBell, | 	farBell, | ||||||
| 	farEnvelope, | 	farEnvelope, | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import autobind from 'autobind-decorator'; | import autobind from 'autobind-decorator'; | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import { EventEmitter } from 'eventemitter3'; | import { EventEmitter } from 'eventemitter3'; | ||||||
| import * as uuid from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||||
|  |  | ||||||
| import initStore from './store'; | import initStore from './store'; | ||||||
| import { apiUrl, version, locale } from './config'; | import { apiUrl, version, locale } from './config'; | ||||||
| @@ -28,7 +28,12 @@ export default class MiOS extends EventEmitter { | |||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	public get instanceName() { | 	public get instanceName() { | ||||||
| 		return this.meta ? (this.meta.data.name || 'Misskey') : 'Misskey'; | 		const siteName = document.querySelector('meta[property="og:site_name"]') as HTMLMetaElement; | ||||||
|  | 		if (siteName && siteName.content) { | ||||||
|  | 			return siteName.content; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return 'Misskey'; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	private isMetaFetching = false; | 	private isMetaFetching = false; | ||||||
| @@ -174,7 +179,7 @@ export default class MiOS extends EventEmitter { | |||||||
| 			// Init service worker | 			// Init service worker | ||||||
| 			if (this.shouldRegisterSw) { | 			if (this.shouldRegisterSw) { | ||||||
| 				this.getMeta().then(data => { | 				this.getMeta().then(data => { | ||||||
| 					this.registerSw(data.swPublickey); | 					if (data.swPublickey) this.registerSw(data.swPublickey); | ||||||
| 				}); | 				}); | ||||||
| 			} | 			} | ||||||
| 		}; | 		}; | ||||||
| @@ -291,7 +296,7 @@ export default class MiOS extends EventEmitter { | |||||||
| 	 * Register service worker | 	 * Register service worker | ||||||
| 	 */ | 	 */ | ||||||
| 	@autobind | 	@autobind | ||||||
| 	private registerSw(swPublickey) { | 	private registerSw(swPublickey: string) { | ||||||
| 		// Check whether service worker and push manager supported | 		// Check whether service worker and push manager supported | ||||||
| 		const isSwSupported = | 		const isSwSupported = | ||||||
| 			('serviceWorker' in navigator) && ('PushManager' in window); | 			('serviceWorker' in navigator) && ('PushManager' in window); | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ import MkIndex from './views/pages/index.vue'; | |||||||
| import MkSignup from './views/pages/signup.vue'; | import MkSignup from './views/pages/signup.vue'; | ||||||
| import MkSelectDrive from './views/pages/selectdrive.vue'; | import MkSelectDrive from './views/pages/selectdrive.vue'; | ||||||
| import MkDrive from './views/pages/drive.vue'; | import MkDrive from './views/pages/drive.vue'; | ||||||
|  | import MkNotifications from './views/pages/notifications.vue'; | ||||||
| import MkMessaging from './views/pages/messaging.vue'; | import MkMessaging from './views/pages/messaging.vue'; | ||||||
| import MkMessagingRoom from './views/pages/messaging-room.vue'; | import MkMessagingRoom from './views/pages/messaging-room.vue'; | ||||||
| import MkNote from './views/pages/note.vue'; | import MkNote from './views/pages/note.vue'; | ||||||
| @@ -54,7 +55,10 @@ init((launch, os) => { | |||||||
| 				const vm = this.$root.new(PostFormDialog, { | 				const vm = this.$root.new(PostFormDialog, { | ||||||
| 					reply: o.reply, | 					reply: o.reply, | ||||||
| 					mention: o.mention, | 					mention: o.mention, | ||||||
| 					renote: o.renote | 					renote: o.renote, | ||||||
|  | 					initialText: o.initialText, | ||||||
|  | 					instant: o.instant, | ||||||
|  | 					initialNote: o.initialNote, | ||||||
| 				}); | 				}); | ||||||
| 				vm.$once('cancel', recover); | 				vm.$once('cancel', recover); | ||||||
| 				vm.$once('posted', recover); | 				vm.$once('posted', recover); | ||||||
| @@ -136,6 +140,7 @@ init((launch, os) => { | |||||||
| 		]), | 		]), | ||||||
| 			{ path: '/signup', name: 'signup', component: MkSignup }, | 			{ path: '/signup', name: 'signup', component: MkSignup }, | ||||||
| 			{ path: '/i/settings', name: 'settings', component: () => import('./views/pages/settings.vue').then(m => m.default) }, | 			{ path: '/i/settings', name: 'settings', component: () => import('./views/pages/settings.vue').then(m => m.default) }, | ||||||
|  | 			{ path: '/i/settings/:page', redirect: '/i/settings' }, | ||||||
| 			{ path: '/i/favorites', name: 'favorites', component: UI, props: route => ({ component: () => import('../common/views/pages/favorites.vue').then(m => m.default), platform: 'mobile' }) }, | 			{ path: '/i/favorites', name: 'favorites', component: UI, props: route => ({ component: () => import('../common/views/pages/favorites.vue').then(m => m.default), platform: 'mobile' }) }, | ||||||
| 			{ path: '/i/pages', name: 'pages', component: UI, props: route => ({ component: () => import('../common/views/pages/pages.vue').then(m => m.default) }) }, | 			{ path: '/i/pages', name: 'pages', component: UI, props: route => ({ component: () => import('../common/views/pages/pages.vue').then(m => m.default) }) }, | ||||||
| 			{ path: '/i/lists', name: 'user-lists', component: UI, props: route => ({ component: () => import('../common/views/pages/user-lists.vue').then(m => m.default) }) }, | 			{ path: '/i/lists', name: 'user-lists', component: UI, props: route => ({ component: () => import('../common/views/pages/user-lists.vue').then(m => m.default) }) }, | ||||||
| @@ -144,6 +149,7 @@ init((launch, os) => { | |||||||
| 			{ path: '/i/groups/:group', component: UI, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.group }) }, | 			{ path: '/i/groups/:group', component: UI, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.group }) }, | ||||||
| 			{ path: '/i/follow-requests', name: 'follow-requests', component: UI, props: route => ({ component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }) }, | 			{ path: '/i/follow-requests', name: 'follow-requests', component: UI, props: route => ({ component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }) }, | ||||||
| 			{ path: '/i/widgets', name: 'widgets', component: () => import('./views/pages/widgets.vue').then(m => m.default) }, | 			{ path: '/i/widgets', name: 'widgets', component: () => import('./views/pages/widgets.vue').then(m => m.default) }, | ||||||
|  | 			{ path: '/i/notifications', name: 'notifications', component: MkNotifications }, | ||||||
| 			{ path: '/i/messaging', name: 'messaging', component: MkMessaging }, | 			{ path: '/i/messaging', name: 'messaging', component: MkMessaging }, | ||||||
| 			{ path: '/i/messaging/group/:group', component: MkMessagingRoom }, | 			{ path: '/i/messaging/group/:group', component: MkMessagingRoom }, | ||||||
| 			{ path: '/i/messaging/:user', component: MkMessagingRoom }, | 			{ path: '/i/messaging/:user', component: MkMessagingRoom }, | ||||||
| @@ -166,6 +172,7 @@ init((launch, os) => { | |||||||
| 			]}, | 			]}, | ||||||
| 			{ path: '/@:user/pages/:page', component: UI, props: route => ({ component: () => import('../common/views/pages/page.vue').then(m => m.default), pageName: route.params.page, username: route.params.user }) }, | 			{ path: '/@:user/pages/:page', component: UI, props: route => ({ component: () => import('../common/views/pages/page.vue').then(m => m.default), pageName: route.params.page, username: route.params.user }) }, | ||||||
| 			{ path: '/@:user/pages/:pageName/view-source', component: UI, props: route => ({ component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), initUser: route.params.user, initPageName: route.params.pageName }) }, | 			{ path: '/@:user/pages/:pageName/view-source', component: UI, props: route => ({ component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), initUser: route.params.user, initPageName: route.params.pageName }) }, | ||||||
|  | 			{ path: '/@:acct/room', props: true, component: () => import('../common/views/pages/room/room.vue').then(m => m.default) }, | ||||||
| 			{ path: '/notes/:note', component: MkNote }, | 			{ path: '/notes/:note', component: MkNote }, | ||||||
| 			{ path: '/authorize-follow', component: MkFollow }, | 			{ path: '/authorize-follow', component: MkFollow }, | ||||||
| 			{ path: '*', component: MkNotFound } | 			{ path: '*', component: MkNotFound } | ||||||
|   | |||||||
| @@ -122,7 +122,7 @@ export default Vue.extend({ | |||||||
| 			this.$root.api('drive/files/delete', { | 			this.$root.api('drive/files/delete', { | ||||||
| 				fileId: this.file.id | 				fileId: this.file.id | ||||||
| 			}).then(() => { | 			}).then(() => { | ||||||
| 				this.browser.cd(this.file.folderId, true); | 				this.browser.cd(this.file.folderId); | ||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -163,8 +163,6 @@ export default Vue.extend({ | |||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		cd(target, silent = false) { | 		cd(target, silent = false) { | ||||||
| 			this.file = null; |  | ||||||
|  |  | ||||||
| 			if (target == null) { | 			if (target == null) { | ||||||
| 				this.goRoot(silent); | 				this.goRoot(silent); | ||||||
| 				return; | 				return; | ||||||
| @@ -172,6 +170,7 @@ export default Vue.extend({ | |||||||
| 				target = target.id; | 				target = target.id; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			this.file = null; | ||||||
| 			this.fetching = true; | 			this.fetching = true; | ||||||
|  |  | ||||||
| 			this.$root.api('drive/folders/show', { | 			this.$root.api('drive/folders/show', { | ||||||
| @@ -244,13 +243,14 @@ export default Vue.extend({ | |||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		goRoot(silent = false) { | 		goRoot(silent = false) { | ||||||
| 			if (this.folder || this.file) { | 			// すでにrootにいるなら何もしない | ||||||
|  | 			if (this.folder == null && this.file == null) return; | ||||||
|  | 			 | ||||||
| 			this.file = null; | 			this.file = null; | ||||||
| 			this.folder = null; | 			this.folder = null; | ||||||
| 			this.hierarchyFolders = []; | 			this.hierarchyFolders = []; | ||||||
| 			this.$emit('move-root', silent); | 			this.$emit('move-root', silent); | ||||||
| 			this.fetch(); | 			this.fetch(); | ||||||
| 			} |  | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		fetch() { | 		fetch() { | ||||||
|   | |||||||
| @@ -100,6 +100,27 @@ export default Vue.extend({ | |||||||
|  |  | ||||||
| <style lang="stylus" scoped> | <style lang="stylus" scoped> | ||||||
| .mk-notification | .mk-notification | ||||||
|  |  | ||||||
|  | 	&.wide | ||||||
|  | 		> .notification | ||||||
|  | 			@media (min-width 350px) | ||||||
|  | 				font-size 14px | ||||||
|  |  | ||||||
|  | 			@media (min-width 500px) | ||||||
|  | 				font-size 16px | ||||||
|  |  | ||||||
|  | 			@media (min-width 600px) | ||||||
|  | 				padding 24px 32px | ||||||
|  |  | ||||||
|  | 			> .avatar | ||||||
|  | 				@media (min-width 500px) | ||||||
|  | 					width 42px | ||||||
|  | 					height 42px | ||||||
|  |  | ||||||
|  | 			> div | ||||||
|  | 				@media (min-width 500px) | ||||||
|  | 					width calc(100% - 42px) | ||||||
|  |  | ||||||
| 	> .notification | 	> .notification | ||||||
| 		padding 16px | 		padding 16px | ||||||
| 		font-size 12px | 		font-size 12px | ||||||
| @@ -159,6 +180,10 @@ export default Vue.extend({ | |||||||
| 					display inline-block | 					display inline-block | ||||||
| 					margin-right 3px | 					margin-right 3px | ||||||
|  |  | ||||||
|  | 		&.reaction | ||||||
|  | 			> div > header | ||||||
|  | 				align-items normal | ||||||
|  |  | ||||||
| 		&.renote | 		&.renote | ||||||
| 			> div > header [data-icon] | 			> div > header [data-icon] | ||||||
| 				color #77B255 | 				color #77B255 | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user