Compare commits
	
		
			206 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					7115bd46ff | ||
| 
						 | 
					f84483896e | ||
| 
						 | 
					fe7bc174fb | ||
| 
						 | 
					e967d9ded3 | ||
| 
						 | 
					c3b55b6849 | ||
| 
						 | 
					3e193c9864 | ||
| 
						 | 
					d64e25e449 | ||
| 
						 | 
					c4707c612d | ||
| 
						 | 
					9d3448c880 | ||
| 
						 | 
					dad6a77645 | ||
| 
						 | 
					f64d3942d7 | ||
| 
						 | 
					45fe754759 | ||
| 
						 | 
					98f1d93628 | ||
| 
						 | 
					8785e1c3a4 | ||
| 
						 | 
					f00ceedae4 | ||
| 
						 | 
					7387e010c2 | ||
| 
						 | 
					a59dfff12f | ||
| 
						 | 
					f6128dd3ff | ||
| 
						 | 
					eeff88ece2 | ||
| 
						 | 
					bae1282f74 | ||
| 
						 | 
					d2007add75 | ||
| 
						 | 
					802db92d98 | ||
| 
						 | 
					a53e1e4ec3 | ||
| 
						 | 
					fd1ef4a62d | ||
| 
						 | 
					8ab9068d8e | ||
| 
						 | 
					47dd30d3b2 | ||
| 
						 | 
					fcdd042b02 | ||
| 
						 | 
					04855f9201 | ||
| 
						 | 
					cf0a5d5735 | ||
| 
						 | 
					bb2db1cf76 | ||
| 
						 | 
					0e69091455 | ||
| 
						 | 
					3cb5ed167a | ||
| 
						 | 
					1ffee15b83 | ||
| 
						 | 
					75b9f31acf | ||
| 
						 | 
					fc56b12690 | ||
| 
						 | 
					60e768436e | ||
| 
						 | 
					84f2192cde | ||
| 
						 | 
					97f2675d40 | ||
| 
						 | 
					6d881d4570 | ||
| 
						 | 
					42cc93dd0f | ||
| 
						 | 
					e9f34a0f09 | ||
| 
						 | 
					b9cb6d1c10 | ||
| 
						 | 
					7bf517e990 | ||
| 
						 | 
					ab54e147f2 | ||
| 
						 | 
					e677540fd6 | ||
| 
						 | 
					31e3aaeda0 | ||
| 
						 | 
					938fc317c9 | ||
| 
						 | 
					4c431c5432 | ||
| 
						 | 
					881b914c6a | ||
| 
						 | 
					df67836c1a | ||
| 
						 | 
					6a3a8ba4d0 | ||
| 
						 | 
					9e535c341e | ||
| 
						 | 
					17fa4ba804 | ||
| 
						 | 
					dd9a3c91fc | ||
| 
						 | 
					7015df37e3 | ||
| 
						 | 
					7ebdd4739a | ||
| 
						 | 
					c4bcb31a00 | ||
| 
						 | 
					0a18ee24ac | ||
| 
						 | 
					37d10b108e | ||
| 
						 | 
					b5cadeca2e | ||
| 
						 | 
					c8e93054bc | ||
| 
						 | 
					def32107af | ||
| 
						 | 
					1b84ae9f3f | ||
| 
						 | 
					73ce1f61a8 | ||
| 
						 | 
					8661cd1ee7 | ||
| 
						 | 
					53f55defda | ||
| 
						 | 
					d60dc60bc9 | ||
| 
						 | 
					bf1d7e6252 | ||
| 
						 | 
					c96b2767b9 | ||
| 
						 | 
					6c1f03eefd | ||
| 
						 | 
					cced83024b | ||
| 
						 | 
					1cd6ba3c1d | ||
| 
						 | 
					2365761ba5 | ||
| 
						 | 
					d3b4b70bfc | ||
| 
						 | 
					f95d5701a2 | ||
| 
						 | 
					a8c56afd0f | ||
| 
						 | 
					4de30aa47e | ||
| 
						 | 
					f05f7c920e | ||
| 
						 | 
					5f86509abc | ||
| 
						 | 
					1561391293 | ||
| 
						 | 
					60553a8a5e | ||
| 
						 | 
					e656074de4 | ||
| 
						 | 
					b81ff340b1 | ||
| 
						 | 
					77456ae0bc | ||
| 
						 | 
					9fd0e90850 | ||
| 
						 | 
					ab01cf1881 | ||
| 
						 | 
					0d10ca02db | ||
| 
						 | 
					0627df8116 | ||
| 
						 | 
					c88ea7150c | ||
| 
						 | 
					555954c71e | ||
| 
						 | 
					f231f02329 | ||
| 
						 | 
					b8af5dfde0 | ||
| 
						 | 
					ef3710fdc2 | ||
| 
						 | 
					bde114ad13 | ||
| 
						 | 
					9d138aa282 | ||
| 
						 | 
					42c4ea38cc | ||
| 
						 | 
					c0ba71c368 | ||
| 
						 | 
					7b2cb30a9d | ||
| 
						 | 
					0f55e65701 | ||
| 
						 | 
					aeb24b96eb | ||
| 
						 | 
					ad4700b3b1 | ||
| 
						 | 
					b2e5ade5ac | ||
| 
						 | 
					90a7b9b551 | ||
| 
						 | 
					31b6ab9b23 | ||
| 
						 | 
					4149c7782f | ||
| 
						 | 
					227f52d758 | ||
| 
						 | 
					baa2a871f1 | ||
| 
						 | 
					5bb619fe7b | ||
| 
						 | 
					fff3c552e2 | ||
| 
						 | 
					e19cc8bebf | ||
| 
						 | 
					f4f8debb92 | ||
| 
						 | 
					ea6b8b599f | ||
| 
						 | 
					c30f02ae4c | ||
| 
						 | 
					09450ba544 | ||
| 
						 | 
					df53968306 | ||
| 
						 | 
					19f753c15c | ||
| 
						 | 
					df530bb66d | ||
| 
						 | 
					fa49427df0 | ||
| 
						 | 
					6678d97cc4 | ||
| 
						 | 
					e3642a8d10 | ||
| 
						 | 
					f9022fdf16 | ||
| 
						 | 
					1c764139bf | ||
| 
						 | 
					048ed26f41 | ||
| 
						 | 
					c52e30e8e0 | ||
| 
						 | 
					0cb04ded36 | ||
| 
						 | 
					6cfad65ac7 | ||
| 
						 | 
					ed20805b10 | ||
| 
						 | 
					576303cd72 | ||
| 
						 | 
					0062e084f8 | ||
| 
						 | 
					b90d76dcfe | ||
| 
						 | 
					c95619b2bf | ||
| 
						 | 
					82150bd5b8 | ||
| 
						 | 
					9e03335ff8 | ||
| 
						 | 
					b3c5c3f0ea | ||
| 
						 | 
					65858dab3e | ||
| 
						 | 
					c968633d15 | ||
| 
						 | 
					39a8942daf | ||
| 
						 | 
					7705a7928e | ||
| 
						 | 
					d2c14b844e | ||
| 
						 | 
					3e45e6c165 | ||
| 
						 | 
					fe8334931f | ||
| 
						 | 
					f565c5f730 | ||
| 
						 | 
					c03e2febb0 | ||
| 
						 | 
					c2f4fb7ba7 | ||
| 
						 | 
					5f869e5d87 | ||
| 
						 | 
					65f1afc4e0 | ||
| 
						 | 
					aec2762bf1 | ||
| 
						 | 
					a41144a00f | ||
| 
						 | 
					aa28e8a7a6 | ||
| 
						 | 
					a1a51ce518 | ||
| 
						 | 
					90999e0ef9 | ||
| 
						 | 
					2ceeb17056 | ||
| 
						 | 
					18afdd6040 | ||
| 
						 | 
					b9972ec6bd | ||
| 
						 | 
					ebb53e87f3 | ||
| 
						 | 
					9f9d7325fd | ||
| 
						 | 
					742a005523 | ||
| 
						 | 
					3a28c06534 | ||
| 
						 | 
					46d5711071 | ||
| 
						 | 
					48113f3afd | ||
| 
						 | 
					be29972ddf | ||
| 
						 | 
					49b3a83f76 | ||
| 
						 | 
					342794c728 | ||
| 
						 | 
					3739638c81 | ||
| 
						 | 
					401351d9c8 | ||
| 
						 | 
					6073a03967 | ||
| 
						 | 
					3e3d294188 | ||
| 
						 | 
					f6f96ae5bf | ||
| 
						 | 
					62ccb53c24 | ||
| 
						 | 
					e410e22980 | ||
| 
						 | 
					fc5ceea335 | ||
| 
						 | 
					38af8d4737 | ||
| 
						 | 
					33b0cab596 | ||
| 
						 | 
					2a47e4a1e1 | ||
| 
						 | 
					46f53868c5 | ||
| 
						 | 
					eac7f11aa7 | ||
| 
						 | 
					e219188f46 | ||
| 
						 | 
					3df8c701a7 | ||
| 
						 | 
					1186813c75 | ||
| 
						 | 
					645b6fdc8a | ||
| 
						 | 
					ae0596a729 | ||
| 
						 | 
					bb5fd3c1f2 | ||
| 
						 | 
					532fa9c5f9 | ||
| 
						 | 
					80a4aa6fa6 | ||
| 
						 | 
					4f218f544f | ||
| 
						 | 
					f261f8d7d1 | ||
| 
						 | 
					e044d11782 | ||
| 
						 | 
					d2da459dd8 | ||
| 
						 | 
					b1e6a33d6b | ||
| 
						 | 
					0d276d0d61 | ||
| 
						 | 
					998936651a | ||
| 
						 | 
					1bec25e8e6 | ||
| 
						 | 
					f220e4183f | ||
| 
						 | 
					e965b57dc2 | ||
| 
						 | 
					5e6e1e237a | ||
| 
						 | 
					41fe364b49 | ||
| 
						 | 
					2953ba17c3 | ||
| 
						 | 
					f3b3e06329 | ||
| 
						 | 
					98249942d5 | ||
| 
						 | 
					0fc8445425 | ||
| 
						 | 
					943a1940e2 | ||
| 
						 | 
					15d166e30e | ||
| 
						 | 
					83619fda98 | ||
| 
						 | 
					12913a16fd | ||
| 
						 | 
					e23ad7833d | ||
| 
						 | 
					38aa760b57 | 
							
								
								
									
										13
									
								
								.babelrc
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								.babelrc
									
									
									
									
									
								
							@@ -1,13 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
	"plugins": [
 | 
			
		||||
		[
 | 
			
		||||
			"@babel/plugin-transform-runtime",
 | 
			
		||||
			{
 | 
			
		||||
				"corejs": {
 | 
			
		||||
					"version": 3,
 | 
			
		||||
					"proposals": true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		]
 | 
			
		||||
	]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
contact_links:
 | 
			
		||||
  - name: 👪 Misskey Forum
 | 
			
		||||
    url: https://forum.misskey.io/
 | 
			
		||||
    about: Ask questions and share knowledge
 | 
			
		||||
  - name: 💬 Misskey official Discord
 | 
			
		||||
    url: https://discord.gg/Wp8gVStHW3
 | 
			
		||||
    about: Chat freely about Misskey
 | 
			
		||||
							
								
								
									
										51
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										51
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							@@ -1,13 +1,40 @@
 | 
			
		||||
## Summary
 | 
			
		||||
<!-- ℹ お読みください
 | 
			
		||||
PRありがとうございます! PRを作成する前に、以下をご確認ください:
 | 
			
		||||
- 可能であればタイトルに、以下で示すようなPRの種類が分かるキーワードをプリフィクスしてください。
 | 
			
		||||
  - fix / refactor / feat / enhance / perf / chore
 | 
			
		||||
  - また、PRの粒度が適切であることを確認してください。ひとつのPRに複数の種類の変更や関心を含めることは避けてください。
 | 
			
		||||
- このPRによって解決されるIssueがある場合は、そのIssueへの参照を本文内に含めてください。
 | 
			
		||||
- CHANGELOG.mdに変更点を追記してください。リファクタリングなど、利用者に影響を与えない変更についてはこの限りではありません。
 | 
			
		||||
- この変更により新たに作成、もしくは更新すべきドキュメントがないか確認してください。
 | 
			
		||||
- 機能追加やバグ修正をした場合は、可能であればテストケースを追加してください。
 | 
			
		||||
- テスト、Lintが通っていることを予め確認してください。
 | 
			
		||||
  - `npm run test`、`npm run lint`でぞれぞれ実施可能です
 | 
			
		||||
- UIに変更がある場合はスクリーンショットを本文内に添付してください。
 | 
			
		||||
ご協力ありがとうございます🤗
 | 
			
		||||
-->
 | 
			
		||||
<!-- ℹ README
 | 
			
		||||
Thank you for your PR! Before creating a PR, please check the following:
 | 
			
		||||
- If possible, prefix the title with a keyword that identifies the type of this PR, as shown below.
 | 
			
		||||
  - fix / refactor / feat / enhance / perf / chore
 | 
			
		||||
  - Also, make sure that the granularity of this PR is appropriate. Please do not include more than one type of change or interest in a single PR.
 | 
			
		||||
- If there is an Issue which will be resolved by this PR, please include a reference to the Issue in the text.
 | 
			
		||||
- Please add the summary of the changes to CHANGELOG.md. However, this is not necessary for changes that do not affect the users, such as refactoring.
 | 
			
		||||
- Check if there are any documents that need to be created or updated due to this change.
 | 
			
		||||
- If you have added a feature or fixed a bug, please add a test case if possible.
 | 
			
		||||
- Please make sure that tests and Lint are passed in advance.
 | 
			
		||||
  - You can run it with `npm run test` and `npm run lint`.
 | 
			
		||||
- If this PR includes UI changes, please attach a screenshot in the text.
 | 
			
		||||
Thanks for your cooperation 🤗
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<!--
 | 
			
		||||
  -
 | 
			
		||||
  - * Please describe your changes here *
 | 
			
		||||
  -
 | 
			
		||||
  - If you are going to resolve some issue, please add this context.
 | 
			
		||||
  - Resolve #ISSUE_NUMBER
 | 
			
		||||
  -
 | 
			
		||||
  - If you are going to fix some bug issue, please add this context.
 | 
			
		||||
  - Fix #ISSUE_NUMBER
 | 
			
		||||
  -
 | 
			
		||||
  -->
 | 
			
		||||
# What
 | 
			
		||||
<!-- このPRで何をしたのか? どう変わるのか? -->
 | 
			
		||||
<!-- What did you do with this PR? How will it change things? -->
 | 
			
		||||
 | 
			
		||||
# Why
 | 
			
		||||
<!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? -->
 | 
			
		||||
<!-- Why do you do it? What are your intentions? What is the problem? -->
 | 
			
		||||
 | 
			
		||||
# Additional info (optional)
 | 
			
		||||
<!-- テスト観点など -->
 | 
			
		||||
<!-- Test perspective, etc -->
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
# To get started with Dependabot version updates, you'll need to specify which
 | 
			
		||||
# package ecosystems to update and where the package manifests are located.
 | 
			
		||||
# Please see the documentation for all configuration options:
 | 
			
		||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
 | 
			
		||||
 | 
			
		||||
version: 2
 | 
			
		||||
updates:
 | 
			
		||||
  - package-ecosystem: "npm" # See documentation for possible values
 | 
			
		||||
    directory: "/" # Location of package manifests
 | 
			
		||||
    schedule:
 | 
			
		||||
      interval: "daily"
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/nodejs.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/nodejs.yml
									
									
									
									
										vendored
									
									
								
							@@ -35,6 +35,8 @@ jobs:
 | 
			
		||||
        node-version: ${{ matrix.node-version }}
 | 
			
		||||
    - name: Install dependencies
 | 
			
		||||
      run: yarn install
 | 
			
		||||
    - name: Check yarn.lock
 | 
			
		||||
      run: git diff --exit-code yarn.lock
 | 
			
		||||
    - name: Copy Configure
 | 
			
		||||
      run: cp .circleci/misskey/*.yml .config
 | 
			
		||||
    - name: Build
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
v16.2.0
 | 
			
		||||
v16.6.2
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										89
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										89
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1 +1,88 @@
 | 
			
		||||
see [releases](https://github.com/misskey-dev/misskey/releases)
 | 
			
		||||
<!--
 | 
			
		||||
## 12.x.x (unreleased)
 | 
			
		||||
 | 
			
		||||
### Improvements
 | 
			
		||||
 | 
			
		||||
### Bugfixes
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
## 12.89.2 (2021/08/24)
 | 
			
		||||
 | 
			
		||||
### Bugfixes
 | 
			
		||||
- カスタムCSSを有効にしているとエラーになる問題を修正
 | 
			
		||||
 | 
			
		||||
## 12.89.1 (2021/08/24)
 | 
			
		||||
 | 
			
		||||
### Improvements
 | 
			
		||||
- クライアントのデザインの調整
 | 
			
		||||
 | 
			
		||||
### Bugfixes
 | 
			
		||||
- 翻訳でDeepLのProアカウントに対応していない問題を修正
 | 
			
		||||
- インスタンス設定でDeepLのAuth Keyが空で表示される問題を修正
 | 
			
		||||
- セキュリティの向上
 | 
			
		||||
 | 
			
		||||
## 12.89.0 (2021/08/21)
 | 
			
		||||
 | 
			
		||||
### Improvements
 | 
			
		||||
- アカウント削除の安定性を向上
 | 
			
		||||
- 絵文字オートコンプリートの挙動を改修
 | 
			
		||||
- localStorageのaccountsはindexedDBで保持するように
 | 
			
		||||
- ActivityPub: ジョブキューの試行タイミングを調整 (#7635)
 | 
			
		||||
- API: sw/unregisterを追加
 | 
			
		||||
- ワードミュートのドキュメントを追加
 | 
			
		||||
- クライアントのデザインの調整
 | 
			
		||||
- 依存関係の更新
 | 
			
		||||
 | 
			
		||||
### Bugfixes
 | 
			
		||||
- チャンネルを作成しているとアカウントを削除できないのを修正
 | 
			
		||||
- ノートの「削除して編集」をするとアンケートの選択肢が[object Object]になる問題を修正
 | 
			
		||||
 | 
			
		||||
## 12.88.0 (2021/08/17)
 | 
			
		||||
 | 
			
		||||
### Features
 | 
			
		||||
- ノートの翻訳機能を追加
 | 
			
		||||
  - 有効にするには、サーバー管理者がDeepLの無料アカウントを登録し、取得した認証キーを「インスタンス設定 > その他 > DeepL Auth Key」に設定する必要があります。
 | 
			
		||||
- Misskey更新時にダイアログを表示するように
 | 
			
		||||
- ジョブキューウィジェットに警報音を鳴らす設定を追加
 | 
			
		||||
 | 
			
		||||
### Improvements
 | 
			
		||||
- ブロックの挙動を改修
 | 
			
		||||
	- ブロックされたユーザーがブロックしたユーザーに対してアクション出来ないようになりました。詳細はドキュメントをご確認ください。
 | 
			
		||||
- UIデザインの調整
 | 
			
		||||
- データベースのインデックスを最適化
 | 
			
		||||
- Proxy使用時にKeep-Aliveをサポート
 | 
			
		||||
- DNSキャッシュでネガティブキャッシュをサポート
 | 
			
		||||
- 依存関係の更新
 | 
			
		||||
 | 
			
		||||
### Bugfixes
 | 
			
		||||
- タッチ操作でウィンドウを閉じることができない問題を修正
 | 
			
		||||
- Renoteされた時刻が投稿された時刻のように表示される問題を修正
 | 
			
		||||
- コントロールパネルでファイルを削除した際の表示を修正
 | 
			
		||||
- ActivityPub: 長いユーザーの名前や自己紹介の対応
 | 
			
		||||
 | 
			
		||||
## 12.87.0 (2021/08/12)
 | 
			
		||||
 | 
			
		||||
### Improvements
 | 
			
		||||
- 絵文字オートコンプリートで一文字目は最近使った絵文字をサジェストするように
 | 
			
		||||
- 絵文字オートコンプリートのパフォーマンスを改善
 | 
			
		||||
- about-misskeyページにドキュメントへのリンクを追加
 | 
			
		||||
- Docker: Node.jsを16.6.2に
 | 
			
		||||
- 依存関係の更新
 | 
			
		||||
- 翻訳の更新
 | 
			
		||||
 | 
			
		||||
### Bugfixes
 | 
			
		||||
- Misskey更新時、テーマキャッシュの影響でスタイルがおかしくなる問題を修正
 | 
			
		||||
 | 
			
		||||
## 12.86.0 (2021/08/11)
 | 
			
		||||
 | 
			
		||||
### Improvements
 | 
			
		||||
- ドキュメントの更新
 | 
			
		||||
	- ドキュメントにchangelogを追加
 | 
			
		||||
- ぼかし効果のオプションを追加
 | 
			
		||||
- Vueを3.2.1に更新
 | 
			
		||||
- UIの調整
 | 
			
		||||
 | 
			
		||||
### Bugfixes
 | 
			
		||||
- ハッシュタグ入力が空のときに#が付くのを修正
 | 
			
		||||
- フォローリクエストのEメール通知を修正
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,11 @@ If your language is not listed in Crowdin, please open an issue.
 | 
			
		||||
## Test
 | 
			
		||||
* Test codes are located in [`/test`](/test).
 | 
			
		||||
 | 
			
		||||
### Run specify test
 | 
			
		||||
```
 | 
			
		||||
npx cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT="./test/tsconfig.json" npx mocha test/foo.ts --require ts-node/register
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Continuous integration
 | 
			
		||||
Misskey uses GitHub Actions for executing automated tests.
 | 
			
		||||
Configuration files are located in [`/.github/workflows`](/.github/workflows).
 | 
			
		||||
@@ -242,6 +247,12 @@ npx ts-node ./node_modules/typeorm/cli.js migration:generate -n 変更の名前
 | 
			
		||||
 | 
			
		||||
作成されたスクリプトは不必要な変更を含むため除去してください。
 | 
			
		||||
 | 
			
		||||
### コネクションには`markRaw`せよ
 | 
			
		||||
**Vueのコンポーネントのdataオプションとして**misskey.jsのコネクションを設定するとき、必ず`markRaw`でラップしてください。インスタンスが不必要にリアクティブ化されることで、misskey.js内の処理で不具合が発生するとともに、パフォーマンス上の問題にも繋がる。なお、Composition APIを使う場合はこの限りではない(リアクティブ化はマニュアルなため)。
 | 
			
		||||
 | 
			
		||||
### JSONのimportに気を付けよう
 | 
			
		||||
TypeScriptでjsonをimportすると、tscでコンパイルするときにそのjsonファイルも一緒にdistディレクトリに吐き出されてしまう。この挙動により、意図せずファイルの書き換えが発生することがあるので、jsonをimportするときは書き換えられても良いものかどうか確認すること。書き換えされて欲しくない場合は、importで読み込むのではなく、`fs.readFileSync`などの関数を使って読み込むようにすればよい。
 | 
			
		||||
 | 
			
		||||
## その他
 | 
			
		||||
### HTMLのクラス名で follow という単語は使わない
 | 
			
		||||
広告ブロッカーで誤ってブロックされる
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
FROM node:16.2.0-alpine3.13 AS base
 | 
			
		||||
FROM node:16.6.2-alpine3.13 AS base
 | 
			
		||||
 | 
			
		||||
ENV NODE_ENV=production
 | 
			
		||||
 | 
			
		||||
@@ -18,9 +18,7 @@ RUN apk add --no-cache \
 | 
			
		||||
    nasm \
 | 
			
		||||
    pkgconfig \
 | 
			
		||||
    python3 \
 | 
			
		||||
    zlib-dev \
 | 
			
		||||
    vips-dev \
 | 
			
		||||
    vips
 | 
			
		||||
    zlib-dev
 | 
			
		||||
 | 
			
		||||
COPY package.json yarn.lock .yarnrc ./
 | 
			
		||||
RUN yarn install
 | 
			
		||||
@@ -31,8 +29,7 @@ FROM base AS runner
 | 
			
		||||
 | 
			
		||||
RUN apk add --no-cache \
 | 
			
		||||
    ffmpeg \
 | 
			
		||||
    tini \
 | 
			
		||||
    vips
 | 
			
		||||
    tini
 | 
			
		||||
 | 
			
		||||
ENTRYPOINT ["/sbin/tini", "--"]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								assets/client/sounds/syuilo/queue-jammed.mp3
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/client/sounds/syuilo/queue-jammed.mp3
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -2,6 +2,6 @@ files:
 | 
			
		||||
  - source: /locales/ja-JP.yml
 | 
			
		||||
    translation: /locales/%locale%.yml
 | 
			
		||||
    update_option: update_as_unapproved
 | 
			
		||||
  - source: /src/docs/ja-JP/*.md
 | 
			
		||||
    translation: /src/docs/%locale%/%original_file_name%
 | 
			
		||||
  - source: /src/docs/ja-JP/**/*.md
 | 
			
		||||
    translation: /src/docs/%locale%/**/%original_file_name%
 | 
			
		||||
    update_option: update_as_unapproved
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								cypress.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								cypress.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
{
 | 
			
		||||
	"baseUrl": "http://localhost"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								cypress/fixtures/example.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								cypress/fixtures/example.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "Using fixtures to represent data",
 | 
			
		||||
  "email": "hello@cypress.io",
 | 
			
		||||
  "body": "Fixtures are a great way to mock data for responses to routes"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										69
									
								
								cypress/integration/basic.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								cypress/integration/basic.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
describe('Basic', () => {
 | 
			
		||||
	before(() => {
 | 
			
		||||
		cy.request('POST', '/api/reset-db');
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	beforeEach(() => {
 | 
			
		||||
		cy.reload(true);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
  it('successfully loads', () => {
 | 
			
		||||
    cy.visit('/');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
	it('setup instance', () => {
 | 
			
		||||
    cy.visit('/');
 | 
			
		||||
 | 
			
		||||
		cy.get('[data-cy-admin-username] input').type('admin');
 | 
			
		||||
 | 
			
		||||
		cy.get('[data-cy-admin-password] input').type('admin1234');
 | 
			
		||||
 | 
			
		||||
		cy.get('[data-cy-admin-ok]').click();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
	it('signup', () => {
 | 
			
		||||
    cy.visit('/');
 | 
			
		||||
 | 
			
		||||
		cy.get('[data-cy-signup]').click();
 | 
			
		||||
 | 
			
		||||
		cy.get('[data-cy-signup-username] input').type('alice');
 | 
			
		||||
 | 
			
		||||
		cy.get('[data-cy-signup-password] input').type('alice1234');
 | 
			
		||||
	
 | 
			
		||||
		cy.get('[data-cy-signup-password-retype] input').type('alice1234');
 | 
			
		||||
 | 
			
		||||
		cy.get('[data-cy-signup-submit]').click();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
	it('signin', () => {
 | 
			
		||||
    cy.visit('/');
 | 
			
		||||
 | 
			
		||||
		cy.get('[data-cy-signin]').click();
 | 
			
		||||
 | 
			
		||||
		cy.get('[data-cy-signin-username] input').type('alice');
 | 
			
		||||
 | 
			
		||||
		// Enterキーでサインインできるかの確認も兼ねる
 | 
			
		||||
		cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
	it('note', () => {
 | 
			
		||||
    cy.visit('/');
 | 
			
		||||
 | 
			
		||||
		//#region TODO: この辺はUI操作ではなくAPI操作でログインする
 | 
			
		||||
		cy.get('[data-cy-signin]').click();
 | 
			
		||||
 | 
			
		||||
		cy.get('[data-cy-signin-username] input').type('alice');
 | 
			
		||||
 | 
			
		||||
		// Enterキーでサインインできるかの確認も兼ねる
 | 
			
		||||
		cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
 | 
			
		||||
		//#endregion
 | 
			
		||||
 | 
			
		||||
		cy.get('[data-cy-open-post-form]').click();
 | 
			
		||||
 | 
			
		||||
		cy.get('[data-cy-post-form-text]').type('Hello, Misskey!');
 | 
			
		||||
 | 
			
		||||
		cy.get('[data-cy-open-post-form-submit]').click();
 | 
			
		||||
 | 
			
		||||
		// TODO: 投稿した文字列が画面内にあるか(=タイムラインに流れてきたか)のテスト
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										22
									
								
								cypress/plugins/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								cypress/plugins/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
/// <reference types="cypress" />
 | 
			
		||||
// ***********************************************************
 | 
			
		||||
// This example plugins/index.js can be used to load plugins
 | 
			
		||||
//
 | 
			
		||||
// You can change the location of this file or turn off loading
 | 
			
		||||
// the plugins file with the 'pluginsFile' configuration option.
 | 
			
		||||
//
 | 
			
		||||
// You can read more here:
 | 
			
		||||
// https://on.cypress.io/plugins-guide
 | 
			
		||||
// ***********************************************************
 | 
			
		||||
 | 
			
		||||
// This function is called when a project is opened or re-opened (e.g. due to
 | 
			
		||||
// the project's config changing)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @type {Cypress.PluginConfig}
 | 
			
		||||
 */
 | 
			
		||||
// eslint-disable-next-line no-unused-vars
 | 
			
		||||
module.exports = (on, config) => {
 | 
			
		||||
  // `on` is used to hook into various events Cypress emits
 | 
			
		||||
  // `config` is the resolved Cypress config
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								cypress/support/commands.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								cypress/support/commands.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
// ***********************************************
 | 
			
		||||
// This example commands.js shows you how to
 | 
			
		||||
// create various custom commands and overwrite
 | 
			
		||||
// existing commands.
 | 
			
		||||
//
 | 
			
		||||
// For more comprehensive examples of custom
 | 
			
		||||
// commands please read more here:
 | 
			
		||||
// https://on.cypress.io/custom-commands
 | 
			
		||||
// ***********************************************
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
// -- This is a parent command --
 | 
			
		||||
// Cypress.Commands.add('login', (email, password) => { ... })
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
// -- This is a child command --
 | 
			
		||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
// -- This is a dual command --
 | 
			
		||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
// -- This will overwrite an existing command --
 | 
			
		||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
 | 
			
		||||
							
								
								
									
										20
									
								
								cypress/support/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								cypress/support/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
// ***********************************************************
 | 
			
		||||
// This example support/index.js is processed and
 | 
			
		||||
// loaded automatically before your test files.
 | 
			
		||||
//
 | 
			
		||||
// This is a great place to put global configuration and
 | 
			
		||||
// behavior that modifies Cypress.
 | 
			
		||||
//
 | 
			
		||||
// You can change the location of this file or turn off
 | 
			
		||||
// automatically serving support files with the
 | 
			
		||||
// 'supportFile' configuration option.
 | 
			
		||||
//
 | 
			
		||||
// You can read more here:
 | 
			
		||||
// https://on.cypress.io/configuration
 | 
			
		||||
// ***********************************************************
 | 
			
		||||
 | 
			
		||||
// Import commands.js using ES2015 syntax:
 | 
			
		||||
import './commands'
 | 
			
		||||
 | 
			
		||||
// Alternatively you can use CommonJS syntax:
 | 
			
		||||
// require('./commands')
 | 
			
		||||
@@ -24,7 +24,7 @@ Please install and setup these softwares:
 | 
			
		||||
 | 
			
		||||
#### Dependencies :package:
 | 
			
		||||
* **[Node.js](https://nodejs.org/en/)** (12.x, 14.x)
 | 
			
		||||
* **[PostgreSQL](https://www.postgresql.org/)** (>= 10)
 | 
			
		||||
* **[PostgreSQL](https://www.postgresql.org/)** (12.x / 13.x is preferred)
 | 
			
		||||
* **[Redis](https://redis.io/)**
 | 
			
		||||
 | 
			
		||||
##### Optional
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								index.js
									
									
									
									
									
								
							@@ -1,3 +1,13 @@
 | 
			
		||||
/*
 | 
			
		||||
import * as fs from 'fs';
 | 
			
		||||
 | 
			
		||||
if (fs.existsSync('./built')) {
 | 
			
		||||
	import('./built/index.js').then(built => built());
 | 
			
		||||
} else {
 | 
			
		||||
	console.log('Built code is not found. Probably an error occurred during a build or you just forgot to build.');
 | 
			
		||||
}
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
 | 
			
		||||
if (fs.existsSync('./built')) {
 | 
			
		||||
 
 | 
			
		||||
@@ -427,9 +427,13 @@ inUse: "مستخدم"
 | 
			
		||||
info: "عن"
 | 
			
		||||
user: "المستخدمون"
 | 
			
		||||
administration: "إدارة "
 | 
			
		||||
postToGallery: "انشر في المعرض"
 | 
			
		||||
gallery: "المعرض"
 | 
			
		||||
expiration: "ينتهي استطلاع الرأي في"
 | 
			
		||||
middle: "متوسط"
 | 
			
		||||
global: "الشامل"
 | 
			
		||||
_docs:
 | 
			
		||||
  admin: "إدارة "
 | 
			
		||||
_email:
 | 
			
		||||
  _follow:
 | 
			
		||||
    title: "يتابعك"
 | 
			
		||||
 
 | 
			
		||||
@@ -111,6 +111,7 @@ editWidgets: "Upravit widget"
 | 
			
		||||
editWidgetsExit: "Hotovo"
 | 
			
		||||
customEmojis: "Vlastní emoji"
 | 
			
		||||
emoji: "Emoji"
 | 
			
		||||
emojis: "Emoji"
 | 
			
		||||
emojiName: "Jméno emoji"
 | 
			
		||||
emojiUrl: "URL obrázku"
 | 
			
		||||
addEmoji: "Přidat emoji"
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,19 +1,19 @@
 | 
			
		||||
---
 | 
			
		||||
_lang_: "Esperanto"
 | 
			
		||||
headlineMisskey: "Reto ligiĝas per notoj"
 | 
			
		||||
introMisskey: "Bonvenon! Miskejo estas malferma kodaの分散型マイクロブログサービスです。\nBonvolu Krei「noto」、いま起こっていることを共有したり、あなたについて皆に発信しよう📡\n「 reaktigoj 」機能で、皆のnotojに素早く反応を追加することもできます👍\n新しい世界を探検しよう🚀"
 | 
			
		||||
headlineMisskey: "Reto ligata per notoj"
 | 
			
		||||
introMisskey: "Bonvenon! Misskey estas malfermitkoda malcentraliza mikrobloga servo.\nKreu \"noto\"n por diskonigu tion ke nun okazas, aŭ por dissendu pri vi. 📡\nPer la funkcio \"reago\", vi ankaŭ povas rapide esprimi vian senton pri ĉies noto. 👍\nOni esploru novan mondon. 🚀"
 | 
			
		||||
monthAndDay: "{day}-a/{month}"
 | 
			
		||||
search: "Serĉi"
 | 
			
		||||
notifications: "Sciigoj"
 | 
			
		||||
username: "Uzantonomo"
 | 
			
		||||
username: "Uzantnomo"
 | 
			
		||||
password: "Pasvorto"
 | 
			
		||||
forgotPassword: "Ĉu vi forgesis pasvorton?"
 | 
			
		||||
fetchingAsApObject: "Informpetado de fediverso..."
 | 
			
		||||
ok: "Okej"
 | 
			
		||||
gotIt: "Mi konprenas!"
 | 
			
		||||
fetchingAsApObject: "Informpetado de Fediverso..."
 | 
			
		||||
ok: "Akcepteble"
 | 
			
		||||
gotIt: "Mi komprenas"
 | 
			
		||||
cancel: "Nuligi"
 | 
			
		||||
enterUsername: "Entajpu uzantonomon"
 | 
			
		||||
renotedBy: "Renotigojn faras {user}"
 | 
			
		||||
enterUsername: "Entajpu uzantnomon"
 | 
			
		||||
renotedBy: "Renoto farita de {user}"
 | 
			
		||||
noNotes: "Neniu noto!"
 | 
			
		||||
noNotifications: "Vi ne havas sciigojn."
 | 
			
		||||
instance: "Ekzemplo"
 | 
			
		||||
@@ -22,7 +22,8 @@ basicSettings: "Ĝeneralaj agordoj"
 | 
			
		||||
otherSettings: "Aliaj agordoj"
 | 
			
		||||
openInWindow: "Malfermi en nova fenestro"
 | 
			
		||||
profile: "Profilo"
 | 
			
		||||
timeline: "Tempolinio"
 | 
			
		||||
timeline: "Templinio"
 | 
			
		||||
noAccountDescription: "Tiu uzanto ankoraŭ ne skribis biografieton"
 | 
			
		||||
login: "Ensaluti"
 | 
			
		||||
loggingIn: "Ensalutado..."
 | 
			
		||||
logout: "Elsaluti"
 | 
			
		||||
@@ -42,47 +43,52 @@ unpin: "Depingli"
 | 
			
		||||
copyContent: "Kopii enhavon"
 | 
			
		||||
copyLink: "Kopii ligilon"
 | 
			
		||||
delete: "Forviŝi"
 | 
			
		||||
deleteAndEdit: "Forviŝi kaj redakti"
 | 
			
		||||
deleteAndEditConfirm: "Ĉu vi certas, ke vi volas forviŝi la noton? La reaktigoj, renotigoj, kaj respondoj ankaŭ forigiĝos."
 | 
			
		||||
addToList: "Aldoni al listo"
 | 
			
		||||
deleteAndEdit: "Forigi kaj redakti"
 | 
			
		||||
deleteAndEditConfirm: "Ĉu vi certas, ke vi volas forigi kaj redakti la noton? Ankaŭ ĉiuj reagoj, renotoj, kaj respondoj al ĝi foriĝos."
 | 
			
		||||
addToList: "Aldoni al la listo"
 | 
			
		||||
sendMessage: "Sendi mesaĝon"
 | 
			
		||||
copyUsername: "Kopii uzantonomon"
 | 
			
		||||
copyUsername: "Kopii uzantnomon"
 | 
			
		||||
searchUser: "Serĉi uzanton"
 | 
			
		||||
reply: "Respondi"
 | 
			
		||||
loadMore: "Vidu plu"
 | 
			
		||||
showMore: "Vidi plu"
 | 
			
		||||
youGotNewFollower: "Vi estas eksekvita."
 | 
			
		||||
loadMore: "Vidu pli"
 | 
			
		||||
showMore: "Vidi pli"
 | 
			
		||||
youGotNewFollower: "sksekvis vin"
 | 
			
		||||
receiveFollowRequest: "Peto de sekvado estas ricevita"
 | 
			
		||||
followRequestAccepted: "La peto de sekvado akceptita"
 | 
			
		||||
mention: "Mencioj"
 | 
			
		||||
mentions: "Mencioj"
 | 
			
		||||
directNotes: "Senperaj notoj"
 | 
			
		||||
importAndExport: "Importaĵo / Eksportaĵo"
 | 
			
		||||
mentions: "Al vi"
 | 
			
		||||
directNotes: "Rektaj notoj"
 | 
			
		||||
importAndExport: "Importi/eksporti"
 | 
			
		||||
import: "Importi"
 | 
			
		||||
export: "Eksporti"
 | 
			
		||||
files: "Dosieroj"
 | 
			
		||||
download: "Elŝuti"
 | 
			
		||||
driveFileDeleteConfirm: "Ĉu vi certas ke vi volas forviŝi la dosieron \"{name}\"? La notoj kun la aldonaĵo ankaŭ forviŝiĝos."
 | 
			
		||||
unfollowConfirm: "Ĉu vi certas, ke vi volas ne plu sekvi {name}?"
 | 
			
		||||
driveFileDeleteConfirm: "Ĉu vi certas, ke vi volas forviŝi la dosieron \"{name}\"? Ankaŭ notoj kiu enhavas ĝin forviŝiĝos."
 | 
			
		||||
unfollowConfirm: "Ĉu vi certas, ke vi volas ne plu sekvi {name}'(o)n?"
 | 
			
		||||
lists: "Listoj"
 | 
			
		||||
noLists: "Neniu listo"
 | 
			
		||||
note: "Elsendi noto"
 | 
			
		||||
notes: "Notoj"
 | 
			
		||||
following: "Sekvi"
 | 
			
		||||
following: "Sekvatoj"
 | 
			
		||||
followers: "Sekvantoj"
 | 
			
		||||
followsYou: "Sekvas vin"
 | 
			
		||||
createList: "Kreii liston"
 | 
			
		||||
manageLists: "Administri liston"
 | 
			
		||||
error: "Eraro"
 | 
			
		||||
somethingHappened: "Problemo okazis."
 | 
			
		||||
retry: "Reprovi"
 | 
			
		||||
enterListName: "Entajpu nomon de la listo"
 | 
			
		||||
privacy: "Privateco"
 | 
			
		||||
follow: "Sekvi"
 | 
			
		||||
followRequest: "Peti eksekvi"
 | 
			
		||||
followRequests: "Eksekvopetoj"
 | 
			
		||||
unfollow: "Ne plu sekvi"
 | 
			
		||||
renote: "Renotici"
 | 
			
		||||
unrenote: "Forigi renotici"
 | 
			
		||||
cantRenote: "Tiu noto estas renototebla."
 | 
			
		||||
cantReRenote: "Renotigo ne estas renotigebla."
 | 
			
		||||
followRequest: "Peti de sekvado"
 | 
			
		||||
followRequests: "Petoj de sekvado"
 | 
			
		||||
unfollow: "Malsekvi"
 | 
			
		||||
enterEmoji: "Entajpu emoĵion"
 | 
			
		||||
renote: "Fari renoton"
 | 
			
		||||
unrenote: "Malfari renoton"
 | 
			
		||||
renoted: "Renoton fariĝis."
 | 
			
		||||
cantRenote: "Tiu noto ne estas renototebla."
 | 
			
		||||
cantReRenote: "Oni ne povas fari renoton kiu enhavas renoto."
 | 
			
		||||
quote: "Citi"
 | 
			
		||||
pinnedNote: "Pinglita noto"
 | 
			
		||||
pinned: "Alpingli sur la profilo"
 | 
			
		||||
@@ -91,7 +97,7 @@ clickToShow: "Klaku por malkaŝu"
 | 
			
		||||
sensitive: "Enhavo ne estas deca por laborejo (NSFW)"
 | 
			
		||||
add: "Aldoni"
 | 
			
		||||
reaction: "Reagoj"
 | 
			
		||||
enterFileName: "Entajpu dosiernomon"
 | 
			
		||||
enterFileName: "Entajpu nomon de dosiero"
 | 
			
		||||
mute: "Silentigi"
 | 
			
		||||
unmute: "Malsilentigi"
 | 
			
		||||
block: "Bloki"
 | 
			
		||||
@@ -103,201 +109,560 @@ unblockConfirm: "Ĉu vi certas ke vi volas malbloki la uzanton?"
 | 
			
		||||
suspendConfirm: "Ĉu vi certas ke vi volas frostigi la uzanton?"
 | 
			
		||||
unsuspendConfirm: "Ĉu vi certas ke vi volas fandi la uzanton?"
 | 
			
		||||
selectList: "Elekti liston"
 | 
			
		||||
emojiUrl: "Retadreso de la emoĵio"
 | 
			
		||||
selectAntenna: "Elekti antenon"
 | 
			
		||||
selectWidget: "Elekti enestraĵon"
 | 
			
		||||
editWidgets: "Redakti fenestraĵon"
 | 
			
		||||
editWidgetsExit: "Fini la redaktadon"
 | 
			
		||||
customEmojis: "Personecigitaj emoĵioj"
 | 
			
		||||
emoji: "Emoĵio"
 | 
			
		||||
emojis: "Emoĵio"
 | 
			
		||||
emojiName: "Nomo de emoĵio"
 | 
			
		||||
emojiUrl: "URL de la emoĵio"
 | 
			
		||||
addEmoji: "Aldoni emoĵion"
 | 
			
		||||
settingGuide: "Rekomendaj agordoj"
 | 
			
		||||
cacheRemoteFiles: "Havi staplon de transaj dosieroj"
 | 
			
		||||
flagAsBot: "Tiu uzanto estas roboto"
 | 
			
		||||
flagAsCat: "Tiu uzanto estas kato"
 | 
			
		||||
addAccount: "Aldoni konton"
 | 
			
		||||
showOnRemote: "Vidi sur la transa ekzemplo"
 | 
			
		||||
showOnRemote: "Vidi sur la fora ekzemplo"
 | 
			
		||||
general: "Ĝenerala"
 | 
			
		||||
wallpaper: "Ekranfonoj"
 | 
			
		||||
setWallpaper: "Apliki ekranfonon"
 | 
			
		||||
removeWallpaper: "Forviŝi ekranfonon. "
 | 
			
		||||
searchWith: "Serĉi: {q}"
 | 
			
		||||
youHaveNoLists: "Vi ne havas listojn."
 | 
			
		||||
followConfirm: "Ĉu vi certas, ke vi volas sekvi {name}'n?"
 | 
			
		||||
followConfirm: "Ĉu vi certas ke vi volas sekvi {name}'(o)n?"
 | 
			
		||||
selectUser: "Elekti uzanton"
 | 
			
		||||
annotation: "Komentarioj"
 | 
			
		||||
federation: "Fediverso"
 | 
			
		||||
federation: "Kunfederaĵo"
 | 
			
		||||
instances: "Ekzemplo"
 | 
			
		||||
perHour: "Po horo"
 | 
			
		||||
perDay: "Po tago"
 | 
			
		||||
blockThisInstance: "Bloki tiu ekzemplo"
 | 
			
		||||
version: "Versio"
 | 
			
		||||
withNFiles: "{n} dosiero(j)"
 | 
			
		||||
disk: "Diskilo"
 | 
			
		||||
blockedInstances: "Blokitaj ekzemploj"
 | 
			
		||||
muteAndBlock: "Silentitaj / Blokitaj"
 | 
			
		||||
mutedUsers: "Silentigitaj uzantoj"
 | 
			
		||||
blockedUsers: "Blokitaj uzantoj"
 | 
			
		||||
instanceInfo: "Informo pri la ekzemplo"
 | 
			
		||||
clearCachedFiles: "Malplenigi la staplon"
 | 
			
		||||
clearCachedFilesConfirm: "Ĉu vi certas, ke vi volas forviŝi ĉiujn transajn dosierojn en la staplo?"
 | 
			
		||||
blockedInstances: "Blokataj ekzemploj"
 | 
			
		||||
muteAndBlock: "Silentigatoj kaj blokatoj"
 | 
			
		||||
mutedUsers: "Silentigataj uzantoj"
 | 
			
		||||
blockedUsers: "Blokataj uzantoj"
 | 
			
		||||
noUsers: "Sen uzantoj"
 | 
			
		||||
editProfile: "Redakti profilon"
 | 
			
		||||
noteDeleteConfirm: "Ĉu vi certas ke vi volas forviŝi la noton?"
 | 
			
		||||
pinLimitExceeded: "Vi ne plu povas alpingli noton."
 | 
			
		||||
pinLimitExceeded: "Vi povas alpingli ne pli noton."
 | 
			
		||||
processing: "Traktado..."
 | 
			
		||||
noCustomEmojis: "Neniu emoĵio"
 | 
			
		||||
federating: "Konfederado"
 | 
			
		||||
blocked: "Blokita"
 | 
			
		||||
federating: "Kunfederado"
 | 
			
		||||
blocked: "Blokata"
 | 
			
		||||
all: "Ĉiuj"
 | 
			
		||||
subscribing: "Abonita"
 | 
			
		||||
publishing: "Dissendado"
 | 
			
		||||
notResponding: "Alvokato ne disponeblas"
 | 
			
		||||
instanceFollowing: "Sekvi ekzemplon"
 | 
			
		||||
instanceFollowers: "Sekvantoj de la ekzemplo"
 | 
			
		||||
instanceFollowing: "Sekvatoj el la ekzemplo"
 | 
			
		||||
instanceFollowers: "Sekvantoj el la ekzemplo"
 | 
			
		||||
instanceUsers: "Uzantoj de la ekzemplo"
 | 
			
		||||
changePassword: "Ŝanĝi pasvorton"
 | 
			
		||||
security: "Sekureco"
 | 
			
		||||
currentPassword: "Aktuala pasvorto"
 | 
			
		||||
newPassword: "Nova pasvorto"
 | 
			
		||||
newPasswordRetype: "Reentajpu la novan pasvorton"
 | 
			
		||||
attachFile: "Aldoni dosieron"
 | 
			
		||||
more: "Plu!"
 | 
			
		||||
usernameOrUserId: "Uzantonomo aŭ ID de uzanto"
 | 
			
		||||
more: "Plu !"
 | 
			
		||||
featured: "Maksimumi"
 | 
			
		||||
usernameOrUserId: "Uzantnomo aŭ identigilo de uzanto"
 | 
			
		||||
noSuchUser: "Neniuj uzantoj trovitaj."
 | 
			
		||||
remove: "Forviŝi"
 | 
			
		||||
lookup: "Informpeti"
 | 
			
		||||
announcements: "Novaĵoj"
 | 
			
		||||
imageUrl: "URL de bildo"
 | 
			
		||||
remove: "Forigi"
 | 
			
		||||
removed: "Forviŝis"
 | 
			
		||||
removeAreYouSure: "Ĉu vi certas ke vi volas forigi \"{x}\"?"
 | 
			
		||||
deleteAreYouSure: "Ĉu vi certas ke vi volas forigi \"{x}\"?"
 | 
			
		||||
messaging: "Babilejoj"
 | 
			
		||||
removeAreYouSure: "Ĉu vi certas ke vi volas forigi \"{x}\"'(o)n?"
 | 
			
		||||
deleteAreYouSure: "Ĉu vi certas ke vi volas forviŝi \"{x}\"'(o)n?"
 | 
			
		||||
saved: "Konservita"
 | 
			
		||||
messaging: "Retbabili"
 | 
			
		||||
upload: "Alŝuti"
 | 
			
		||||
fromDrive: "De la diskilo"
 | 
			
		||||
fromUrl: "De retadreso"
 | 
			
		||||
uploadFromUrl: "Aldoni de retadreso"
 | 
			
		||||
uploadFromUrlDescription: "Retadreso de la dosiero kiun vi volu alŝuti"
 | 
			
		||||
games: "Ludoj sur Miskejo"
 | 
			
		||||
fromDrive: "De la disko"
 | 
			
		||||
fromUrl: "De URL"
 | 
			
		||||
uploadFromUrl: "Alŝuti de URL"
 | 
			
		||||
uploadFromUrlDescription: "URL de la dosiero kiun vi volas alŝuti"
 | 
			
		||||
explore: "Esplori"
 | 
			
		||||
games: "Miskiaj Ludoj"
 | 
			
		||||
messageRead: "Legita"
 | 
			
		||||
startMessaging: "Komenci babiladon"
 | 
			
		||||
tos: "Kondiĉoj de Uzado"
 | 
			
		||||
nUsersRead: "Legita de {n} homoj"
 | 
			
		||||
tos: "Kondiĉoj de uzado"
 | 
			
		||||
start: "Komenciĝi"
 | 
			
		||||
home: "Ĉefpaĝo"
 | 
			
		||||
drive: "Diskilo"
 | 
			
		||||
home: "Hejmo"
 | 
			
		||||
remoteUserCaution: "Ĉi tiu Infomoj estas ne tute ekzakta pro distanca uzanto."
 | 
			
		||||
activity: "Aktiveco"
 | 
			
		||||
images: "Bildoj"
 | 
			
		||||
birthday: "Naskiĝdato"
 | 
			
		||||
registeredDate: "Registriĝdato"
 | 
			
		||||
location: "Loko"
 | 
			
		||||
theme: "Koloraro"
 | 
			
		||||
light: "Luma"
 | 
			
		||||
dark: "Malluma"
 | 
			
		||||
drive: "Disko"
 | 
			
		||||
fileName: "Dosiernomo"
 | 
			
		||||
selectFile: "Elekti dosieron"
 | 
			
		||||
selectFiles: "Elekti dosieron"
 | 
			
		||||
renameFile: "Renomigi dosieron"
 | 
			
		||||
selectFolder: "Elekti dosierujon"
 | 
			
		||||
selectFolders: "Elekti dosierujon"
 | 
			
		||||
renameFile: "Alinomi la dosieron"
 | 
			
		||||
folderName: "Nomo de la dosierujo"
 | 
			
		||||
createFolder: "Krei dosierujon"
 | 
			
		||||
renameFolder: "Alinomi la dosierujon"
 | 
			
		||||
deleteFolder: "Forviŝi dosierujon"
 | 
			
		||||
addFile: "Aldoni dosieron"
 | 
			
		||||
emptyDrive: "La diskilo enhavas neniun."
 | 
			
		||||
emptyDrive: "La disko malplenas"
 | 
			
		||||
emptyFolder: "La dosierujo malplenas"
 | 
			
		||||
unableToDelete: "Ne forigebla"
 | 
			
		||||
inputNewFileName: "Entajpu nova dosiernomon"
 | 
			
		||||
hasChildFilesOrFolders: "La dosierujo estas neforviŝebla pro tio, ke ĝi enhavas dosieron."
 | 
			
		||||
copyUrl: "Kopii retadreson"
 | 
			
		||||
inputNewFileName: "Entajpu nova nomon de la dosiero"
 | 
			
		||||
inputNewFolderName: "Entajpu nova nomon de la dosierujo"
 | 
			
		||||
hasChildFilesOrFolders: "La dosierujo ne estas forviŝebla, ĉar ĝi ne malplenas."
 | 
			
		||||
copyUrl: "Kopii URL"
 | 
			
		||||
rename: "Alinomi"
 | 
			
		||||
avatar: "Ikono"
 | 
			
		||||
banner: "Standardo"
 | 
			
		||||
nsfw: "Enhavo ne estas deca por laborejo (NSFW)"
 | 
			
		||||
reload: "Reŝargi"
 | 
			
		||||
watch: "Observi"
 | 
			
		||||
unwatch: "Malobservi"
 | 
			
		||||
accept: "Permesi"
 | 
			
		||||
normal: "Normala"
 | 
			
		||||
instanceName: "Nomo de la ekzemplo"
 | 
			
		||||
maintainerName: "Nomo de la administranto"
 | 
			
		||||
maintainerEmail: "Retpoŝto de la administranto"
 | 
			
		||||
tosUrl: "URL de kondiĉoj de uzado"
 | 
			
		||||
thisYear: "Ĉi-jare"
 | 
			
		||||
thisMonth: "Ĉi-monate"
 | 
			
		||||
today: "Hodiaŭ"
 | 
			
		||||
dayX: "{day}-a"
 | 
			
		||||
monthX: "{month}"
 | 
			
		||||
yearX: "La jaro {year}"
 | 
			
		||||
pages: "Paĝoj"
 | 
			
		||||
connectService: "Konekti"
 | 
			
		||||
disconnectService: "Farkonektiĝi"
 | 
			
		||||
driveCapacityPerLocalAccount: "Volumo po unu loka-uzanto"
 | 
			
		||||
driveCapacityPerRemoteAccount: "Volumo po unu transa uzanto"
 | 
			
		||||
enableLocalTimeline: "Ebligi lokan templinion"
 | 
			
		||||
enableGlobalTimeline: "Ebligi mallokan templinion"
 | 
			
		||||
registration: "Registri"
 | 
			
		||||
driveCapacityPerLocalAccount: "Volumo de disko po unu loka uzanto"
 | 
			
		||||
driveCapacityPerRemoteAccount: "Volumo de disko po unu transa uzanto"
 | 
			
		||||
iconUrl: "URL de la ikono (retpaĝsimbolo, ktp)"
 | 
			
		||||
bannerUrl: "URL de standardo"
 | 
			
		||||
backgroundImageUrl: "URL de fona bildo"
 | 
			
		||||
basicInfo: "Baza informo"
 | 
			
		||||
pinnedUsers: "Alpinglita uzanto"
 | 
			
		||||
pinnedPages: "Alpinglitaj paĝoj"
 | 
			
		||||
pinnedNotes: "Pinglita noto"
 | 
			
		||||
antennas: "Antenoj"
 | 
			
		||||
manageAntennas: "Administri antenojn"
 | 
			
		||||
name: "Nomo"
 | 
			
		||||
withFileAntenna: "Nur kun aldonaĵo"
 | 
			
		||||
withReplies: "Inkluzive respondoj"
 | 
			
		||||
notesAndReplies: "Kun respondoj"
 | 
			
		||||
withFiles: "Kun aldonaĵo"
 | 
			
		||||
silenceConfirm: "Ĉu vi certas ke vi volas silentigi la uzanton?"
 | 
			
		||||
unsilenceConfirm: "Ĉu vi certas ke vi volas malsilentigi la uzanton?"
 | 
			
		||||
silence: "Mutigi"
 | 
			
		||||
silenceConfirm: "Ĉu vi certas ke vi volas mutigi la uzanton?"
 | 
			
		||||
unsilence: "Malmutigi"
 | 
			
		||||
unsilenceConfirm: "Ĉu vi certas ke vi volas malmutigi la uzanton?"
 | 
			
		||||
recentlyUpdatedUsers: "Uzantoj kiu lastatempe faris noton"
 | 
			
		||||
recentlyRegisteredUsers: "Nove aniĝintaj uzantoj"
 | 
			
		||||
popularTags: "Popularaj kradvortoj"
 | 
			
		||||
userList: "Listoj"
 | 
			
		||||
aboutMisskey: "Pri Miskejo"
 | 
			
		||||
about: "Informoj"
 | 
			
		||||
aboutMisskey: "Pri Misskey"
 | 
			
		||||
administrator: "Administranto"
 | 
			
		||||
moderator: "Moderigisto"
 | 
			
		||||
securityKey: "Sekureca ŝlosilo"
 | 
			
		||||
securityKeyName: "Nomo de la ŝlosilo"
 | 
			
		||||
lastUsed: "Plej malnove uzita"
 | 
			
		||||
passwordLessLogin: "Ensaluti sen pasvorto"
 | 
			
		||||
resetPassword: "Restarigi pasvorton"
 | 
			
		||||
newPasswordIs: "La nova pasvorto estas {password}."
 | 
			
		||||
share: "Diskonigi"
 | 
			
		||||
notFound: "Ne trovita"
 | 
			
		||||
cacheClear: "Malplenigi staplon"
 | 
			
		||||
help: "Manlibro de uzado"
 | 
			
		||||
inputMessageHere: "Entajpu masaĝo tie ĉi"
 | 
			
		||||
close: "Fermi"
 | 
			
		||||
group: "Grupo"
 | 
			
		||||
groups: "Grupoj"
 | 
			
		||||
createGroup: "Krei grupon"
 | 
			
		||||
groupName: "Grupa nomo"
 | 
			
		||||
members: "Membroj"
 | 
			
		||||
messagingWithUser: "Mesaĝado kun uzanto"
 | 
			
		||||
messagingWithGroup: "Mesaĝado kun grupo"
 | 
			
		||||
title: "Titolo"
 | 
			
		||||
text: "Teksto"
 | 
			
		||||
enable: "Ebligi"
 | 
			
		||||
next: "Sekve"
 | 
			
		||||
noteOf: "Noto de {user}"
 | 
			
		||||
noMessagesYet: "Neniu mesaĝo"
 | 
			
		||||
newMessageExists: "Vi ricevis novan mesaĝon."
 | 
			
		||||
onlyOneFileCanBeAttached: "Vi povas aldoni nur unu dosieron po unu mesaĝo."
 | 
			
		||||
uiLanguage: "Lingvo de la interfaco"
 | 
			
		||||
noFollowRequests: "Vi ne havas eksekvopetojn."
 | 
			
		||||
invitationCode: "Kodo de invito"
 | 
			
		||||
or: "Aŭ"
 | 
			
		||||
language: "Lingvo"
 | 
			
		||||
uiLanguage: "Lingvo de la fasado"
 | 
			
		||||
aboutX: "Pri {x}"
 | 
			
		||||
useOsNativeEmojis: "Oni uzas la emoĵioj de la denaska sistemo"
 | 
			
		||||
youHaveNoGroups: "Neniuj grupoj"
 | 
			
		||||
category: "Kategorio"
 | 
			
		||||
tags: "Etikedoj"
 | 
			
		||||
createAccount: "Krei konton"
 | 
			
		||||
existingAccount: "Ekzista konto"
 | 
			
		||||
fontSize: "Tipara grando"
 | 
			
		||||
noFollowRequests: "Vi ne havas peto de sekvado"
 | 
			
		||||
openImageInNewTab: "Fermi la bildon en nova tablo"
 | 
			
		||||
dashboard: "Stirpanelo"
 | 
			
		||||
local: "Loka"
 | 
			
		||||
remote: "Transa"
 | 
			
		||||
total: "Entute"
 | 
			
		||||
clientSettings: "Agordoj de kliento"
 | 
			
		||||
accountSettings: "Agordoj de Konto"
 | 
			
		||||
numberOfDays: "Nombro de tagoj"
 | 
			
		||||
hideThisNote: "Kaŝi tiun noton"
 | 
			
		||||
deleteAllFiles: "Forvisi ĉiujn dosierojn"
 | 
			
		||||
objectStorageBaseUrl: "Baza URL"
 | 
			
		||||
objectStorageRegion: "Regiono"
 | 
			
		||||
objectStorageUseSSL: "Oni uzas SSL"
 | 
			
		||||
serverLogs: "Servila protokolo"
 | 
			
		||||
deleteAll: "Forviŝi ĉiujn"
 | 
			
		||||
sounds: "Sonoj"
 | 
			
		||||
listen: "Aŭdi"
 | 
			
		||||
none: "Neniu"
 | 
			
		||||
showInPage: "Vidi en paĝo"
 | 
			
		||||
deleteAllFiles: "Forviŝi ĉiujn dosierojn"
 | 
			
		||||
deleteAllFilesConfirm: "Ĉu vi certas, ke vi volas forviŝi ĉiujn viajn dosierojn?"
 | 
			
		||||
userSilenced: "Tiu uzanto estas mutigata."
 | 
			
		||||
menu: "Menuo"
 | 
			
		||||
deletedNote: "Forviŝita noto"
 | 
			
		||||
invisibleNote: "Malpublika noto"
 | 
			
		||||
poll: "Balotujo"
 | 
			
		||||
useCw: "Kaŝi enhavo"
 | 
			
		||||
themeEditor: "Redaktilo de koloraroj"
 | 
			
		||||
author: "Aŭtoro"
 | 
			
		||||
manage: "Administro"
 | 
			
		||||
plugins: "Kromaĵoj"
 | 
			
		||||
deck: "Kartaro"
 | 
			
		||||
medium: "Meza"
 | 
			
		||||
small: "Malgranda"
 | 
			
		||||
edit: "Redakti"
 | 
			
		||||
emailServer: "Retpoŝta servilo"
 | 
			
		||||
email: "Retpoŝto"
 | 
			
		||||
emailAddress: "Retpoŝtadreso"
 | 
			
		||||
smtpUser: "Uzantonomo"
 | 
			
		||||
emailAddress: "Retpoŝta adreso"
 | 
			
		||||
smtpConfig: "Agordoj de la servilo SMTP"
 | 
			
		||||
smtpPort: "Pordo"
 | 
			
		||||
smtpUser: "Uzantnomo"
 | 
			
		||||
smtpPass: "Pasvorto"
 | 
			
		||||
wordMute: "Silentigo de vortoj"
 | 
			
		||||
userSaysSomething: "{name} parolis ion"
 | 
			
		||||
display: "Vidi"
 | 
			
		||||
copy: "Kopii"
 | 
			
		||||
database: "Datumbazo"
 | 
			
		||||
channel: "Kanalo"
 | 
			
		||||
fileIdOrUrl: "Dosirero ID aŭ retadreso"
 | 
			
		||||
create: "Krei"
 | 
			
		||||
notificationSetting: "Agordoj de sciigoj"
 | 
			
		||||
useGlobalSetting: "Oni uzas malloka agordo"
 | 
			
		||||
fileIdOrUrl: "Dosiera identigilo aŭ URL"
 | 
			
		||||
abuseReports: "Signaloj"
 | 
			
		||||
reportAbuse: "Signalo"
 | 
			
		||||
reportAbuseOf: "Signali kontraŭ {name}'(o)"
 | 
			
		||||
send: "Sendi"
 | 
			
		||||
i18nInfo: "Tradukojn de Misskey en diversaj lingvoj faras volontuloj. Vi povus kunlabori en tradukado sur {link}, se vi volus."
 | 
			
		||||
driveFilesCount: "Numero de dosieroj en la diskilo"
 | 
			
		||||
onlineUsersCount: "{n} uzanto(j) estas surkonektita"
 | 
			
		||||
openInNewTab: "Malfermi en nova langeto"
 | 
			
		||||
editTheseSettingsMayBreakAccount: "Redakti tiujn agordojn estas eble damaĝi konton."
 | 
			
		||||
public: "Publika"
 | 
			
		||||
i18nInfo: "Misskey estas tradukata en diversaj lingvoj far volontuloj. Oni povas kontribui por la tradukado ĉe {link}."
 | 
			
		||||
accountInfo: "Kontaj Informoj"
 | 
			
		||||
notesCount: "Numero de notoj"
 | 
			
		||||
repliesCount: "Numero de respondoj senditaj"
 | 
			
		||||
renotesCount: "Numero de renotoj kiun vi sendis"
 | 
			
		||||
repliedCount: "Numero de respondoj ricevitaj"
 | 
			
		||||
renotedCount: "Numero de renotoj kiun vi ricevis"
 | 
			
		||||
followingCount: "Numero de sekvatoj"
 | 
			
		||||
followersCount: "Numero de sekvantoj"
 | 
			
		||||
sentReactionsCount: "Numero de sentitaj reagoj"
 | 
			
		||||
receivedReactionsCount: "Numero de ricevitaj reagoj"
 | 
			
		||||
yes: "Jes"
 | 
			
		||||
no: "Ne"
 | 
			
		||||
driveFilesCount: "Numero de dosieroj sur la disko"
 | 
			
		||||
notSet: "Ne elektita"
 | 
			
		||||
noteFavoritesCount: "Numero de la preferataj notoj"
 | 
			
		||||
contact: "Kontakto"
 | 
			
		||||
makeExplorable: "Videbligi konton sur la paĝo \"Esplori\""
 | 
			
		||||
duplicate: "Duobligi"
 | 
			
		||||
left: "Maldekstra"
 | 
			
		||||
center: "Centra"
 | 
			
		||||
showTitlebar: "Montri titola stango"
 | 
			
		||||
clearCache: "Malplenigi staplon"
 | 
			
		||||
onlineUsersCount: "{n} uzanto(j) estas surlinea"
 | 
			
		||||
nUsers: "{n} uzanto(j)"
 | 
			
		||||
nNotes: "{n} notoj"
 | 
			
		||||
myTheme: "Miaj koloraroj"
 | 
			
		||||
backgroundColor: "Fona koloro"
 | 
			
		||||
textColor: "Teksto"
 | 
			
		||||
saveAs: "Konservi kiel…"
 | 
			
		||||
value: "Valoro"
 | 
			
		||||
createdAt: "Kreita je"
 | 
			
		||||
updatedAt: "Laste ĝisdatigita"
 | 
			
		||||
deleteConfirm: "Ĉu certas forviŝi?"
 | 
			
		||||
closeAccount: "Forigi konton"
 | 
			
		||||
currentVersion: "Nuna versio"
 | 
			
		||||
latestVersion: "Plej nova versio"
 | 
			
		||||
youAreRunningUpToDateClient: "Vi uzas la plej novan version de via kliento."
 | 
			
		||||
newVersionOfClientAvailable: "Nova versio de via kliento estas disponebla."
 | 
			
		||||
inUse: "Uzata"
 | 
			
		||||
editCode: "Redakti kodon"
 | 
			
		||||
emailNotification: "Sciigoj per retpoŝto"
 | 
			
		||||
publish: "Publikigi"
 | 
			
		||||
inChannelSearch: "Serĉi en kanalo"
 | 
			
		||||
useReactionPickerForContextMenu: "Oni malfermas reago-elektilon per dekstro-kliki"
 | 
			
		||||
typingUsers: "{users} estas entajpanta(j)..."
 | 
			
		||||
info: "Informoj"
 | 
			
		||||
unknown: "Nekonata"
 | 
			
		||||
online: "Surkonektita"
 | 
			
		||||
offline: "Forkonektita"
 | 
			
		||||
instanceBlocking: "Ekzempla blokado"
 | 
			
		||||
instanceBlocking: "Blokado de ekzemplo"
 | 
			
		||||
selectAccount: "Elekti konton"
 | 
			
		||||
user: "Uzanto"
 | 
			
		||||
administration: "Administro"
 | 
			
		||||
accounts: "Kontoj"
 | 
			
		||||
high: "Alta"
 | 
			
		||||
middle: "Meza"
 | 
			
		||||
low: "Malalta"
 | 
			
		||||
customCss: "Uzantula CSS"
 | 
			
		||||
global: "Malloka"
 | 
			
		||||
sent: "Sendi"
 | 
			
		||||
received: "Ricevita"
 | 
			
		||||
searchResult: "Serĉorezultoj"
 | 
			
		||||
hashtags: "Kradvorto"
 | 
			
		||||
learnMore: "Lernu pli"
 | 
			
		||||
translate: "Traduki"
 | 
			
		||||
translatedFrom: "Tradukita el {x}"
 | 
			
		||||
_docs:
 | 
			
		||||
  continueReading: "Legi plu"
 | 
			
		||||
  features: "Funkcioj"
 | 
			
		||||
  admin: "Administro"
 | 
			
		||||
_gallery:
 | 
			
		||||
  liked: "Ŝatitaj notoj"
 | 
			
		||||
  like: "Ŝati"
 | 
			
		||||
_email:
 | 
			
		||||
  _follow:
 | 
			
		||||
    title: "Vi estas eksekvita."
 | 
			
		||||
    title: "Vi estas eksekvita"
 | 
			
		||||
  _receiveFollowRequest:
 | 
			
		||||
    title: "Vi ricevis eksekvopeton."
 | 
			
		||||
    title: "Vi ricevis peton de sekvado"
 | 
			
		||||
_plugin:
 | 
			
		||||
  install: "Instali kromaĵon"
 | 
			
		||||
  manage: "Administri kromaĵojn"
 | 
			
		||||
_registry:
 | 
			
		||||
  key: "Ŝlosilo"
 | 
			
		||||
  keys: "Ŝlosiloj"
 | 
			
		||||
  domain: "Nomregno"
 | 
			
		||||
  createKey: "Krei ŝlosilon"
 | 
			
		||||
_aboutMisskey:
 | 
			
		||||
  about: "Misskey estas malferma koda programo evoluigata far syuilo ekde la 2014."
 | 
			
		||||
  about: "Misskey estas malfermitkoda programo evoluigata de syuilo ekde la 2014."
 | 
			
		||||
  contributors: "Precipaj kontribuantoj"
 | 
			
		||||
  allContributors: "Ĉiuj kontribuintoj"
 | 
			
		||||
  source: "Fontkodo"
 | 
			
		||||
  translation: "Traduki Misskey'on"
 | 
			
		||||
  patrons: "Mecenatoj"
 | 
			
		||||
_mfm:
 | 
			
		||||
  dummy: "Misskey vastigas la mondon de Fediverso"
 | 
			
		||||
  mention: "Mencioj"
 | 
			
		||||
  url: "Retadreso"
 | 
			
		||||
  blockCode: "Kodo (Ujo)"
 | 
			
		||||
  blockMath: "Formulo (Ujo)"
 | 
			
		||||
  hashtag: "Kradvorto"
 | 
			
		||||
  url: "URL"
 | 
			
		||||
  link: "Ligilo"
 | 
			
		||||
  bold: "Grasa"
 | 
			
		||||
  small: "Malgrande"
 | 
			
		||||
  center: "Centrigi"
 | 
			
		||||
  inlineCode: "Kodo (en linio)"
 | 
			
		||||
  blockCode: "Kodo (bloko)"
 | 
			
		||||
  inlineMath: "Formulo (en linio)"
 | 
			
		||||
  blockMath: "Formulo (bloko)"
 | 
			
		||||
  quote: "Citi"
 | 
			
		||||
  emoji: "Personecigitaj emoĵioj"
 | 
			
		||||
  search: "Serĉi"
 | 
			
		||||
  flip: "Inversa"
 | 
			
		||||
  x2: "Granda"
 | 
			
		||||
  x3: "Grandega"
 | 
			
		||||
  x4: "Pli grandega"
 | 
			
		||||
_reversi:
 | 
			
		||||
  total: "Entute"
 | 
			
		||||
_instanceTicker:
 | 
			
		||||
  none: "Ne montri"
 | 
			
		||||
  remote: "Montri al transaj uzantoj"
 | 
			
		||||
  always: "Ĉiam montri"
 | 
			
		||||
_channel:
 | 
			
		||||
  create: "Krei kanalon"
 | 
			
		||||
  edit: "Redakti kanalon"
 | 
			
		||||
  following: "Sekvaton"
 | 
			
		||||
  owned: "Posedaĵo"
 | 
			
		||||
  following: "Sekvante"
 | 
			
		||||
  usersCount: "{n} partoprenanto(j)"
 | 
			
		||||
_menuDisplay:
 | 
			
		||||
  hide: "Kaŝi"
 | 
			
		||||
_wordMute:
 | 
			
		||||
  muteWords: "Kaŝigitaj vortoj"
 | 
			
		||||
  mutedNotes: "Silentigataj notoj"
 | 
			
		||||
_theme:
 | 
			
		||||
  manage: "Administri kolorarojn"
 | 
			
		||||
  code: "Kodo de koloraro"
 | 
			
		||||
  darken: "Malbrileco"
 | 
			
		||||
  lighten: "Brileco"
 | 
			
		||||
  keys:
 | 
			
		||||
    bg: "Fono"
 | 
			
		||||
    navBg: "Fono de flanka stango"
 | 
			
		||||
    hashtag: "Kradvorto"
 | 
			
		||||
    mention: "Mencioj"
 | 
			
		||||
    renote: "Renotici"
 | 
			
		||||
    renote: "Fari renoton"
 | 
			
		||||
    buttonBg: "Fono de butono"
 | 
			
		||||
    driveFolderBg: "Fono de dosierujo de la disko"
 | 
			
		||||
_sfx:
 | 
			
		||||
  note: "Nova noto"
 | 
			
		||||
  noteMy: "Mia noto"
 | 
			
		||||
  notification: "Sciigoj"
 | 
			
		||||
  chat: "Babilejoj"
 | 
			
		||||
  channel: "Kanala sciigoj"
 | 
			
		||||
  chat: "Retbabili"
 | 
			
		||||
  chatBg: "Retbabili (BG)"
 | 
			
		||||
  antenna: "Ricevo de anteno"
 | 
			
		||||
  channel: "Sciigoj de kanalo"
 | 
			
		||||
_ago:
 | 
			
		||||
  future: "Futuro"
 | 
			
		||||
  justNow: "Ĵus"
 | 
			
		||||
  secondsAgo: "Antaŭ {n} sekundoj"
 | 
			
		||||
  minutesAgo: "Antaŭ {n} minutoj"
 | 
			
		||||
  hoursAgo: "Antaŭ {n} horo(j)"
 | 
			
		||||
  daysAgo: "Antaŭ {n} tagoj"
 | 
			
		||||
  weeksAgo: "Antaŭ {n} semajnoj"
 | 
			
		||||
  monthsAgo: "Antaŭ {n} monatoj"
 | 
			
		||||
  yearsAgo: "Antaŭ {n} jaroj"
 | 
			
		||||
_time:
 | 
			
		||||
  second: "sek"
 | 
			
		||||
  minute: "min"
 | 
			
		||||
  hour: "hor"
 | 
			
		||||
  day: "Tago"
 | 
			
		||||
_tutorial:
 | 
			
		||||
  title: "Uzado de Miskejo"
 | 
			
		||||
  title: "Uzado de Misskey"
 | 
			
		||||
  step1_1: "Bonvenon."
 | 
			
		||||
  step7_2: "Se vi volas scii pli pri Misskey, rigardu la fakon {help}."
 | 
			
		||||
  step7_3: "Do, bonvolu amuziĝi Misskey'on🚀"
 | 
			
		||||
_permissions:
 | 
			
		||||
  "read:blocks": "Vidi la listo de la uzantoj kiun vi blokis."
 | 
			
		||||
  "read:drive": "Vidi dosierojn en la diskilo"
 | 
			
		||||
  "read:channels": "Legi kanalon"
 | 
			
		||||
  "read:blocks": "Vidi la liston de uzantoj kiun vi blokas"
 | 
			
		||||
  "write:blocks": "Redakti vian liston de blokataj uzantoj"
 | 
			
		||||
  "read:drive": "Operacio por legi la informon de dosiero en via disko de Misskey"
 | 
			
		||||
  "write:drive": "Ĉia operacio por skribi, forviŝi, aŭ alimaniere ŝanĝi la informon de dosiero en via disko de Misskey"
 | 
			
		||||
  "read:favorites": "Vidi vian liston de preferatoj"
 | 
			
		||||
  "read:following": "Vidi tiun kiun vi sekvas"
 | 
			
		||||
  "write:following": "Sekvi aŭ malsekvi alian uzanton"
 | 
			
		||||
  "read:messaging": "Vidi vian retbabiladon"
 | 
			
		||||
  "read:mutes": "Vidi vian liston de silentigoj"
 | 
			
		||||
  "write:mutes": "Redakti vian liston de silentigoj"
 | 
			
		||||
  "write:notes": "Krei / Forviŝi noton"
 | 
			
		||||
  "read:notifications": "Vidi sciigojn"
 | 
			
		||||
  "read:reactions": "Vidi reagojn"
 | 
			
		||||
  "write:reactions": "Redakti viajn reagojn"
 | 
			
		||||
  "read:pages": "Vidi via paĝojn"
 | 
			
		||||
  "read:page-likes": "Vidi ŝatojn de paĝo"
 | 
			
		||||
  "read:channels": "Vidi kanalojn"
 | 
			
		||||
_antennaSources:
 | 
			
		||||
  all: "Ĉiuj notoj"
 | 
			
		||||
  homeTimeline: "Notoj far uzantoj, kiujn vi sekvas"
 | 
			
		||||
_weekday:
 | 
			
		||||
  sunday: "dimanĉo"
 | 
			
		||||
  monday: "lundo"
 | 
			
		||||
  tuesday: "mardo"
 | 
			
		||||
  wednesday: "merkredo"
 | 
			
		||||
  thursday: "ĵaŭdo"
 | 
			
		||||
  friday: "vendredo"
 | 
			
		||||
  saturday: "sabato"
 | 
			
		||||
_widgets:
 | 
			
		||||
  notifications: "Sciigoj"
 | 
			
		||||
  timeline: "Tempolinio"
 | 
			
		||||
  federation: "Fediverso"
 | 
			
		||||
  timeline: "Templinio"
 | 
			
		||||
  clock: "Horloĝo"
 | 
			
		||||
  activity: "Aktiveco"
 | 
			
		||||
  federation: "Kunfederaĵo"
 | 
			
		||||
  slideshow: "Bildoprezento"
 | 
			
		||||
  button: "Butono"
 | 
			
		||||
  onlineUsers: "Surkonektita uzanto"
 | 
			
		||||
_cw:
 | 
			
		||||
  show: "Vidu plu"
 | 
			
		||||
  show: "Vidu pli"
 | 
			
		||||
  files: "{count} dosiero(j)"
 | 
			
		||||
_poll:
 | 
			
		||||
  choiceN: "Balotilo {n}"
 | 
			
		||||
  noMore: "Oni ne povas aldoni pli."
 | 
			
		||||
  infinite: "Neniam"
 | 
			
		||||
  deadlineTime: "hor"
 | 
			
		||||
  votesCount: "{n} balotiloj"
 | 
			
		||||
  vote: "Baloti"
 | 
			
		||||
  closed: "Oni jam balotis ĝin"
 | 
			
		||||
_visibility:
 | 
			
		||||
  publicDescription: "Via noto aperiĝos sur konfederacia tempolinio"
 | 
			
		||||
  home: "Ĉefpaĝo"
 | 
			
		||||
  homeDescription: "Elsendi nur sur hejma tempolinio"
 | 
			
		||||
  publicDescription: "Via noto aperiĝos sur la konfederacia templinio"
 | 
			
		||||
  home: "Hejmo"
 | 
			
		||||
  homeDescription: "Elsendi nur sur la hejmtemplinio"
 | 
			
		||||
  followers: "Sekvantoj"
 | 
			
		||||
  followersDescription: "Elsendi nur al sekvantoj de mi"
 | 
			
		||||
  followersDescription: "Nur al sekvantoj al mi"
 | 
			
		||||
  localOnly: "Nur loka"
 | 
			
		||||
  localOnlyDescription: "Nelegabla al transaj uzantoj"
 | 
			
		||||
  localOnlyDescription: "Ne montri al transaj uzantoj"
 | 
			
		||||
_postForm:
 | 
			
		||||
  channelPlaceholder: "Elsendi sur la kanalo"
 | 
			
		||||
  replyPlaceholder: "Respondi al tiu noto..."
 | 
			
		||||
  quotePlaceholder: "Citado tiun noton..."
 | 
			
		||||
  channelPlaceholder: "Sendi sur la kanalo"
 | 
			
		||||
_profile:
 | 
			
		||||
  username: "Uzantonomo"
 | 
			
		||||
  name: "Nomo"
 | 
			
		||||
  username: "Uzantnomo"
 | 
			
		||||
  metadataEdit: "Redakti kromaj informoj"
 | 
			
		||||
  changeAvatar: "Ŝanĝi profilbildon"
 | 
			
		||||
  changeBanner: "Ŝanĝi standardon"
 | 
			
		||||
_exportOrImport:
 | 
			
		||||
  followingList: "Sekvi"
 | 
			
		||||
  muteList: "Silentigi"
 | 
			
		||||
  allNotes: "Ĉiuj notoj"
 | 
			
		||||
  followingList: "Sekvataj"
 | 
			
		||||
  muteList: "Silentigoj"
 | 
			
		||||
  blockingList: "Blokado"
 | 
			
		||||
  userLists: "Listoj"
 | 
			
		||||
_charts:
 | 
			
		||||
  federationInstancesTotal: "Tuta numero de kunfederantaj ekzemploj"
 | 
			
		||||
  filesTotal: "Tuta numero de dosieroj"
 | 
			
		||||
_timelines:
 | 
			
		||||
  home: "Hejmo"
 | 
			
		||||
  local: "Loka"
 | 
			
		||||
  social: "Hejmo kaj loka"
 | 
			
		||||
  home: "HEJMO"
 | 
			
		||||
  local: "LOKA"
 | 
			
		||||
  social: "SOCIALA"
 | 
			
		||||
  global: "MALLOKA"
 | 
			
		||||
_rooms:
 | 
			
		||||
  translate: "Movi"
 | 
			
		||||
  chooseImage: "Elekti bildon"
 | 
			
		||||
  _furnitures:
 | 
			
		||||
    server: "Servilo"
 | 
			
		||||
    moon: "La luno"
 | 
			
		||||
_pages:
 | 
			
		||||
  editPage: "Redakti paĝon"
 | 
			
		||||
  deleted: "La paĝo estas forigita."
 | 
			
		||||
  editThisPage: "Redakti la paĝon"
 | 
			
		||||
  viewPage: "Vidi via paĝojn"
 | 
			
		||||
  my: "Miaj paĝoj"
 | 
			
		||||
  featured: "Ravaĵoj"
 | 
			
		||||
  content: "Blokado de paĝo"
 | 
			
		||||
  url: "Retadreso de la paĝo"
 | 
			
		||||
  chooseBlock: "Aldoni blokado"
 | 
			
		||||
  url: "URL de paĝo"
 | 
			
		||||
  alignCenter: "Centrigi"
 | 
			
		||||
  chooseBlock: "Aldoni blokon"
 | 
			
		||||
  blocks:
 | 
			
		||||
    image: "Bildo"
 | 
			
		||||
    button: "Butono"
 | 
			
		||||
    _post:
 | 
			
		||||
      canvasId: "Kanvasa identigilo"
 | 
			
		||||
    _numberInput:
 | 
			
		||||
      text: "Titolo"
 | 
			
		||||
    _canvas:
 | 
			
		||||
      id: "Kanvasa identigilo"
 | 
			
		||||
    _note:
 | 
			
		||||
      id: "Identigilo de noto"
 | 
			
		||||
    _counter:
 | 
			
		||||
      text: "Titolo"
 | 
			
		||||
    _button:
 | 
			
		||||
      text: "Titolo"
 | 
			
		||||
      _action:
 | 
			
		||||
        _pushEvent:
 | 
			
		||||
          event: "Nomo de la evento"
 | 
			
		||||
  script:
 | 
			
		||||
    categories:
 | 
			
		||||
      list: "Listoj"
 | 
			
		||||
@@ -315,23 +680,32 @@ _pages:
 | 
			
		||||
        arg1: "Listoj"
 | 
			
		||||
      _listLen:
 | 
			
		||||
        arg1: "Listoj"
 | 
			
		||||
      _splitStrByLine:
 | 
			
		||||
        arg1: "Teksto"
 | 
			
		||||
    types:
 | 
			
		||||
      array: "Listoj"
 | 
			
		||||
      stringArray: "List de teksto"
 | 
			
		||||
_notification:
 | 
			
		||||
  fileUploaded: "La dosiero sukcese alŝutiĝis."
 | 
			
		||||
  youWereFollowed: "Vi estas eksekvita."
 | 
			
		||||
  youReceivedFollowRequest: "Vi ricevis eksekvopeton."
 | 
			
		||||
  yourFollowRequestAccepted: "Via eksekvopeto estas akceptita."
 | 
			
		||||
  youGotPoll: "{name} balotis"
 | 
			
		||||
  youGotMessagingMessageFromUser: "{name} sentis mesaĝon al vi."
 | 
			
		||||
  youGotMessagingMessageFromGroup: "Retbabilan mesaĝon oni sendis al la grupo {name}"
 | 
			
		||||
  youWereFollowed: "sksekvis vin"
 | 
			
		||||
  youReceivedFollowRequest: "Vi ricevis peton de sekvado"
 | 
			
		||||
  yourFollowRequestAccepted: "Via peto por sekvado estis akceptita."
 | 
			
		||||
  _types:
 | 
			
		||||
    follow: "Sekvi"
 | 
			
		||||
    follow: "Sekvatoj"
 | 
			
		||||
    mention: "Mencioj"
 | 
			
		||||
    renote: "Renotici"
 | 
			
		||||
    renote: "Fari renoton"
 | 
			
		||||
    quote: "Citi"
 | 
			
		||||
    reaction: "Reagoj"
 | 
			
		||||
    receiveFollowRequest: "Eksekvopeto ricevita"
 | 
			
		||||
    receiveFollowRequest: "Ricevita peton de sekvado"
 | 
			
		||||
    followRequestAccepted: "Akceptita peto por sekvado"
 | 
			
		||||
_deck:
 | 
			
		||||
  profile: "Agordaro"
 | 
			
		||||
  _columns:
 | 
			
		||||
    notifications: "Sciigoj"
 | 
			
		||||
    tl: "Tempolinio"
 | 
			
		||||
    tl: "Templinio"
 | 
			
		||||
    antenna: "Antenoj"
 | 
			
		||||
    list: "Listoj"
 | 
			
		||||
    mentions: "Mencioj"
 | 
			
		||||
    mentions: "Al vi"
 | 
			
		||||
 
 | 
			
		||||
@@ -127,6 +127,7 @@ editWidgets: "Editar widgets"
 | 
			
		||||
editWidgetsExit: "Terminar edición"
 | 
			
		||||
customEmojis: "Emojis personalizados"
 | 
			
		||||
emoji: "Emoji"
 | 
			
		||||
emojis: "Emoji"
 | 
			
		||||
emojiName: "Nombre del emoji"
 | 
			
		||||
emojiUrl: "URL de la imágen del emoji"
 | 
			
		||||
addEmoji: "Agregar emoji"
 | 
			
		||||
@@ -665,6 +666,10 @@ administration: "Administrar"
 | 
			
		||||
expiration: "Termina el"
 | 
			
		||||
middle: "Mediano"
 | 
			
		||||
global: "Global"
 | 
			
		||||
sent: "Enviar"
 | 
			
		||||
hashtags: "Hashtag"
 | 
			
		||||
_docs:
 | 
			
		||||
  admin: "Administrar"
 | 
			
		||||
_ad:
 | 
			
		||||
  back: "Deseleccionar"
 | 
			
		||||
_gallery:
 | 
			
		||||
 
 | 
			
		||||
@@ -91,11 +91,11 @@ followRequests: "Demandes d’abonnement"
 | 
			
		||||
unfollow: "Se désabonner"
 | 
			
		||||
followRequestPending: "Demande d'abonnement en attente de confirmation"
 | 
			
		||||
enterEmoji: "Insérer un émoji"
 | 
			
		||||
renote: "Partager"
 | 
			
		||||
unrenote: "Annuler le partage"
 | 
			
		||||
renoted: "Republié !"
 | 
			
		||||
cantRenote: "Ce message ne peut pas être republié."
 | 
			
		||||
cantReRenote: "Impossible de repartager un partage."
 | 
			
		||||
renote: "Renoter"
 | 
			
		||||
unrenote: "Annuler la Renote"
 | 
			
		||||
renoted: "Renoté !"
 | 
			
		||||
cantRenote: "Ce message ne peut pas être renoté."
 | 
			
		||||
cantReRenote: "Impossible de renoter une Renote."
 | 
			
		||||
quote: "Citer"
 | 
			
		||||
pinnedNote: "Note épinglée"
 | 
			
		||||
pinned: "Épingler sur le profil"
 | 
			
		||||
@@ -128,6 +128,7 @@ editWidgets: "Modifier les widgets"
 | 
			
		||||
editWidgetsExit: "Valider les modifications"
 | 
			
		||||
customEmojis: "Émojis personnalisés"
 | 
			
		||||
emoji: "Émoji"
 | 
			
		||||
emojis: "Émoji"
 | 
			
		||||
emojiName: "Nom de l’émoji"
 | 
			
		||||
emojiUrl: "URL de l’émoji"
 | 
			
		||||
addEmoji: "Ajouter un émoji"
 | 
			
		||||
@@ -528,6 +529,7 @@ removeAllFollowing: "Retenir tous les abonnements"
 | 
			
		||||
removeAllFollowingDescription: "Se désabonner de tous les comptes de {host}. Veuillez lancer cette action uniquement si l’instance n’existe plus."
 | 
			
		||||
userSuspended: "Cet·te utilisateur·rice a été suspendu·e."
 | 
			
		||||
userSilenced: "Cette utilisateur·trice a été mis·e en sourdine."
 | 
			
		||||
menu: "Menu"
 | 
			
		||||
divider: "Séparateur"
 | 
			
		||||
addItem: "Ajouter un élément"
 | 
			
		||||
rooms: "Chambre"
 | 
			
		||||
@@ -636,9 +638,9 @@ manageAccessTokens: "Gérer les jetons d'accès"
 | 
			
		||||
accountInfo: " Informations du compte "
 | 
			
		||||
notesCount: "Nombre de notes"
 | 
			
		||||
repliesCount: "Nombre de réponses envoyées"
 | 
			
		||||
renotesCount: "Nombre de notes repartagées"
 | 
			
		||||
renotesCount: "Nombre de notes que vous avez renotées"
 | 
			
		||||
repliedCount: "Nombre de réponses reçues"
 | 
			
		||||
renotedCount: "Nombre de Renotes"
 | 
			
		||||
renotedCount: "Nombre de vos notes renotées"
 | 
			
		||||
followingCount: "Nombre de comptes suivis"
 | 
			
		||||
followersCount: "Nombre d'abonnés"
 | 
			
		||||
sentReactionsCount: "Nombre de réactions envoyées"
 | 
			
		||||
@@ -760,7 +762,27 @@ middle: "Moyen"
 | 
			
		||||
low: "Basse"
 | 
			
		||||
emailNotConfiguredWarning: "Vous n'avez pas configuré d'adresse e-mail."
 | 
			
		||||
ratio: "Ratio"
 | 
			
		||||
customCss: "CSS personnalisé"
 | 
			
		||||
customCssWarn: "Utilisez cette fonctionnalité uniquement si vous savez exactement ce que vous faites. Une configuration inadaptée peut empêcher le client de s'exécuter normalement."
 | 
			
		||||
global: "Global"
 | 
			
		||||
squareAvatars: "Avatars carrés"
 | 
			
		||||
sent: "Envoyer"
 | 
			
		||||
searchResult: "Résultats de la recherche"
 | 
			
		||||
hashtags: "Hashtags"
 | 
			
		||||
troubleshooting: "Résolution de problèmes"
 | 
			
		||||
useBlurEffect: "Utiliser des effets de flou dans l'interface"
 | 
			
		||||
learnMore: "Plus d'informations"
 | 
			
		||||
misskeyUpdated: "Misskey a été mis à jour !"
 | 
			
		||||
whatIsNew: "Voir les derniers changements"
 | 
			
		||||
translate: "Traduire"
 | 
			
		||||
translatedFrom: "Traduit depuis {x}"
 | 
			
		||||
_docs:
 | 
			
		||||
  continueReading: "Lire plus"
 | 
			
		||||
  features: "Fonctionnalités"
 | 
			
		||||
  generalTopics: "Sujets généraux"
 | 
			
		||||
  advancedTopics: "Sujets avancés"
 | 
			
		||||
  admin: "Gestion"
 | 
			
		||||
  translateWarn: "Ceci est une traduction dont le contenu peut différer du texte original."
 | 
			
		||||
_ad:
 | 
			
		||||
  back: "Retour"
 | 
			
		||||
  reduceFrequencyOfThisAd: "Voir cette publicité moins souvent"
 | 
			
		||||
@@ -859,6 +881,8 @@ _mfm:
 | 
			
		||||
  blurDescription: "Le contenu peut être flouté ; il sera visible en le survolant avec le curseur."
 | 
			
		||||
  font: "Police de caractères"
 | 
			
		||||
  fontDescription: "Il est possible de choisir la police."
 | 
			
		||||
  rainbow: "Arc-en-ciel"
 | 
			
		||||
  rainbowDescription: "Permet d'afficher le contenu en couleurs arc-en-ciel."
 | 
			
		||||
_reversi:
 | 
			
		||||
  reversi: "Reversi"
 | 
			
		||||
  gameSettings: "Réglages de la partie"
 | 
			
		||||
@@ -911,6 +935,9 @@ _channel:
 | 
			
		||||
  usersCount: "{n} Participant·e·s"
 | 
			
		||||
  notesCount: "{n} Notes"
 | 
			
		||||
_menuDisplay:
 | 
			
		||||
  sideFull: "Latéral"
 | 
			
		||||
  sideIcon: "Latéral (icônes)"
 | 
			
		||||
  top: "Haut de page"
 | 
			
		||||
  hide: "Masquer"
 | 
			
		||||
_wordMute:
 | 
			
		||||
  muteWords: "Mots à filtrer"
 | 
			
		||||
@@ -969,7 +996,7 @@ _theme:
 | 
			
		||||
    hashtag: "Hashtags"
 | 
			
		||||
    mention: "Mentionner"
 | 
			
		||||
    mentionMe: "Mentions (Moi)"
 | 
			
		||||
    renote: "Partager"
 | 
			
		||||
    renote: "Renoter"
 | 
			
		||||
    modalBg: "Modal d'arrière-plan"
 | 
			
		||||
    divider: "Séparateur"
 | 
			
		||||
    scrollbarHandle: "Poignée de la barre de navigation"
 | 
			
		||||
@@ -1590,11 +1617,11 @@ _notification:
 | 
			
		||||
  youWereInvitedToGroup: "Invité·e au groupe"
 | 
			
		||||
  _types:
 | 
			
		||||
    all: "Toutes"
 | 
			
		||||
    follow: "Abonnements"
 | 
			
		||||
    follow: "Nouvel·le abonné·e"
 | 
			
		||||
    mention: "Mentions"
 | 
			
		||||
    reply: "Réponses"
 | 
			
		||||
    renote: "Partager"
 | 
			
		||||
    quote: "Citer"
 | 
			
		||||
    renote: "Renotes"
 | 
			
		||||
    quote: "Citations"
 | 
			
		||||
    reaction: "Réactions"
 | 
			
		||||
    pollVote: "Votes dans des sondages"
 | 
			
		||||
    receiveFollowRequest: "Demande d'abonnement reçue"
 | 
			
		||||
 
 | 
			
		||||
@@ -128,6 +128,7 @@ editWidgets: "Sunting gawit"
 | 
			
		||||
editWidgetsExit: "Selesai"
 | 
			
		||||
customEmojis: "Emoji kustom"
 | 
			
		||||
emoji: "Emoji"
 | 
			
		||||
emojis: "Emoji"
 | 
			
		||||
emojiName: "Nama emoji"
 | 
			
		||||
emojiUrl: "URL Emoji"
 | 
			
		||||
addEmoji: "Tambahkan emoji"
 | 
			
		||||
@@ -528,6 +529,7 @@ removeAllFollowing: "Tahan semua mengikuti"
 | 
			
		||||
removeAllFollowingDescription: "Batal mengikuti semua akun dari {host}. Mohon jalankan ini ketika instansi sudah tidak ada lagi."
 | 
			
		||||
userSuspended: "Pengguna ini telah dibekukan."
 | 
			
		||||
userSilenced: "Pengguna ini telah dibungkam."
 | 
			
		||||
menu: "Menu"
 | 
			
		||||
divider: "Pembagi"
 | 
			
		||||
addItem: "Tambahkan item"
 | 
			
		||||
rooms: "Ruang"
 | 
			
		||||
@@ -760,7 +762,26 @@ middle: "Sedang"
 | 
			
		||||
low: "Rendah"
 | 
			
		||||
emailNotConfiguredWarning: "Alamat surel tidak disetel."
 | 
			
		||||
ratio: "Rasio"
 | 
			
		||||
customCss: "Custom CSS"
 | 
			
		||||
customCssWarn: "Pengaturan ini seharusnya digunakan jika kamu tahu cara kerjanya. Memasukkan nilai yang tidak tepat dapat menyebabkan klien tidak berfungsi semestinya."
 | 
			
		||||
global: "Global"
 | 
			
		||||
squareAvatars: "Tampilkan avatar sebagai persegi"
 | 
			
		||||
sent: "Kirim"
 | 
			
		||||
received: "Diterima"
 | 
			
		||||
searchResult: "Hasil Penelusuran"
 | 
			
		||||
hashtags: "Tagar"
 | 
			
		||||
troubleshooting: "Penyelesaian Masalah"
 | 
			
		||||
useBlurEffect: "Gunakan efek blur pada antarmuka"
 | 
			
		||||
learnMore: "Pelajari lebih lanjut"
 | 
			
		||||
misskeyUpdated: "Misskey telah dimutakhirkan!"
 | 
			
		||||
whatIsNew: "Lihat perubahan pemutakhiran"
 | 
			
		||||
_docs:
 | 
			
		||||
  continueReading: "Baca lebih lanjut"
 | 
			
		||||
  features: "Fitur"
 | 
			
		||||
  generalTopics: "Topik umum"
 | 
			
		||||
  advancedTopics: "Topik tingkat lanjut"
 | 
			
		||||
  admin: "Manajemen"
 | 
			
		||||
  translateWarn: "Ini merupakan dokumen terjemahan. Konten di dalamnya kemungkinan dapat berbeda dari yang aslinya."
 | 
			
		||||
_ad:
 | 
			
		||||
  back: "Kembali"
 | 
			
		||||
  reduceFrequencyOfThisAd: "Tampilkan iklan ini lebih sedikit"
 | 
			
		||||
@@ -859,6 +880,8 @@ _mfm:
 | 
			
		||||
  blurDescription: "Konten dapat diburamkan dengan efek ini. Konten dapat ditampilkan dengan jelas dengan melayangkan kursor tetikus di atasnya."
 | 
			
		||||
  font: "Font"
 | 
			
		||||
  fontDescription: "Setel font yang ditampilkan untuk konten."
 | 
			
		||||
  rainbow: "Pelangi"
 | 
			
		||||
  rainbowDescription: "Membuat konten muncul dalam warna pelangi."
 | 
			
		||||
_reversi:
 | 
			
		||||
  reversi: "Reversi"
 | 
			
		||||
  gameSettings: "Pengaturan permainan"
 | 
			
		||||
@@ -911,6 +934,9 @@ _channel:
 | 
			
		||||
  usersCount: "{n} Partisipan"
 | 
			
		||||
  notesCount: "terdapat {n} catatan"
 | 
			
		||||
_menuDisplay:
 | 
			
		||||
  sideFull: "Horisontal"
 | 
			
		||||
  sideIcon: "Horisontal (Ikon)"
 | 
			
		||||
  top: "Atas"
 | 
			
		||||
  hide: "Sembunyikan"
 | 
			
		||||
_wordMute:
 | 
			
		||||
  muteWords: "Kata yang dibisukan"
 | 
			
		||||
 
 | 
			
		||||
@@ -127,6 +127,7 @@ editWidgets: "Modifica i widget"
 | 
			
		||||
editWidgetsExit: "Modifica fine"
 | 
			
		||||
customEmojis: "Emoji personalizzati"
 | 
			
		||||
emoji: "Emoji"
 | 
			
		||||
emojis: "Emoji"
 | 
			
		||||
emojiName: "Nome dell'emoji"
 | 
			
		||||
emojiUrl: "URL dell'emoji"
 | 
			
		||||
addEmoji: "Aggiungi un emoji"
 | 
			
		||||
@@ -741,6 +742,13 @@ low: "Bassa"
 | 
			
		||||
emailNotConfiguredWarning: "Non hai impostato nessun indirizzo e-mail."
 | 
			
		||||
ratio: "Rapporto"
 | 
			
		||||
global: "Federata"
 | 
			
		||||
sent: "Inviare"
 | 
			
		||||
hashtags: "Hashtag"
 | 
			
		||||
troubleshooting: "Risoluzione problemi"
 | 
			
		||||
_docs:
 | 
			
		||||
  continueReading: "Leggi di più"
 | 
			
		||||
  features: "Funzionalità"
 | 
			
		||||
  admin: "Gestione"
 | 
			
		||||
_ad:
 | 
			
		||||
  back: "Indietro"
 | 
			
		||||
  reduceFrequencyOfThisAd: "Visualizza questa pubblicità meno spesso"
 | 
			
		||||
@@ -799,6 +807,7 @@ _mfm:
 | 
			
		||||
  blur: "Sfocatura"
 | 
			
		||||
  font: "Tipo di carattere"
 | 
			
		||||
  fontDescription: "Puoi scegliere il tipo di carattere per il contenuto."
 | 
			
		||||
  rainbow: "Arcobaleno"
 | 
			
		||||
_reversi:
 | 
			
		||||
  reversi: "Reversi"
 | 
			
		||||
  gameSettings: "Impostazioni di gioco"
 | 
			
		||||
@@ -1386,12 +1395,12 @@ _notification:
 | 
			
		||||
  youWereInvitedToGroup: "Invitat@ al gruppo"
 | 
			
		||||
  _types:
 | 
			
		||||
    all: "Tutto"
 | 
			
		||||
    follow: "Follows"
 | 
			
		||||
    follow: "Nuovə follower"
 | 
			
		||||
    mention: "Menzioni"
 | 
			
		||||
    reply: "Rispondi"
 | 
			
		||||
    reply: "Risposte"
 | 
			
		||||
    renote: "Rinota"
 | 
			
		||||
    quote: "Cita"
 | 
			
		||||
    reaction: "Reazione"
 | 
			
		||||
    reaction: "Reazioni"
 | 
			
		||||
    pollVote: "Voti ricevuti"
 | 
			
		||||
    receiveFollowRequest: "Richiesta di follow ricevuta"
 | 
			
		||||
    followRequestAccepted: "Richiesta di follow accettata"
 | 
			
		||||
 
 | 
			
		||||
@@ -128,6 +128,7 @@ editWidgets: "ウィジェットを編集"
 | 
			
		||||
editWidgetsExit: "編集を終了"
 | 
			
		||||
customEmojis: "カスタム絵文字"
 | 
			
		||||
emoji: "絵文字"
 | 
			
		||||
emojis: "絵文字"
 | 
			
		||||
emojiName: "絵文字名"
 | 
			
		||||
emojiUrl: "絵文字画像URL"
 | 
			
		||||
addEmoji: "絵文字を追加"
 | 
			
		||||
@@ -765,6 +766,35 @@ customCss: "カスタムCSS"
 | 
			
		||||
customCssWarn: "この設定は必ず知識のある方が行ってください。不適切な設定を行うとクライアントが正常に使用できなくなる恐れがあります。"
 | 
			
		||||
global: "グローバル"
 | 
			
		||||
squareAvatars: "アイコンを四角形で表示"
 | 
			
		||||
sent: "送信"
 | 
			
		||||
received: "受信"
 | 
			
		||||
searchResult: "検索結果"
 | 
			
		||||
hashtags: "ハッシュタグ"
 | 
			
		||||
troubleshooting: "トラブルシューティング"
 | 
			
		||||
useBlurEffect: "UIにぼかし効果を使用"
 | 
			
		||||
learnMore: "詳しく"
 | 
			
		||||
misskeyUpdated: "Misskeyが更新されました!"
 | 
			
		||||
whatIsNew: "更新情報を見る"
 | 
			
		||||
translate: "翻訳"
 | 
			
		||||
translatedFrom: "{x}から翻訳"
 | 
			
		||||
accountDeletionInProgress: "アカウントの削除が進行中です"
 | 
			
		||||
usernameInfo: "サーバー上であなたのアカウントを一意に識別するための名前。アルファベット(a~z, A~Z)、数字(0~9)、およびアンダーバー(_)が使用できます。ユーザー名は後から変更することは出来ません。"
 | 
			
		||||
 | 
			
		||||
_accountDelete:
 | 
			
		||||
  accountDelete: "アカウントの削除"
 | 
			
		||||
  mayTakeTime: "アカウントの削除は負荷のかかる処理であるため、作成したコンテンツの数やアップロードしたファイルの数が多いと完了までに時間がかかることがあります。"
 | 
			
		||||
  sendEmail: "アカウントの削除が完了する際は、登録してあったメールアドレス宛に通知を送信します。"
 | 
			
		||||
  requestAccountDelete: "アカウント削除をリクエスト"
 | 
			
		||||
  started: "削除処理が開始されました。"
 | 
			
		||||
  inProgress: "削除が進行中"
 | 
			
		||||
 | 
			
		||||
_docs: 
 | 
			
		||||
  continueReading: "続きを読む"
 | 
			
		||||
  features: "機能"
 | 
			
		||||
  generalTopics: "一般的なトピック"
 | 
			
		||||
  advancedTopics: "高度なトピック"
 | 
			
		||||
  admin: "管理"
 | 
			
		||||
  translateWarn: "このドキュメントは翻訳されたものです。オリジナルとは内容が異なる場合があります。"
 | 
			
		||||
 | 
			
		||||
_ad:
 | 
			
		||||
  back: "戻る"
 | 
			
		||||
@@ -872,6 +902,8 @@ _mfm:
 | 
			
		||||
  blurDescription: "内容をぼかすことができます。ポインターを上に乗せるとはっきり見えるようになります。"
 | 
			
		||||
  font: "フォント"
 | 
			
		||||
  fontDescription: "内容のフォントを指定することができます。"
 | 
			
		||||
  rainbow: "レインボー"
 | 
			
		||||
  rainbowDescription: "内容をレインボーにします。"
 | 
			
		||||
 | 
			
		||||
_reversi:
 | 
			
		||||
  reversi: "リバーシ"
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ search: "探す"
 | 
			
		||||
notifications: "通知"
 | 
			
		||||
username: "ユーザー名"
 | 
			
		||||
password: "パスワード"
 | 
			
		||||
forgotPassword: "パスワード忘れてん"
 | 
			
		||||
fetchingAsApObject: "今ちと連合に照会しとるで"
 | 
			
		||||
ok: "OKや"
 | 
			
		||||
gotIt: "ほい"
 | 
			
		||||
@@ -127,6 +128,7 @@ editWidgets: "ウィジェットをいじる"
 | 
			
		||||
editWidgetsExit: "編集終ったで"
 | 
			
		||||
customEmojis: "カスタム絵文字"
 | 
			
		||||
emoji: "絵文字"
 | 
			
		||||
emojis: "絵文字"
 | 
			
		||||
emojiName: "絵文字名"
 | 
			
		||||
emojiUrl: "絵文字画像URL"
 | 
			
		||||
addEmoji: "絵文字を追加"
 | 
			
		||||
@@ -138,6 +140,7 @@ flagAsBotDescription: "もしこのアカウントがプログラムによって
 | 
			
		||||
flagAsCat: "Catやで"
 | 
			
		||||
flagAsCatDescription: "ワレ、猫ちゃんならこのフラグをつけてみ?"
 | 
			
		||||
autoAcceptFollowed: "フォローしとるユーザーからのフォローリクエストを勝手に許可しとく"
 | 
			
		||||
addAccount: "アカウントを追加"
 | 
			
		||||
loginFailed: "ログインに失敗してしもうた…"
 | 
			
		||||
showOnRemote: "リモートで見る"
 | 
			
		||||
general: "全般"
 | 
			
		||||
@@ -277,6 +280,7 @@ emptyDrive: "ドライブにはなんも残っとらん"
 | 
			
		||||
emptyFolder: "ふぉろだーにはなんも残っとらん"
 | 
			
		||||
unableToDelete: "消そうおもってんけどな、あかんかったわ"
 | 
			
		||||
inputNewFileName: "今度のファイル名は何にするん?"
 | 
			
		||||
inputNewDescription: "新しいキャプションを入力しましょ"
 | 
			
		||||
inputNewFolderName: "今度のフォルダ名は何にするん?"
 | 
			
		||||
circularReferenceFolder: "移動先のフォルダーは、移動するフォルダーのサブフォルダーや。"
 | 
			
		||||
hasChildFilesOrFolders: "このフォルダ、まだなんか入っとるから消されへん"
 | 
			
		||||
@@ -647,6 +651,10 @@ high: "高い"
 | 
			
		||||
middle: "中"
 | 
			
		||||
low: "低い"
 | 
			
		||||
global: "グローバル"
 | 
			
		||||
sent: "送信"
 | 
			
		||||
hashtags: "ハッシュタグ"
 | 
			
		||||
_docs:
 | 
			
		||||
  admin: "管理"
 | 
			
		||||
_ad:
 | 
			
		||||
  back: "戻る"
 | 
			
		||||
_gallery:
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,9 @@ username: "Isem n umseqdac"
 | 
			
		||||
password: "Awal uffir"
 | 
			
		||||
ok: "IH"
 | 
			
		||||
settings: "Iɣewwaṛen"
 | 
			
		||||
otherSettings: "Iɣewwaren nniḍen"
 | 
			
		||||
profile: "Amaɣnu"
 | 
			
		||||
signup: "Jerred"
 | 
			
		||||
save: "Sekles"
 | 
			
		||||
delete: "Kkes"
 | 
			
		||||
addToList: "Rnu ɣer tebdart"
 | 
			
		||||
@@ -27,15 +29,31 @@ followers: "Imeḍfaṛen"
 | 
			
		||||
followsYou: "Yeṭṭafaṛ-ik·em-id"
 | 
			
		||||
createList: "Snulfu-d tabdart"
 | 
			
		||||
enterListName: "Isem n tebdart"
 | 
			
		||||
privacy: "Tabaḍnit"
 | 
			
		||||
follow: "Ḍfeṛ"
 | 
			
		||||
you: "Kečči·mmi"
 | 
			
		||||
selectList: "Fren tabdart"
 | 
			
		||||
youHaveNoLists: "Ulac ɣur-k·m ula d yiwet n tabdart"
 | 
			
		||||
security: "Taɣellist"
 | 
			
		||||
remove: "Kkes"
 | 
			
		||||
userList: "Tibdarin"
 | 
			
		||||
securityKey: "Tasarutt n tɣellist"
 | 
			
		||||
securityKeyName: "Isem n tsarutt"
 | 
			
		||||
signinRequired: "Ttxil jerred"
 | 
			
		||||
signinWith: "Tuqqna s {x}"
 | 
			
		||||
tapSecurityKey: "Sekcem tasarutt-ik·im n tɣellist"
 | 
			
		||||
uiLanguage: "Tutlayt n wegrudem"
 | 
			
		||||
accountSettings: "Iɣewwaṛen n umiḍan"
 | 
			
		||||
plugins: "Izegrar"
 | 
			
		||||
email: "Imayl"
 | 
			
		||||
emailAddress: "Tansa imayl"
 | 
			
		||||
smtpUser: "Isem n umseqdac"
 | 
			
		||||
smtpPass: "Awal uffir"
 | 
			
		||||
other: "Wiyyaḍ"
 | 
			
		||||
accountInfo: "Talɣut n umiḍan"
 | 
			
		||||
emailNotification: "Ilɣa imayl"
 | 
			
		||||
selectAccount: "Fren amiḍan"
 | 
			
		||||
accounts: "Imiḍan"
 | 
			
		||||
_email:
 | 
			
		||||
  _follow:
 | 
			
		||||
    title: "Yeṭṭafaṛ-ik·em-id"
 | 
			
		||||
@@ -48,6 +66,8 @@ _theme:
 | 
			
		||||
    mention: "Bder"
 | 
			
		||||
_sfx:
 | 
			
		||||
  notification: "Ilɣuyen"
 | 
			
		||||
_permissions:
 | 
			
		||||
  "write:account": "Ẓreg talɣut n umiḍan-ik·im"
 | 
			
		||||
_widgets:
 | 
			
		||||
  notifications: "Ilɣuyen"
 | 
			
		||||
_cw:
 | 
			
		||||
 
 | 
			
		||||
@@ -128,6 +128,7 @@ editWidgets: "위젯 편집"
 | 
			
		||||
editWidgetsExit: "편집 종료"
 | 
			
		||||
customEmojis: "커스텀 이모지"
 | 
			
		||||
emoji: "이모지"
 | 
			
		||||
emojis: "이모지"
 | 
			
		||||
emojiName: "이모지 이름"
 | 
			
		||||
emojiUrl: "이모지 URL"
 | 
			
		||||
addEmoji: "이모지 추가"
 | 
			
		||||
@@ -528,6 +529,7 @@ removeAllFollowing: "모든 팔로잉 해제"
 | 
			
		||||
removeAllFollowingDescription: "{host}(으)로부터 모든 팔로잉을 해제합니다. 해당 인스턴스가 더 이상 존재하지 않게 된 경우 등에 실행해 주세요."
 | 
			
		||||
userSuspended: "이 계정은 정지된 상태입니다."
 | 
			
		||||
userSilenced: "이 계정은 사일런스된 상태입니다."
 | 
			
		||||
menu: "메뉴"
 | 
			
		||||
divider: "구분선"
 | 
			
		||||
addItem: "항목 추가"
 | 
			
		||||
rooms: "방"
 | 
			
		||||
@@ -760,7 +762,37 @@ middle: "보통"
 | 
			
		||||
low: "낮음"
 | 
			
		||||
emailNotConfiguredWarning: "메일 주소가 설정되어 있지 않습니다."
 | 
			
		||||
ratio: "비율"
 | 
			
		||||
customCss: "CSS 사용자화"
 | 
			
		||||
customCssWarn: "이 설정은 기능을 알고 있는 경우에만 사용해야 합니다. 잘못된 값을 입력하면 클라이언트가 정상적으로 작동하지 않을 수 있습니다."
 | 
			
		||||
global: "글로벌"
 | 
			
		||||
squareAvatars: "프로필 아이콘을 사각형으로 표시"
 | 
			
		||||
sent: "전송"
 | 
			
		||||
received: "수신"
 | 
			
		||||
searchResult: "검색 결과"
 | 
			
		||||
hashtags: "해시태그"
 | 
			
		||||
troubleshooting: "문제 해결"
 | 
			
		||||
useBlurEffect: "UI에 흐림 효과 사용"
 | 
			
		||||
learnMore: "자세히"
 | 
			
		||||
misskeyUpdated: "Misskey가 업데이트 되었습니다!"
 | 
			
		||||
whatIsNew: "패치 정보 보기"
 | 
			
		||||
translate: "번역"
 | 
			
		||||
translatedFrom: "{x}에서 번역"
 | 
			
		||||
accountDeletionInProgress: "계정 삭제 작업을 진행하고 있습니다"
 | 
			
		||||
usernameInfo: "서버상에서 계정을 식별하기 위한 이름. 알파벳(a~z, A~Z), 숫자(0~9) 및 언더바(_)를 사용할 수 있습니다. 사용자명은 나중에 변경할 수 없습니다."
 | 
			
		||||
_accountDelete:
 | 
			
		||||
  accountDelete: "계정 삭제"
 | 
			
		||||
  mayTakeTime: "계정 삭제는 서버에 부하를 가하기 때문에, 작성한 콘텐츠나 업로드한 파일의 수가 많으면 완료까지 시간이 걸릴 수 있습니다."
 | 
			
		||||
  sendEmail: "계정 삭제가 완료되면 등록된 이메일 주소로 알림을 보냅니다."
 | 
			
		||||
  requestAccountDelete: "계정 삭제 요청"
 | 
			
		||||
  started: "삭제 작업이 시작되었습니다."
 | 
			
		||||
  inProgress: "삭제 진행 중"
 | 
			
		||||
_docs:
 | 
			
		||||
  continueReading: "계속 읽기"
 | 
			
		||||
  features: "기능"
 | 
			
		||||
  generalTopics: "일반 주제"
 | 
			
		||||
  advancedTopics: "심화 주제"
 | 
			
		||||
  admin: "관리"
 | 
			
		||||
  translateWarn: "이 문서는 번역되었기 때문에 원본과는 내용이 다를 수 있습니다."
 | 
			
		||||
_ad:
 | 
			
		||||
  back: "뒤로"
 | 
			
		||||
  reduceFrequencyOfThisAd: "이 광고의 표시 빈도 낮추기"
 | 
			
		||||
@@ -859,6 +891,8 @@ _mfm:
 | 
			
		||||
  blurDescription: "내용이 흐리게 보입니다. 마우스를 위에 올려두면 내용이 보입니다."
 | 
			
		||||
  font: "폰트"
 | 
			
		||||
  fontDescription: "내용의 글꼴을 지정할 수 있습니다."
 | 
			
		||||
  rainbow: "무지개"
 | 
			
		||||
  rainbowDescription: "내용을 무지개로 표시합니다."
 | 
			
		||||
_reversi:
 | 
			
		||||
  reversi: "리버시"
 | 
			
		||||
  gameSettings: "대국 설정"
 | 
			
		||||
@@ -911,6 +945,9 @@ _channel:
 | 
			
		||||
  usersCount: "{n}명 참여 중"
 | 
			
		||||
  notesCount: "{n}노트"
 | 
			
		||||
_menuDisplay:
 | 
			
		||||
  sideFull: "가로"
 | 
			
		||||
  sideIcon: "가로(아이콘)"
 | 
			
		||||
  top: "상단"
 | 
			
		||||
  hide: "숨기기"
 | 
			
		||||
_wordMute:
 | 
			
		||||
  muteWords: "뮤트할 단어"
 | 
			
		||||
 
 | 
			
		||||
@@ -128,6 +128,7 @@ editWidgets: "Edytuj widżet"
 | 
			
		||||
editWidgetsExit: "Gotowe"
 | 
			
		||||
customEmojis: "Niestandardowe emoji"
 | 
			
		||||
emoji: "Emoji"
 | 
			
		||||
emojis: "Emoji"
 | 
			
		||||
emojiName: "Nazwa emoji"
 | 
			
		||||
emojiUrl: "Adres URL emoji"
 | 
			
		||||
addEmoji: "Dodaj emoji"
 | 
			
		||||
@@ -735,6 +736,10 @@ low: "Niski"
 | 
			
		||||
emailNotConfiguredWarning: "Nie podano adresu e-mail"
 | 
			
		||||
ratio: "Stosunek"
 | 
			
		||||
global: "Globalna"
 | 
			
		||||
sent: "Wyślij"
 | 
			
		||||
hashtags: "Hashtag"
 | 
			
		||||
_docs:
 | 
			
		||||
  admin: "Zarządzanie"
 | 
			
		||||
_ad:
 | 
			
		||||
  back: "Wróć"
 | 
			
		||||
  reduceFrequencyOfThisAd: "Pokazuj tę reklamę rzadziej"
 | 
			
		||||
 
 | 
			
		||||
@@ -128,6 +128,7 @@ editWidgets: "Редактировать виджеты"
 | 
			
		||||
editWidgetsExit: "Готово"
 | 
			
		||||
customEmojis: "Эмодзи пользователя"
 | 
			
		||||
emoji: "Эмодзи"
 | 
			
		||||
emojis: "Эмодзи"
 | 
			
		||||
emojiName: "Название эмодзи"
 | 
			
		||||
emojiUrl: "URL эмодзи"
 | 
			
		||||
addEmoji: "Добавить эмодзи"
 | 
			
		||||
@@ -528,6 +529,7 @@ removeAllFollowing: "Удалить всех подписчиков"
 | 
			
		||||
removeAllFollowingDescription: "Отменить все подписки с домена {host}? Пожалуйста, применяйте это действие, если инстанс больше не существует."
 | 
			
		||||
userSuspended: "Эта учётная запись заморожена"
 | 
			
		||||
userSilenced: "Этот пользователь был заглушен"
 | 
			
		||||
menu: "Меню"
 | 
			
		||||
divider: "Линия-разделитель"
 | 
			
		||||
addItem: "Добавить элемент"
 | 
			
		||||
rooms: "Комната"
 | 
			
		||||
@@ -760,7 +762,26 @@ middle: "Средне"
 | 
			
		||||
low: "Низкий"
 | 
			
		||||
emailNotConfiguredWarning: "Не указан адрес электронной почты"
 | 
			
		||||
ratio: "Соотношение"
 | 
			
		||||
customCss: "Индивидуальный CSS"
 | 
			
		||||
customCssWarn: "Используйте эту настройку только если знаете, что делаете. Ошибки здесь чреваты тем, что сайт перестанет нормально работать у вас."
 | 
			
		||||
global: "Всеобщая"
 | 
			
		||||
squareAvatars: "Квадратные аватарки"
 | 
			
		||||
sent: "Отправить"
 | 
			
		||||
received: "Получено"
 | 
			
		||||
searchResult: "Результаты поиска"
 | 
			
		||||
hashtags: "Хэштег"
 | 
			
		||||
troubleshooting: "Разрешение проблем"
 | 
			
		||||
useBlurEffect: "Размытие в интерфейсе"
 | 
			
		||||
learnMore: "Подробнее"
 | 
			
		||||
misskeyUpdated: "Misskey обновился!"
 | 
			
		||||
whatIsNew: "Что новенького?"
 | 
			
		||||
_docs:
 | 
			
		||||
  continueReading: "Читать подробнее"
 | 
			
		||||
  features: "Возможности"
 | 
			
		||||
  generalTopics: "Основные темы"
 | 
			
		||||
  advancedTopics: "Дополнительные темы"
 | 
			
		||||
  admin: "Управление"
 | 
			
		||||
  translateWarn: "Это перевод документа. Он может неточно отражать содержимое оригинала."
 | 
			
		||||
_ad:
 | 
			
		||||
  back: "Выход"
 | 
			
		||||
  reduceFrequencyOfThisAd: "Реже показывать эту рекламу"
 | 
			
		||||
@@ -859,6 +880,8 @@ _mfm:
 | 
			
		||||
  blurDescription: "Размывает текст до нечитаемости, будто его поместили за матовое стекло. Наведение указателя мыши на размытый текст возвращает чёткость."
 | 
			
		||||
  font: "Шрифт"
 | 
			
		||||
  fontDescription: "Так можно писать произвольным шрифтом."
 | 
			
		||||
  rainbow: "Радуга"
 | 
			
		||||
  rainbowDescription: "Заставлять содержимое отображаться в цветах радуги."
 | 
			
		||||
_reversi:
 | 
			
		||||
  reversi: "Реверси"
 | 
			
		||||
  gameSettings: "Настройки игры"
 | 
			
		||||
@@ -911,6 +934,9 @@ _channel:
 | 
			
		||||
  usersCount: "Участников: {n}"
 | 
			
		||||
  notesCount: "Заметок: {n}"
 | 
			
		||||
_menuDisplay:
 | 
			
		||||
  sideFull: "Сторона"
 | 
			
		||||
  sideIcon: "Сторона (иконки)"
 | 
			
		||||
  top: "Вверх"
 | 
			
		||||
  hide: "Спрятать"
 | 
			
		||||
_wordMute:
 | 
			
		||||
  muteWords: "Скрыть слово"
 | 
			
		||||
 
 | 
			
		||||
@@ -127,6 +127,7 @@ editWidgets: "Редагувати віджети"
 | 
			
		||||
editWidgetsExit: "Готово"
 | 
			
		||||
customEmojis: "Кастомні емоджі"
 | 
			
		||||
emoji: "Емоджі"
 | 
			
		||||
emojis: "Емоджі"
 | 
			
		||||
emojiName: "Назва емоджі"
 | 
			
		||||
emojiUrl: "URL емодзі"
 | 
			
		||||
addEmoji: "Додати емодзі"
 | 
			
		||||
@@ -689,6 +690,10 @@ administration: "Управління"
 | 
			
		||||
expiration: "Опитування закінчується"
 | 
			
		||||
middle: "Середній"
 | 
			
		||||
global: "Глобальна"
 | 
			
		||||
sent: "Відправити"
 | 
			
		||||
hashtags: "Хештеґ"
 | 
			
		||||
_docs:
 | 
			
		||||
  admin: "Управління"
 | 
			
		||||
_ad:
 | 
			
		||||
  back: "Назад"
 | 
			
		||||
_gallery:
 | 
			
		||||
 
 | 
			
		||||
@@ -128,6 +128,7 @@ editWidgets: "编辑小工具"
 | 
			
		||||
editWidgetsExit: "完成编辑"
 | 
			
		||||
customEmojis: "自定义表情符号"
 | 
			
		||||
emoji: "表情符号"
 | 
			
		||||
emojis: "表情符号"
 | 
			
		||||
emojiName: "表情符号名称"
 | 
			
		||||
emojiUrl: "表情符号地址"
 | 
			
		||||
addEmoji: "添加表情符号"
 | 
			
		||||
@@ -765,6 +766,29 @@ customCss: "自定义 CSS"
 | 
			
		||||
customCssWarn: "这些设置必须有相关的基础知识,不当的配置可能导致客户端无法正常使用!"
 | 
			
		||||
global: "全局"
 | 
			
		||||
squareAvatars: "显示方形头像图标"
 | 
			
		||||
sent: "发送"
 | 
			
		||||
received: "收取"
 | 
			
		||||
searchResult: "搜索结果"
 | 
			
		||||
hashtags: "话题标签"
 | 
			
		||||
troubleshooting: "故障排除"
 | 
			
		||||
useBlurEffect: "在UI上使用模糊效果"
 | 
			
		||||
learnMore: "更多信息"
 | 
			
		||||
misskeyUpdated: "Misskey更新完成!"
 | 
			
		||||
whatIsNew: "显示更新信息"
 | 
			
		||||
translate: "翻译"
 | 
			
		||||
translatedFrom: "从 {x} 翻译"
 | 
			
		||||
accountDeletionInProgress: "正在删除账户"
 | 
			
		||||
usernameInfo: "在服务器上唯一标识您的帐户的名称。您可以使用字母 (a ~ z, A ~ Z)、数字 (0 ~ 9) 和下划线 (_)。用户名以后不能更改。"
 | 
			
		||||
_accountDelete:
 | 
			
		||||
  accountDelete: "删除帐户"
 | 
			
		||||
  inProgress: "正在删除"
 | 
			
		||||
_docs:
 | 
			
		||||
  continueReading: "继续阅读"
 | 
			
		||||
  features: "特性"
 | 
			
		||||
  generalTopics: "通常提示"
 | 
			
		||||
  advancedTopics: "进阶提示"
 | 
			
		||||
  admin: "管理"
 | 
			
		||||
  translateWarn: "本文档是翻译后的文档。内容可能与原文有所不同。"
 | 
			
		||||
_ad:
 | 
			
		||||
  back: "返回"
 | 
			
		||||
  reduceFrequencyOfThisAd: "减少此广告的频率"
 | 
			
		||||
@@ -863,6 +887,8 @@ _mfm:
 | 
			
		||||
  blurDescription: "产生模糊效果。将鼠标指针放在上面即可将内容显示出来。"
 | 
			
		||||
  font: "字体"
 | 
			
		||||
  fontDescription: "可以设置内容所使用的字体。"
 | 
			
		||||
  rainbow: "彩虹"
 | 
			
		||||
  rainbowDescription: "用彩虹色来显示内容。"
 | 
			
		||||
_reversi:
 | 
			
		||||
  reversi: "黑白棋"
 | 
			
		||||
  gameSettings: "对局设置"
 | 
			
		||||
 
 | 
			
		||||
@@ -128,6 +128,7 @@ editWidgets: "編輯小工具"
 | 
			
		||||
editWidgetsExit: "完成"
 | 
			
		||||
customEmojis: "自訂表情符號"
 | 
			
		||||
emoji: "表情符號"
 | 
			
		||||
emojis: "表情符號"
 | 
			
		||||
emojiName: "表情符號名稱"
 | 
			
		||||
emojiUrl: "表情符號URL"
 | 
			
		||||
addEmoji: "加入表情符號"
 | 
			
		||||
@@ -751,6 +752,10 @@ low: "低"
 | 
			
		||||
emailNotConfiguredWarning: "沒有設定電子郵件地址"
 | 
			
		||||
ratio: "%"
 | 
			
		||||
global: "公開"
 | 
			
		||||
sent: "發送"
 | 
			
		||||
hashtags: "#tag"
 | 
			
		||||
_docs:
 | 
			
		||||
  admin: "管理"
 | 
			
		||||
_ad:
 | 
			
		||||
  back: "返回"
 | 
			
		||||
  reduceFrequencyOfThisAd: "降低此廣告的頻率 "
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										182
									
								
								migration/1629004542760-chart-reindex.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								migration/1629004542760-chart-reindex.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,182 @@
 | 
			
		||||
import {MigrationInterface, QueryRunner} from "typeorm";
 | 
			
		||||
 | 
			
		||||
export class chartReindex1629004542760 implements MigrationInterface {
 | 
			
		||||
    name = 'chartReindex1629004542760'
 | 
			
		||||
 | 
			
		||||
    public async up(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
        await queryRunner.query(`DELETE FROM "__chart__active_users" a USING "__chart__active_users" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
 | 
			
		||||
        await queryRunner.query(`DELETE FROM "__chart__drive" a USING "__chart__drive" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
 | 
			
		||||
        await queryRunner.query(`DELETE FROM "__chart__federation" a USING "__chart__federation" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
 | 
			
		||||
        await queryRunner.query(`DELETE FROM "__chart__hashtag" a USING "__chart__hashtag" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
 | 
			
		||||
        await queryRunner.query(`DELETE FROM "__chart__instance" a USING "__chart__instance" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
 | 
			
		||||
        await queryRunner.query(`DELETE FROM "__chart__network" a USING "__chart__network" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
 | 
			
		||||
        await queryRunner.query(`DELETE FROM "__chart__notes" a USING "__chart__notes" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
 | 
			
		||||
        await queryRunner.query(`DELETE FROM "__chart__per_user_drive" a USING "__chart__per_user_drive" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
 | 
			
		||||
        await queryRunner.query(`DELETE FROM "__chart__per_user_following" a USING "__chart__per_user_following" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
 | 
			
		||||
        await queryRunner.query(`DELETE FROM "__chart__per_user_notes" a USING "__chart__per_user_notes" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
 | 
			
		||||
        await queryRunner.query(`DELETE FROM "__chart__per_user_reaction" a USING "__chart__per_user_reaction" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
 | 
			
		||||
        await queryRunner.query(`DELETE FROM "__chart__test_grouped" a USING "__chart__test_grouped" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
 | 
			
		||||
        await queryRunner.query(`DELETE FROM "__chart__test_unique" a USING "__chart__test_unique" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
 | 
			
		||||
        await queryRunner.query(`DELETE FROM "__chart__users" a USING "__chart__users" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_0ad37b7ef50f4ddc84363d7ccc"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_00ed5f86db1f7efafb1978bf21"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_9a3ed15a30ab7e3a37702e6e08"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_13565815f618a1ff53886c5b28"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_7a170f67425e62a8fabb76c872"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_3313d7288855ec105b5bbf6c21"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_36cb699c49580d4e6c2e6159f9"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_76e87c7bfc5d925fcbba405d84"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_dd907becf76104e4b656659e6b"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_07747a1038c05f532a718fe1de"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_99a7d2faaef84a6f728d714ad6"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_25a97c02003338124b2b75fdbc"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_6b8f34a1a64b06014b6fb66824"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_da8a46ba84ca1d8bb5a29bfb63"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_39ee857ab2f23493037c6b6631"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_a1efd3e0048a5f2793a47360dc"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_7b5da130992ec9df96712d4290"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_0a905b992fecd2b5c3fb98759e"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_42eb716a37d381cdf566192b2b"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_7036f2957151588b813185c794"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_f09d543e3acb16c5976bdb31fa"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_5f86db6492274e07c1a3cdf286"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_e496ca8096d28f6b9b509264dc"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_30bf67687f483ace115c5ca642"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_7af07790712aa3438ff6773f3b"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_4b3593098b6edc9c5afe36b18b"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_b77d4dd9562c3a899d9a286fcd"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_84234bd1abb873f07329681c83"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_55bf20f366979f2436de99206b"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_5048e9daccbbbc6d567bb142d3"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_f7bf4c62059764c2c2bb40fdab"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_8cf3156fd7a6b15c43459c6e3b"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_229a41ad465f9205f1f5703291"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_0c641990ecf47d2545df4edb75"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_234dff3c0b56a6150b95431ab9"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_b14489029e4b3aaf4bba5fb524"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_437bab3c6061d90f6bb65fd2cc"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_bbfa573a8181018851ed0b6357"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_a0cd75442dd10d0643a17c4a49"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_b070a906db04b44c67c6c2144d"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_d41cce6aee1a50bfc062038f9b"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_a319e5dbf47e8a17497623beae"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_845254b3eaf708ae8a6cac3026"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_ed9b95919c672a13008e9487ee"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_337e9599f278bd7537fe30876f"`);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_9a3ed15a30ab7e3a37702e6e08" ON "__chart__active_users" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_60c5c6e7e538c09aa274ecd1cf" ON "__chart__active_users" ("date") WHERE "group" IS NULL`);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_3313d7288855ec105b5bbf6c21" ON "__chart__drive" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ceab80a6729f8e2e6f5b8a1a3d" ON "__chart__drive" ("date") WHERE "group" IS NULL`);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_dd907becf76104e4b656659e6b" ON "__chart__federation" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_eddfed8fb40305a04c6f941050" ON "__chart__federation" ("date") WHERE "group" IS NULL`);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_25a97c02003338124b2b75fdbc" ON "__chart__hashtag" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_53a3604b939e2b479eb2cfaac8" ON "__chart__hashtag" ("date") WHERE "group" IS NULL`);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_39ee857ab2f23493037c6b6631" ON "__chart__instance" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_8111b817b9818c04d7eb8475b1" ON "__chart__instance" ("date") WHERE "group" IS NULL`);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0a905b992fecd2b5c3fb98759e" ON "__chart__network" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_2082327b2699ce924fa654afc5" ON "__chart__network" ("date") WHERE "group" IS NULL`);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_f09d543e3acb16c5976bdb31fa" ON "__chart__notes" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e60c358aaced5aab8900a4af31" ON "__chart__notes" ("date") WHERE "group" IS NULL`);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_30bf67687f483ace115c5ca642" ON "__chart__per_user_drive" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a9a806d466b314f253a1a611c4" ON "__chart__per_user_drive" ("date") WHERE "group" IS NULL`);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_b77d4dd9562c3a899d9a286fcd" ON "__chart__per_user_following" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_dabbb38a51ab86ee3cab291326" ON "__chart__per_user_following" ("date") WHERE "group" IS NULL`);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_5048e9daccbbbc6d567bb142d3" ON "__chart__per_user_notes" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_583a157ed0cf0ed1b5ec2a833f" ON "__chart__per_user_notes" ("date") WHERE "group" IS NULL`);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_229a41ad465f9205f1f5703291" ON "__chart__per_user_reaction" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_3b7697a96f522d0478972e6d6f" ON "__chart__per_user_reaction" ("date") WHERE "group" IS NULL`);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_b14489029e4b3aaf4bba5fb524" ON "__chart__test_grouped" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_da522b4008a9f5d7743b87ad55" ON "__chart__test_grouped" ("date") WHERE "group" IS NULL`);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a0cd75442dd10d0643a17c4a49" ON "__chart__test_unique" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_16effb2e888f6763673b579f80" ON "__chart__test_unique" ("date") WHERE "group" IS NULL`);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a319e5dbf47e8a17497623beae" ON "__chart__test" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_dab383a36f3c9db4a0c9b02cf3" ON "__chart__test" ("date") WHERE "group" IS NULL`);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_337e9599f278bd7537fe30876f" ON "__chart__users" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_66feba81e1795d176d06c0b1e6" ON "__chart__users" ("date") WHERE "group" IS NULL`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async down(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_66feba81e1795d176d06c0b1e6"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_337e9599f278bd7537fe30876f"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_dab383a36f3c9db4a0c9b02cf3"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_a319e5dbf47e8a17497623beae"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_16effb2e888f6763673b579f80"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_a0cd75442dd10d0643a17c4a49"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_da522b4008a9f5d7743b87ad55"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_b14489029e4b3aaf4bba5fb524"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_3b7697a96f522d0478972e6d6f"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_229a41ad465f9205f1f5703291"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_583a157ed0cf0ed1b5ec2a833f"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_5048e9daccbbbc6d567bb142d3"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_dabbb38a51ab86ee3cab291326"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_b77d4dd9562c3a899d9a286fcd"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_a9a806d466b314f253a1a611c4"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_30bf67687f483ace115c5ca642"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_e60c358aaced5aab8900a4af31"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_f09d543e3acb16c5976bdb31fa"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_2082327b2699ce924fa654afc5"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_0a905b992fecd2b5c3fb98759e"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_8111b817b9818c04d7eb8475b1"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_39ee857ab2f23493037c6b6631"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_53a3604b939e2b479eb2cfaac8"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_25a97c02003338124b2b75fdbc"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_eddfed8fb40305a04c6f941050"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_dd907becf76104e4b656659e6b"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_ceab80a6729f8e2e6f5b8a1a3d"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_3313d7288855ec105b5bbf6c21"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_60c5c6e7e538c09aa274ecd1cf"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_9a3ed15a30ab7e3a37702e6e08"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_a9021cc2e1feb5f72d3db6e9f5"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_f22169eb10657bded6d875ac8f"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_c8cc87bd0f2f4487d17c651fbf"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_754499f9b2642336433769518d"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_315c779174fe8247ab324f036e"`);
 | 
			
		||||
        await queryRunner.query(`DROP INDEX "IDX_c5d46cbfda48b1c33ed852e21b"`);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_337e9599f278bd7537fe30876f" ON "__chart__users" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_ed9b95919c672a13008e9487ee" ON "__chart__users" ("group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_845254b3eaf708ae8a6cac3026" ON "__chart__users" ("date") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_a319e5dbf47e8a17497623beae" ON "__chart__test" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_d41cce6aee1a50bfc062038f9b" ON "__chart__test" ("group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_b070a906db04b44c67c6c2144d" ON "__chart__test" ("date") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_a0cd75442dd10d0643a17c4a49" ON "__chart__test_unique" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_bbfa573a8181018851ed0b6357" ON "__chart__test_unique" ("group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_437bab3c6061d90f6bb65fd2cc" ON "__chart__test_unique" ("date") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_b14489029e4b3aaf4bba5fb524" ON "__chart__test_grouped" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_234dff3c0b56a6150b95431ab9" ON "__chart__test_grouped" ("group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_0c641990ecf47d2545df4edb75" ON "__chart__test_grouped" ("date") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_229a41ad465f9205f1f5703291" ON "__chart__per_user_reaction" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_8cf3156fd7a6b15c43459c6e3b" ON "__chart__per_user_reaction" ("group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_f7bf4c62059764c2c2bb40fdab" ON "__chart__per_user_reaction" ("date") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_5048e9daccbbbc6d567bb142d3" ON "__chart__per_user_notes" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_55bf20f366979f2436de99206b" ON "__chart__per_user_notes" ("group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_84234bd1abb873f07329681c83" ON "__chart__per_user_notes" ("date") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_b77d4dd9562c3a899d9a286fcd" ON "__chart__per_user_following" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_4b3593098b6edc9c5afe36b18b" ON "__chart__per_user_following" ("group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_7af07790712aa3438ff6773f3b" ON "__chart__per_user_following" ("date") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_30bf67687f483ace115c5ca642" ON "__chart__per_user_drive" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_e496ca8096d28f6b9b509264dc" ON "__chart__per_user_drive" ("group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_5f86db6492274e07c1a3cdf286" ON "__chart__per_user_drive" ("date") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_f09d543e3acb16c5976bdb31fa" ON "__chart__notes" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_7036f2957151588b813185c794" ON "__chart__notes" ("group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_42eb716a37d381cdf566192b2b" ON "__chart__notes" ("date") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_0a905b992fecd2b5c3fb98759e" ON "__chart__network" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_7b5da130992ec9df96712d4290" ON "__chart__network" ("group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_a1efd3e0048a5f2793a47360dc" ON "__chart__network" ("date") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_39ee857ab2f23493037c6b6631" ON "__chart__instance" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_da8a46ba84ca1d8bb5a29bfb63" ON "__chart__instance" ("group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_6b8f34a1a64b06014b6fb66824" ON "__chart__instance" ("date") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_25a97c02003338124b2b75fdbc" ON "__chart__hashtag" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_99a7d2faaef84a6f728d714ad6" ON "__chart__hashtag" ("group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_07747a1038c05f532a718fe1de" ON "__chart__hashtag" ("date") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_dd907becf76104e4b656659e6b" ON "__chart__federation" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_76e87c7bfc5d925fcbba405d84" ON "__chart__federation" ("group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_36cb699c49580d4e6c2e6159f9" ON "__chart__federation" ("date") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_3313d7288855ec105b5bbf6c21" ON "__chart__drive" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_7a170f67425e62a8fabb76c872" ON "__chart__drive" ("group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_13565815f618a1ff53886c5b28" ON "__chart__drive" ("date") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_9a3ed15a30ab7e3a37702e6e08" ON "__chart__active_users" ("date", "group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_00ed5f86db1f7efafb1978bf21" ON "__chart__active_users" ("group") `);
 | 
			
		||||
        await queryRunner.query(`CREATE INDEX "IDX_0ad37b7ef50f4ddc84363d7ccc" ON "__chart__active_users" ("date") `);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								migration/1629024377804-deepl-integration.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								migration/1629024377804-deepl-integration.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
import {MigrationInterface, QueryRunner} from "typeorm";
 | 
			
		||||
 | 
			
		||||
export class deeplIntegration1629024377804 implements MigrationInterface {
 | 
			
		||||
    name = 'deeplIntegration1629024377804'
 | 
			
		||||
 | 
			
		||||
    public async up(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
        await queryRunner.query(`ALTER TABLE "meta" ADD "deeplAuthKey" character varying(128)`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async down(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "deeplAuthKey"`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								migration/1629288472000-fix-channel-userId.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								migration/1629288472000-fix-channel-userId.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
import {MigrationInterface, QueryRunner} from "typeorm";
 | 
			
		||||
 | 
			
		||||
export class fixChannelUserId1629288472000 implements MigrationInterface {
 | 
			
		||||
    name = 'fixChannelUserId1629288472000'
 | 
			
		||||
 | 
			
		||||
    public async up(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
        await queryRunner.query(`ALTER TABLE "channel" ALTER COLUMN "userId" DROP NOT NULL;`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async down(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
        await queryRunner.query(`ALTER TABLE "channel" ALTER COLUMN "userId" SET NOT NULL;`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								migration/1629512953000-user-is-deleted.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								migration/1629512953000-user-is-deleted.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
import {MigrationInterface, QueryRunner} from "typeorm";
 | 
			
		||||
 | 
			
		||||
export class isUserDeleted1629512953000 implements MigrationInterface {
 | 
			
		||||
    name = 'isUserDeleted1629512953000'
 | 
			
		||||
 | 
			
		||||
    public async up(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
        await queryRunner.query(`ALTER TABLE "user" ADD "isDeleted" boolean NOT NULL DEFAULT false`);
 | 
			
		||||
        await queryRunner.query(`COMMENT ON COLUMN "user"."isDeleted" IS 'Whether the User is deleted.'`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async down(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
        await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isDeleted"`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								migration/1629778475000-deepl-integration2.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								migration/1629778475000-deepl-integration2.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
import {MigrationInterface, QueryRunner} from "typeorm";
 | 
			
		||||
 | 
			
		||||
export class deeplIntegration21629778475000 implements MigrationInterface {
 | 
			
		||||
    name = 'deeplIntegration21629778475000'
 | 
			
		||||
 | 
			
		||||
    public async up(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
        await queryRunner.query(`ALTER TABLE "meta" ADD "deeplIsPro" boolean NOT NULL DEFAULT false`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async down(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "deeplIsPro"`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										130
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										130
									
								
								package.json
									
									
									
									
									
								
							@@ -1,7 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "misskey",
 | 
			
		||||
	"author": "syuilo <syuilotan@yahoo.co.jp>",
 | 
			
		||||
	"version": "12.84.0",
 | 
			
		||||
	"version": "12.89.2",
 | 
			
		||||
	"codename": "indigo",
 | 
			
		||||
	"repository": {
 | 
			
		||||
		"type": "git",
 | 
			
		||||
@@ -10,7 +10,8 @@
 | 
			
		||||
	"main": "./index.js",
 | 
			
		||||
	"private": true,
 | 
			
		||||
	"scripts": {
 | 
			
		||||
		"start": "node ./index.js",
 | 
			
		||||
		"start": "node --experimental-json-modules ./index.js",
 | 
			
		||||
		"start:test": "cross-env NODE_ENV=test node --experimental-json-modules ./index.js",
 | 
			
		||||
		"init": "npm run migrate",
 | 
			
		||||
		"ormconfig": "node ./built/ormconfig.js",
 | 
			
		||||
		"migrate": "ts-node ./node_modules/typeorm/cli.js migration:run",
 | 
			
		||||
@@ -26,6 +27,9 @@
 | 
			
		||||
		"clean": "gulp clean",
 | 
			
		||||
		"cleanall": "gulp cleanall",
 | 
			
		||||
		"lint": "tslint 'src/**/*.ts'",
 | 
			
		||||
		"cy:open": "cypress open",
 | 
			
		||||
		"cy:run": "cypress run",
 | 
			
		||||
		"e2e": "start-server-and-test start:test http://localhost cy:run",
 | 
			
		||||
		"test": "cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
 | 
			
		||||
		"format": "gulp format"
 | 
			
		||||
	},
 | 
			
		||||
@@ -34,7 +38,6 @@
 | 
			
		||||
		"lodash": "^4.17.21"
 | 
			
		||||
	},
 | 
			
		||||
	"dependencies": {
 | 
			
		||||
		"@babel/plugin-transform-runtime": "7.14.5",
 | 
			
		||||
		"@elastic/elasticsearch": "7.11.0",
 | 
			
		||||
		"@koa/cors": "3.1.0",
 | 
			
		||||
		"@koa/multer": "3.0.0",
 | 
			
		||||
@@ -44,7 +47,7 @@
 | 
			
		||||
		"@sinonjs/fake-timers": "7.1.2",
 | 
			
		||||
		"@syuilo/aiscript": "0.11.1",
 | 
			
		||||
		"@types/bcryptjs": "2.4.2",
 | 
			
		||||
		"@types/bull": "3.15.2",
 | 
			
		||||
		"@types/bull": "3.15.3",
 | 
			
		||||
		"@types/cbor": "6.0.0",
 | 
			
		||||
		"@types/dateformat": "3.0.1",
 | 
			
		||||
		"@types/escape-regexp": "0.0.0",
 | 
			
		||||
@@ -57,8 +60,8 @@
 | 
			
		||||
		"@types/jsonld": "1.5.6",
 | 
			
		||||
		"@types/katex": "0.11.1",
 | 
			
		||||
		"@types/koa": "2.13.4",
 | 
			
		||||
		"@types/koa-bodyparser": "4.3.2",
 | 
			
		||||
		"@types/koa-cors": "0.0.1",
 | 
			
		||||
		"@types/koa-bodyparser": "4.3.3",
 | 
			
		||||
		"@types/koa-cors": "0.0.2",
 | 
			
		||||
		"@types/koa-favicon": "2.0.21",
 | 
			
		||||
		"@types/koa-logger": "3.1.1",
 | 
			
		||||
		"@types/koa-mount": "4.0.0",
 | 
			
		||||
@@ -68,10 +71,10 @@
 | 
			
		||||
		"@types/koa__multer": "2.0.3",
 | 
			
		||||
		"@types/koa__router": "8.0.7",
 | 
			
		||||
		"@types/markdown-it": "12.0.3",
 | 
			
		||||
		"@types/matter-js": "0.17.3",
 | 
			
		||||
		"@types/matter-js": "0.17.5",
 | 
			
		||||
		"@types/mocha": "8.2.3",
 | 
			
		||||
		"@types/node": "16.3.3",
 | 
			
		||||
		"@types/node-fetch": "2.5.11",
 | 
			
		||||
		"@types/node": "16.6.2",
 | 
			
		||||
		"@types/node-fetch": "2.5.12",
 | 
			
		||||
		"@types/nodemailer": "6.4.4",
 | 
			
		||||
		"@types/nprogress": "0.2.0",
 | 
			
		||||
		"@types/oauth": "0.9.1",
 | 
			
		||||
@@ -86,9 +89,9 @@
 | 
			
		||||
		"@types/redis": "2.8.31",
 | 
			
		||||
		"@types/rename": "1.0.4",
 | 
			
		||||
		"@types/request-stats": "3.0.0",
 | 
			
		||||
		"@types/rimraf": "3.0.1",
 | 
			
		||||
		"@types/rimraf": "3.0.2",
 | 
			
		||||
		"@types/seedrandom": "2.4.28",
 | 
			
		||||
		"@types/sharp": "0.28.4",
 | 
			
		||||
		"@types/sharp": "0.28.5",
 | 
			
		||||
		"@types/sinonjs__fake-timers": "6.0.3",
 | 
			
		||||
		"@types/speakeasy": "2.0.6",
 | 
			
		||||
		"@types/throttle-debounce": "2.1.0",
 | 
			
		||||
@@ -98,40 +101,40 @@
 | 
			
		||||
		"@types/web-push": "3.3.2",
 | 
			
		||||
		"@types/webpack": "5.28.0",
 | 
			
		||||
		"@types/webpack-stream": "3.2.12",
 | 
			
		||||
		"@types/websocket": "1.0.3",
 | 
			
		||||
		"@types/ws": "7.4.6",
 | 
			
		||||
		"@typescript-eslint/parser": "4.28.3",
 | 
			
		||||
		"@vue/compiler-sfc": "3.2.0-beta.2",
 | 
			
		||||
		"@types/websocket": "1.0.4",
 | 
			
		||||
		"@types/ws": "7.4.7",
 | 
			
		||||
		"@typescript-eslint/parser": "4.29.2",
 | 
			
		||||
		"@vue/compiler-sfc": "3.2.4",
 | 
			
		||||
		"abort-controller": "3.0.0",
 | 
			
		||||
		"apexcharts": "3.27.2",
 | 
			
		||||
		"apexcharts": "3.27.3",
 | 
			
		||||
		"autobind-decorator": "2.4.0",
 | 
			
		||||
		"autosize": "4.0.4",
 | 
			
		||||
		"autwh": "0.1.0",
 | 
			
		||||
		"aws-sdk": "2.948.0",
 | 
			
		||||
		"aws-sdk": "2.966.0",
 | 
			
		||||
		"bcryptjs": "2.4.3",
 | 
			
		||||
		"blurhash": "1.1.3",
 | 
			
		||||
		"broadcast-channel": "3.7.0",
 | 
			
		||||
		"bull": "3.26.0",
 | 
			
		||||
		"blurhash": "1.1.4",
 | 
			
		||||
		"broadcast-channel": "4.2.0",
 | 
			
		||||
		"bull": "3.28.1",
 | 
			
		||||
		"cacheable-lookup": "6.0.0",
 | 
			
		||||
		"cafy": "15.2.1",
 | 
			
		||||
		"cbor": "7.0.6",
 | 
			
		||||
		"chalk": "4.1.1",
 | 
			
		||||
		"cbor": "8.0.0",
 | 
			
		||||
		"chalk": "4.1.2",
 | 
			
		||||
		"chart.js": "2.9.4",
 | 
			
		||||
		"cli-highlight": "2.1.11",
 | 
			
		||||
		"commander": "7.2.0",
 | 
			
		||||
		"concurrently": "6.2.0",
 | 
			
		||||
		"commander": "8.1.0",
 | 
			
		||||
		"compare-versions": "3.6.0",
 | 
			
		||||
		"concurrently": "6.2.1",
 | 
			
		||||
		"content-disposition": "0.5.3",
 | 
			
		||||
		"core-js": "3.15.2",
 | 
			
		||||
		"crc-32": "1.2.0",
 | 
			
		||||
		"css-loader": "6.0.0",
 | 
			
		||||
		"cssnano": "5.0.6",
 | 
			
		||||
		"css-loader": "6.2.0",
 | 
			
		||||
		"cssnano": "5.0.8",
 | 
			
		||||
		"dateformat": "4.5.1",
 | 
			
		||||
		"diskusage": "1.1.3",
 | 
			
		||||
		"escape-regexp": "0.0.1",
 | 
			
		||||
		"eslint": "7.30.0",
 | 
			
		||||
		"eslint-plugin-vue": "7.13.0",
 | 
			
		||||
		"eslint": "7.32.0",
 | 
			
		||||
		"eslint-plugin-vue": "7.16.0",
 | 
			
		||||
		"eventemitter3": "4.0.7",
 | 
			
		||||
		"feed": "4.2.2",
 | 
			
		||||
		"file-type": "16.5.1",
 | 
			
		||||
		"file-type": "16.5.3",
 | 
			
		||||
		"fluent-ffmpeg": "2.1.2",
 | 
			
		||||
		"glob": "7.1.7",
 | 
			
		||||
		"got": "11.8.2",
 | 
			
		||||
@@ -141,22 +144,18 @@
 | 
			
		||||
		"gulp-replace": "1.1.3",
 | 
			
		||||
		"gulp-terser": "2.0.1",
 | 
			
		||||
		"gulp-tslint": "8.1.4",
 | 
			
		||||
		"hard-source-webpack-plugin": "0.13.1",
 | 
			
		||||
		"html-minifier": "4.0.0",
 | 
			
		||||
		"http-proxy-agent": "4.0.1",
 | 
			
		||||
		"hpagent": "0.1.2",
 | 
			
		||||
		"http-signature": "1.3.5",
 | 
			
		||||
		"https-proxy-agent": "5.0.0",
 | 
			
		||||
		"idb-keyval": "5.0.6",
 | 
			
		||||
		"idb-keyval": "5.1.3",
 | 
			
		||||
		"insert-text-at-cursor": "0.3.0",
 | 
			
		||||
		"is-root": "2.1.0",
 | 
			
		||||
		"is-svg": "4.3.1",
 | 
			
		||||
		"js-yaml": "4.1.0",
 | 
			
		||||
		"jsdom": "16.6.0",
 | 
			
		||||
		"jsdom": "16.7.0",
 | 
			
		||||
		"json5": "2.2.0",
 | 
			
		||||
		"json5-loader": "4.0.1",
 | 
			
		||||
		"jsonld": "5.2.0",
 | 
			
		||||
		"jsrsasign": "8.0.20",
 | 
			
		||||
		"katex": "0.13.11",
 | 
			
		||||
		"katex": "0.13.13",
 | 
			
		||||
		"koa": "2.13.1",
 | 
			
		||||
		"koa-bodyparser": "4.3.0",
 | 
			
		||||
		"koa-favicon": "2.1.0",
 | 
			
		||||
@@ -167,30 +166,26 @@
 | 
			
		||||
		"koa-slow": "2.1.0",
 | 
			
		||||
		"koa-views": "7.0.1",
 | 
			
		||||
		"langmap": "0.0.16",
 | 
			
		||||
		"lookup-dns-cache": "2.1.0",
 | 
			
		||||
		"markdown-it": "12.1.0",
 | 
			
		||||
		"markdown-it": "12.2.0",
 | 
			
		||||
		"markdown-it-anchor": "7.1.0",
 | 
			
		||||
		"matter-js": "0.17.1",
 | 
			
		||||
		"mfm-js": "0.19.0",
 | 
			
		||||
		"misskey-js": "0.0.6",
 | 
			
		||||
		"mocha": "8.4.0",
 | 
			
		||||
		"moji": "0.5.1",
 | 
			
		||||
		"ms": "2.1.3",
 | 
			
		||||
		"multer": "1.4.2",
 | 
			
		||||
		"multer": "1.4.3",
 | 
			
		||||
		"nested-property": "4.0.0",
 | 
			
		||||
		"node-fetch": "2.6.1",
 | 
			
		||||
		"nodemailer": "6.6.3",
 | 
			
		||||
		"object-assign-deep": "0.4.0",
 | 
			
		||||
		"os-utils": "0.0.14",
 | 
			
		||||
		"parse5": "6.0.1",
 | 
			
		||||
		"pg": "8.6.0",
 | 
			
		||||
		"pg": "8.7.1",
 | 
			
		||||
		"portscanner": "2.2.0",
 | 
			
		||||
		"postcss": "8.3.5",
 | 
			
		||||
		"postcss": "8.3.6",
 | 
			
		||||
		"postcss-loader": "6.1.1",
 | 
			
		||||
		"prismjs": "1.24.1",
 | 
			
		||||
		"probe-image-size": "7.2.1",
 | 
			
		||||
		"promise-limit": "2.7.0",
 | 
			
		||||
		"promise-sequential": "1.1.1",
 | 
			
		||||
		"pug": "3.0.2",
 | 
			
		||||
		"punycode": "2.1.1",
 | 
			
		||||
		"pureimage": "0.3.2",
 | 
			
		||||
@@ -198,67 +193,62 @@
 | 
			
		||||
		"random-seed": "0.3.0",
 | 
			
		||||
		"ratelimiter": "3.4.1",
 | 
			
		||||
		"re2": "1.16.0",
 | 
			
		||||
		"reconnecting-websocket": "4.4.0",
 | 
			
		||||
		"redis": "3.1.2",
 | 
			
		||||
		"redis-lock": "0.1.4",
 | 
			
		||||
		"reflect-metadata": "0.1.13",
 | 
			
		||||
		"regenerator-runtime": "0.13.7",
 | 
			
		||||
		"rename": "1.0.4",
 | 
			
		||||
		"request-stats": "3.0.0",
 | 
			
		||||
		"require-all": "3.0.0",
 | 
			
		||||
		"rimraf": "3.0.2",
 | 
			
		||||
		"rndstr": "1.0.0",
 | 
			
		||||
		"s-age": "1.1.2",
 | 
			
		||||
		"sass": "1.35.2",
 | 
			
		||||
		"sass": "1.38.0",
 | 
			
		||||
		"sass-loader": "12.1.0",
 | 
			
		||||
		"seedrandom": "3.0.5",
 | 
			
		||||
		"sharp": "0.28.3",
 | 
			
		||||
		"sharp": "0.29.0",
 | 
			
		||||
		"speakeasy": "2.0.0",
 | 
			
		||||
		"stringz": "2.1.0",
 | 
			
		||||
		"style-loader": "3.1.0",
 | 
			
		||||
		"summaly": "2.4.0",
 | 
			
		||||
		"style-loader": "3.2.1",
 | 
			
		||||
		"summaly": "2.4.1",
 | 
			
		||||
		"syslog-pro": "1.0.0",
 | 
			
		||||
		"systeminformation": "5.7.7",
 | 
			
		||||
		"systeminformation": "5.8.0",
 | 
			
		||||
		"syuilo-password-strength": "0.0.1",
 | 
			
		||||
		"textarea-caret": "3.1.0",
 | 
			
		||||
		"three": "0.117.1",
 | 
			
		||||
		"throttle-debounce": "3.0.1",
 | 
			
		||||
		"tinycolor2": "1.4.2",
 | 
			
		||||
		"tmp": "0.2.1",
 | 
			
		||||
		"ts-loader": "9.2.3",
 | 
			
		||||
		"ts-node": "10.1.0",
 | 
			
		||||
		"tsc-alias": "1.3.7",
 | 
			
		||||
		"ts-loader": "9.2.5",
 | 
			
		||||
		"ts-node": "10.2.1",
 | 
			
		||||
		"tsc-alias": "1.3.9",
 | 
			
		||||
		"tsconfig-paths": "3.10.1",
 | 
			
		||||
		"tslint": "6.1.3",
 | 
			
		||||
		"tslint-sonarts": "1.9.0",
 | 
			
		||||
		"twemoji-parser": "13.1.0",
 | 
			
		||||
		"typeorm": "0.2.32",
 | 
			
		||||
		"typeorm": "0.2.37",
 | 
			
		||||
		"typescript": "4.3.5",
 | 
			
		||||
		"ulid": "2.3.0",
 | 
			
		||||
		"uuid": "8.3.2",
 | 
			
		||||
		"v-debounce": "0.1.2",
 | 
			
		||||
		"vanilla-tilt": "1.7.0",
 | 
			
		||||
		"vue": "3.2.0-beta.2",
 | 
			
		||||
		"vue-color": "2.8.1",
 | 
			
		||||
		"vue-json-pretty": "1.8.1",
 | 
			
		||||
		"vue-loader": "16.3.1",
 | 
			
		||||
		"vue": "3.2.4",
 | 
			
		||||
		"vue-loader": "16.5.0",
 | 
			
		||||
		"vue-prism-editor": "2.0.0-alpha.2",
 | 
			
		||||
		"vue-router": "4.0.5",
 | 
			
		||||
		"vue-style-loader": "4.1.3",
 | 
			
		||||
		"vue-svg-loader": "0.17.0-beta.2",
 | 
			
		||||
		"vuedraggable": "4.0.1",
 | 
			
		||||
		"web-push": "3.4.5",
 | 
			
		||||
		"webpack": "5.45.1",
 | 
			
		||||
		"webpack-cli": "4.7.2",
 | 
			
		||||
		"webpack": "5.51.0",
 | 
			
		||||
		"webpack-cli": "4.8.0",
 | 
			
		||||
		"websocket": "1.0.34",
 | 
			
		||||
		"ws": "7.5.3",
 | 
			
		||||
		"ws": "8.2.0",
 | 
			
		||||
		"xev": "2.0.1"
 | 
			
		||||
	},
 | 
			
		||||
	"devDependencies": {
 | 
			
		||||
		"@redocly/openapi-core": "1.0.0-beta.44",
 | 
			
		||||
		"@types/chai": "4.2.16",
 | 
			
		||||
		"@redocly/openapi-core": "1.0.0-beta.54",
 | 
			
		||||
		"@types/fluent-ffmpeg": "2.1.17",
 | 
			
		||||
		"chai": "4.3.4",
 | 
			
		||||
		"cross-env": "7.0.3"
 | 
			
		||||
		"cross-env": "7.0.3",
 | 
			
		||||
		"cypress": "8.3.0",
 | 
			
		||||
		"start-server-and-test": "1.13.1"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								src/@types/is-root.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								src/@types/is-root.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -1,7 +0,0 @@
 | 
			
		||||
declare module 'is-root' {
 | 
			
		||||
	function isRoot(): boolean;
 | 
			
		||||
 | 
			
		||||
	namespace isRoot {} // Hack
 | 
			
		||||
 | 
			
		||||
	export = isRoot;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								src/@types/lookup-dns-cache.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								src/@types/lookup-dns-cache.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -1,9 +0,0 @@
 | 
			
		||||
declare module 'lookup-dns-cache' {
 | 
			
		||||
	import { LookupOneOptions, LookupAllOptions, LookupOptions, LookupAddress } from 'dns';
 | 
			
		||||
 | 
			
		||||
	function lookup(hostname: string, family: number, callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void): void;
 | 
			
		||||
	function lookup(hostname: string, options: LookupOneOptions, callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void): void;
 | 
			
		||||
	function lookup(hostname: string, options: LookupAllOptions, callback: (err: NodeJS.ErrnoException | null, addresses: LookupAddress[]) => void): void;
 | 
			
		||||
	function lookup(hostname: string, options: LookupOptions, callback: (err: NodeJS.ErrnoException | null, address: string | LookupAddress[], family: number) => void): void;
 | 
			
		||||
	function lookup(hostname: string, callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void): void;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								src/argv.ts
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								src/argv.ts
									
									
									
									
									
								
							@@ -1,24 +1,23 @@
 | 
			
		||||
import { Command } from 'commander';
 | 
			
		||||
import config from '@/config';
 | 
			
		||||
import config from '@/config/index';
 | 
			
		||||
 | 
			
		||||
const program = new Command();
 | 
			
		||||
 | 
			
		||||
program
 | 
			
		||||
	.version(config.version)
 | 
			
		||||
	.option('--no-daemons', 'Disable daemon processes (for debbuging)')
 | 
			
		||||
	.option('--disable-clustering', 'Disable clustering')
 | 
			
		||||
	.option('--only-server', 'Run server only (without job queue processing)')
 | 
			
		||||
	.option('--only-queue', 'Pocessing job queue only (without server)')
 | 
			
		||||
	.option('--quiet', 'Suppress all logs')
 | 
			
		||||
	.option('--verbose', 'Enable all logs')
 | 
			
		||||
	.option('--with-log-time', 'Include timestamp for each logs')
 | 
			
		||||
	.option('--slow', 'Delay all requests (for debbuging)')
 | 
			
		||||
	.option('--color', 'This option is a dummy for some external program\'s (e.g. forever) issue.')
 | 
			
		||||
	.parse(process.argv);
 | 
			
		||||
program.version(config.version);
 | 
			
		||||
program.option('--no-daemons', 'Disable daemon processes (for debbuging)');
 | 
			
		||||
program.option('--disable-clustering', 'Disable clustering');
 | 
			
		||||
program.option('--only-server', 'Run server only (without job queue processing)');
 | 
			
		||||
program.option('--only-queue', 'Pocessing job queue only (without server)');
 | 
			
		||||
program.option('--quiet', 'Suppress all logs');
 | 
			
		||||
program.option('--verbose', 'Enable all logs');
 | 
			
		||||
program.option('--with-log-time', 'Include timestamp for each logs');
 | 
			
		||||
program.option('--slow', 'Delay all requests (for debbuging)');
 | 
			
		||||
program.option('--color', 'This option is a dummy for some external program\'s (e.g. forever) issue.');
 | 
			
		||||
program.parse(process.argv);
 | 
			
		||||
 | 
			
		||||
if (process.env.MK_ONLY_QUEUE) program.onlyQueue = true;
 | 
			
		||||
if (process.env.NODE_ENV === 'test') program.disableClustering = true;
 | 
			
		||||
if (process.env.NODE_ENV === 'test') program.quiet = true;
 | 
			
		||||
//if (process.env.NODE_ENV === 'test') program.quiet = true;
 | 
			
		||||
if (process.env.NODE_ENV === 'test') program.noDaemons = true;
 | 
			
		||||
 | 
			
		||||
export { program };
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import * as cluster from 'cluster';
 | 
			
		||||
import * as chalk from 'chalk';
 | 
			
		||||
import Xev from 'xev';
 | 
			
		||||
 | 
			
		||||
import Logger from '../services/logger';
 | 
			
		||||
import Logger from '@/services/logger';
 | 
			
		||||
import { program } from '../argv';
 | 
			
		||||
 | 
			
		||||
// for typeorm
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,25 @@
 | 
			
		||||
import * as fs from 'fs';
 | 
			
		||||
import { fileURLToPath } from 'url';
 | 
			
		||||
import { dirname } from 'path';
 | 
			
		||||
import * as os from 'os';
 | 
			
		||||
import * as cluster from 'cluster';
 | 
			
		||||
import * as chalk from 'chalk';
 | 
			
		||||
import * as portscanner from 'portscanner';
 | 
			
		||||
import * as isRoot from 'is-root';
 | 
			
		||||
import { getConnection } from 'typeorm';
 | 
			
		||||
 | 
			
		||||
import Logger from '../services/logger';
 | 
			
		||||
import Logger from '@/services/logger';
 | 
			
		||||
import loadConfig from '@/config/load';
 | 
			
		||||
import { Config } from '@/config/types';
 | 
			
		||||
import { lessThan } from '../prelude/array';
 | 
			
		||||
import { lessThan } from '@/prelude/array';
 | 
			
		||||
import { program } from '../argv';
 | 
			
		||||
import { showMachineInfo } from '@/misc/show-machine-info';
 | 
			
		||||
import { initDb } from '../db/postgre';
 | 
			
		||||
const meta = require('../meta.json');
 | 
			
		||||
 | 
			
		||||
//const _filename = fileURLToPath(import.meta.url);
 | 
			
		||||
const _filename = __filename;
 | 
			
		||||
const _dirname = dirname(_filename);
 | 
			
		||||
 | 
			
		||||
const meta = JSON.parse(fs.readFileSync(`${_dirname}/../meta.json`, 'utf-8'));
 | 
			
		||||
 | 
			
		||||
const logger = new Logger('core', 'cyan');
 | 
			
		||||
const bootLogger = logger.createSubLogger('boot', 'magenta', false);
 | 
			
		||||
@@ -39,6 +46,11 @@ function greet() {
 | 
			
		||||
	bootLogger.info(`Misskey v${meta.version}`, null, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isRoot() {
 | 
			
		||||
	// maybe process.getuid will be undefined under not POSIX environment (e.g. Windows)
 | 
			
		||||
	return process.getuid != null && process.getuid() === 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Init master process
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,8 @@
 | 
			
		||||
import { get, set } from '@client/scripts/idb-proxy';
 | 
			
		||||
import { reactive } from 'vue';
 | 
			
		||||
import { apiUrl } from '@client/config';
 | 
			
		||||
import { waiting } from '@client/os';
 | 
			
		||||
import { unisonReload } from '@client/scripts/unison-reload';
 | 
			
		||||
import { unisonReload, reloadChannel } from '@client/scripts/unison-reload';
 | 
			
		||||
 | 
			
		||||
// TODO: 他のタブと永続化されたstateを同期
 | 
			
		||||
 | 
			
		||||
@@ -10,6 +11,7 @@ type Account = {
 | 
			
		||||
	token: string;
 | 
			
		||||
	isModerator: boolean;
 | 
			
		||||
	isAdmin: boolean;
 | 
			
		||||
	isDeleted: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const data = localStorage.getItem('account');
 | 
			
		||||
@@ -17,22 +19,45 @@ const data = localStorage.getItem('account');
 | 
			
		||||
// TODO: 外部からはreadonlyに
 | 
			
		||||
export const $i = data ? reactive(JSON.parse(data) as Account) : null;
 | 
			
		||||
 | 
			
		||||
export function signout() {
 | 
			
		||||
export async function signout() {
 | 
			
		||||
	waiting();
 | 
			
		||||
	localStorage.removeItem('account');
 | 
			
		||||
 | 
			
		||||
	//#region Remove account
 | 
			
		||||
	const accounts = await getAccounts();
 | 
			
		||||
	accounts.splice(accounts.findIndex(x => x.id === $i.id), 1);
 | 
			
		||||
	set('accounts', accounts);
 | 
			
		||||
	//#endregion
 | 
			
		||||
 | 
			
		||||
	//#region Remove push notification registration
 | 
			
		||||
	try {
 | 
			
		||||
		const registration = await navigator.serviceWorker.ready;
 | 
			
		||||
		const push = await registration.pushManager.getSubscription();
 | 
			
		||||
		if (!push) return;
 | 
			
		||||
		await fetch(`${apiUrl}/sw/unregister`, {
 | 
			
		||||
			method: 'POST',
 | 
			
		||||
			body: JSON.stringify({
 | 
			
		||||
				i: $i.token,
 | 
			
		||||
				endpoint: push.endpoint,
 | 
			
		||||
			}),
 | 
			
		||||
		});
 | 
			
		||||
	} catch (e) {}
 | 
			
		||||
	//#endregion
 | 
			
		||||
 | 
			
		||||
	document.cookie = `igi=; path=/`;
 | 
			
		||||
	location.href = '/';
 | 
			
		||||
 | 
			
		||||
	if (accounts.length > 0) login(accounts[0].token);
 | 
			
		||||
	else unisonReload();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getAccounts() {
 | 
			
		||||
	const accountsData = localStorage.getItem('accounts');
 | 
			
		||||
	const accounts: { id: Account['id'], token: Account['token'] }[] = accountsData ? JSON.parse(accountsData) : [];
 | 
			
		||||
	return accounts;
 | 
			
		||||
export async function getAccounts(): Promise<{ id: Account['id'], token: Account['token'] }[]> {
 | 
			
		||||
	return (await get('accounts')) || [];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function addAccount(id: Account['id'], token: Account['token']) {
 | 
			
		||||
	const accounts = getAccounts();
 | 
			
		||||
export async function addAccount(id: Account['id'], token: Account['token']) {
 | 
			
		||||
	const accounts = await getAccounts();
 | 
			
		||||
	if (!accounts.some(x => x.id === id)) {
 | 
			
		||||
		localStorage.setItem('accounts', JSON.stringify(accounts.concat([{ id, token }])));
 | 
			
		||||
		await set('accounts', accounts.concat([{ id, token }]));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -47,7 +72,7 @@ function fetchAccount(token): Promise<Account> {
 | 
			
		||||
		})
 | 
			
		||||
		.then(res => {
 | 
			
		||||
			// When failed to authenticate user
 | 
			
		||||
			if (res.status >= 400 && res.status < 500) {
 | 
			
		||||
			if (res.status !== 200 && res.status < 500) {
 | 
			
		||||
				return signout();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@@ -69,15 +94,22 @@ export function updateAccount(data) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function refreshAccount() {
 | 
			
		||||
	fetchAccount($i.token).then(updateAccount);
 | 
			
		||||
	return fetchAccount($i.token).then(updateAccount);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function login(token: Account['token']) {
 | 
			
		||||
export async function login(token: Account['token'], redirect?: string) {
 | 
			
		||||
	waiting();
 | 
			
		||||
	if (_DEV_) console.log('logging as token ', token);
 | 
			
		||||
	const me = await fetchAccount(token);
 | 
			
		||||
	localStorage.setItem('account', JSON.stringify(me));
 | 
			
		||||
	addAccount(me.id, token);
 | 
			
		||||
	await addAccount(me.id, token);
 | 
			
		||||
 | 
			
		||||
	if (redirect) {
 | 
			
		||||
		reloadChannel.postMessage('reload');
 | 
			
		||||
		location.href = redirect;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	unisonReload();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,9 +10,9 @@
 | 
			
		||||
	</template>
 | 
			
		||||
	<div class="dpvffvvy _monolithic_">
 | 
			
		||||
		<div class="_section">
 | 
			
		||||
			<MkTextarea v-model:value="comment">
 | 
			
		||||
				<span>{{ $ts.details }}</span>
 | 
			
		||||
				<template #desc>{{ $ts.fillAbuseReportDescription }}</template>
 | 
			
		||||
			<MkTextarea v-model="comment">
 | 
			
		||||
				<template #label>{{ $ts.details }}</template>
 | 
			
		||||
				<template #caption>{{ $ts.fillAbuseReportDescription }}</template>
 | 
			
		||||
			</MkTextarea>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="_section">
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,7 @@ import { twemojiSvgBase } from '@/misc/twemoji-base';
 | 
			
		||||
import { getStaticImageUrl } from '@client/scripts/get-static-image-url';
 | 
			
		||||
import { acct } from '@client/filters/user';
 | 
			
		||||
import * as os from '@client/os';
 | 
			
		||||
import { instance } from '@client/instance';
 | 
			
		||||
 | 
			
		||||
type EmojiDef = {
 | 
			
		||||
	emoji: string;
 | 
			
		||||
@@ -75,6 +76,36 @@ for (const x of lib) {
 | 
			
		||||
 | 
			
		||||
emjdb.sort((a, b) => a.name.length - b.name.length);
 | 
			
		||||
 | 
			
		||||
//#region Construct Emoji DB
 | 
			
		||||
const customEmojis = instance.emojis;
 | 
			
		||||
const emojiDefinitions: EmojiDef[] = [];
 | 
			
		||||
 | 
			
		||||
for (const x of customEmojis) {
 | 
			
		||||
	emojiDefinitions.push({
 | 
			
		||||
		name: x.name,
 | 
			
		||||
		emoji: `:${x.name}:`,
 | 
			
		||||
		url: x.url,
 | 
			
		||||
		isCustomEmoji: true
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	if (x.aliases) {
 | 
			
		||||
		for (const alias of x.aliases) {
 | 
			
		||||
			emojiDefinitions.push({
 | 
			
		||||
				name: alias,
 | 
			
		||||
				aliasOf: x.name,
 | 
			
		||||
				emoji: `:${x.name}:`,
 | 
			
		||||
				url: x.url,
 | 
			
		||||
				isCustomEmoji: true
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
emojiDefinitions.sort((a, b) => a.name.length - b.name.length);
 | 
			
		||||
 | 
			
		||||
const emojiDb = markRaw(emojiDefinitions.concat(emjdb));
 | 
			
		||||
//#endregion
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
	props: {
 | 
			
		||||
		type: {
 | 
			
		||||
@@ -124,7 +155,6 @@ export default defineComponent({
 | 
			
		||||
			emojis: [],
 | 
			
		||||
			items: [],
 | 
			
		||||
			select: -1,
 | 
			
		||||
			emojiDb: [] as EmojiDef[]
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -144,36 +174,6 @@ export default defineComponent({
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.setPosition();
 | 
			
		||||
 | 
			
		||||
		//#region Construct Emoji DB
 | 
			
		||||
		const customEmojis = this.$instance.emojis;
 | 
			
		||||
		const emojiDefinitions: EmojiDef[] = [];
 | 
			
		||||
 | 
			
		||||
		for (const x of customEmojis) {
 | 
			
		||||
			emojiDefinitions.push({
 | 
			
		||||
				name: x.name,
 | 
			
		||||
				emoji: `:${x.name}:`,
 | 
			
		||||
				url: x.url,
 | 
			
		||||
				isCustomEmoji: true
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			if (x.aliases) {
 | 
			
		||||
				for (const alias of x.aliases) {
 | 
			
		||||
					emojiDefinitions.push({
 | 
			
		||||
						name: alias,
 | 
			
		||||
						aliasOf: x.name,
 | 
			
		||||
						emoji: `:${x.name}:`,
 | 
			
		||||
						url: x.url,
 | 
			
		||||
						isCustomEmoji: true
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		emojiDefinitions.sort((a, b) => a.name.length - b.name.length);
 | 
			
		||||
 | 
			
		||||
		this.emojiDb = markRaw(emojiDefinitions.concat(emjdb));
 | 
			
		||||
		//#endregion
 | 
			
		||||
 | 
			
		||||
		this.textarea.addEventListener('keydown', this.onKeydown);
 | 
			
		||||
 | 
			
		||||
		for (const el of Array.from(document.querySelectorAll('body *'))) {
 | 
			
		||||
@@ -203,6 +203,13 @@ export default defineComponent({
 | 
			
		||||
		complete(type, value) {
 | 
			
		||||
			this.$emit('done', { type, value });
 | 
			
		||||
			this.$emit('closed');
 | 
			
		||||
 | 
			
		||||
			if (type === 'emoji') {
 | 
			
		||||
				let recents = this.$store.state.recentlyUsedEmojis;
 | 
			
		||||
				recents = recents.filter((e: any) => e !== value);
 | 
			
		||||
				recents.unshift(value);
 | 
			
		||||
				this.$store.set('recentlyUsedEmojis', recents.splice(0, 32));
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		setPosition() {
 | 
			
		||||
@@ -281,29 +288,26 @@ export default defineComponent({
 | 
			
		||||
				}
 | 
			
		||||
			} else if (this.type == 'emoji') {
 | 
			
		||||
				if (this.q == null || this.q == '') {
 | 
			
		||||
					this.emojis = this.emojiDb.filter(x => x.isCustomEmoji && !x.aliasOf).sort((a, b) => {
 | 
			
		||||
						var textA = a.name.toUpperCase();
 | 
			
		||||
						var textB = b.name.toUpperCase();
 | 
			
		||||
						return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
 | 
			
		||||
					});
 | 
			
		||||
					// 最近使った絵文字をサジェスト
 | 
			
		||||
					this.emojis = this.$store.state.recentlyUsedEmojis.map(emoji => emojiDb.find(e => e.emoji == emoji)).filter(x => x != null);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				const matched = [];
 | 
			
		||||
				const max = 30;
 | 
			
		||||
 | 
			
		||||
				this.emojiDb.some(x => {
 | 
			
		||||
				emojiDb.some(x => {
 | 
			
		||||
					if (x.name.startsWith(this.q) && !x.aliasOf && !matched.some(y => y.emoji == x.emoji)) matched.push(x);
 | 
			
		||||
					return matched.length == max;
 | 
			
		||||
				});
 | 
			
		||||
				if (matched.length < max) {
 | 
			
		||||
					this.emojiDb.some(x => {
 | 
			
		||||
					emojiDb.some(x => {
 | 
			
		||||
						if (x.name.startsWith(this.q) && !matched.some(y => y.emoji == x.emoji)) matched.push(x);
 | 
			
		||||
						return matched.length == max;
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
				if (matched.length < max) {
 | 
			
		||||
					this.emojiDb.some(x => {
 | 
			
		||||
					emojiDb.some(x => {
 | 
			
		||||
						if (x.name.includes(this.q) && !matched.some(y => y.emoji == x.emoji)) matched.push(x);
 | 
			
		||||
						return matched.length == max;
 | 
			
		||||
					});
 | 
			
		||||
 
 | 
			
		||||
@@ -48,15 +48,7 @@ export default defineComponent({
 | 
			
		||||
	render() {
 | 
			
		||||
		if (this.items.length === 0) return;
 | 
			
		||||
 | 
			
		||||
		return h(this.$store.state.animation ? TransitionGroup : 'div', this.$store.state.animation ? {
 | 
			
		||||
			class: 'sqadhkmv' + (this.noGap ? ' noGap _block' : ''),
 | 
			
		||||
			name: 'list',
 | 
			
		||||
			tag: 'div',
 | 
			
		||||
			'data-direction': this.direction,
 | 
			
		||||
			'data-reversed': this.reversed ? 'true' : 'false',
 | 
			
		||||
		} : {
 | 
			
		||||
			class: 'sqadhkmv' + (this.noGap ? ' noGap _block' : ''),
 | 
			
		||||
		}, this.items.map((item, i) => {
 | 
			
		||||
		const renderChildren = () => this.items.map((item, i) => {
 | 
			
		||||
			const el = this.$slots.default({
 | 
			
		||||
				item: item
 | 
			
		||||
			})[0];
 | 
			
		||||
@@ -98,7 +90,19 @@ export default defineComponent({
 | 
			
		||||
					return el;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}));
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		return h(this.$store.state.animation ? TransitionGroup : 'div', this.$store.state.animation ? {
 | 
			
		||||
			class: 'sqadhkmv' + (this.noGap ? ' noGap' : ''),
 | 
			
		||||
			name: 'list',
 | 
			
		||||
			tag: 'div',
 | 
			
		||||
			'data-direction': this.direction,
 | 
			
		||||
			'data-reversed': this.reversed ? 'true' : 'false',
 | 
			
		||||
		} : {
 | 
			
		||||
			class: 'sqadhkmv' + (this.noGap ? ' noGap' : ''),
 | 
			
		||||
		}, {
 | 
			
		||||
			default: renderChildren
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -14,8 +14,8 @@
 | 
			
		||||
		</div>
 | 
			
		||||
		<header v-if="title"><Mfm :text="title"/></header>
 | 
			
		||||
		<div class="body" v-if="text"><Mfm :text="text"/></div>
 | 
			
		||||
		<MkInput v-if="input" v-model:value="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></MkInput>
 | 
			
		||||
		<MkSelect v-if="select" v-model:value="selectedValue" autofocus>
 | 
			
		||||
		<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></MkInput>
 | 
			
		||||
		<MkSelect v-if="select" v-model="selectedValue" autofocus>
 | 
			
		||||
			<template v-if="select.items">
 | 
			
		||||
				<option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
 | 
			
		||||
			</template>
 | 
			
		||||
 
 | 
			
		||||
@@ -114,7 +114,7 @@ export default defineComponent({
 | 
			
		||||
			if (this.selectMode) {
 | 
			
		||||
				this.$emit('chosen', this.file);
 | 
			
		||||
			} else {
 | 
			
		||||
				os.modalMenu(this.getMenu(), ev.currentTarget || ev.target);
 | 
			
		||||
				os.popupMenu(this.getMenu(), ev.currentTarget || ev.target);
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@
 | 
			
		||||
			<span class="separator" v-if="folder != null"><i class="fas fa-angle-right"></i></span>
 | 
			
		||||
			<span class="folder current" v-if="folder != null">{{ folder.name }}</span>
 | 
			
		||||
		</div>
 | 
			
		||||
		<button @click="showMenu" class="menu _button"><i class="fas fa-ellipsis-h"></i></button>
 | 
			
		||||
	</nav>
 | 
			
		||||
	<div class="main" :class="{ uploading: uploadings.length > 0, fetching }"
 | 
			
		||||
		ref="main"
 | 
			
		||||
@@ -46,7 +47,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { defineComponent } from 'vue';
 | 
			
		||||
import { defineComponent, markRaw } from 'vue';
 | 
			
		||||
import XNavFolder from './drive.nav-folder.vue';
 | 
			
		||||
import XFolder from './drive.folder.vue';
 | 
			
		||||
import XFile from './drive.file.vue';
 | 
			
		||||
@@ -139,7 +140,7 @@ export default defineComponent({
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this.connection = os.stream.useChannel('drive');
 | 
			
		||||
		this.connection = markRaw(os.stream.useChannel('drive'));
 | 
			
		||||
 | 
			
		||||
		this.connection.on('fileCreated', this.onStreamDriveFileCreated);
 | 
			
		||||
		this.connection.on('fileUpdated', this.onStreamDriveFileUpdated);
 | 
			
		||||
@@ -627,8 +628,12 @@ export default defineComponent({
 | 
			
		||||
			}];
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onContextmenu(e) {
 | 
			
		||||
			os.contextMenu(this.getMenu(), e);
 | 
			
		||||
		showMenu(ev) {
 | 
			
		||||
			os.popupMenu(this.getMenu(), ev.currentTarget || ev.target);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onContextmenu(ev) {
 | 
			
		||||
			os.contextMenu(this.getMenu(), ev);
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
@@ -641,7 +646,7 @@ export default defineComponent({
 | 
			
		||||
	height: 100%;
 | 
			
		||||
 | 
			
		||||
	> nav {
 | 
			
		||||
		display: block;
 | 
			
		||||
		display: flex;
 | 
			
		||||
		z-index: 2;
 | 
			
		||||
		width: 100%;
 | 
			
		||||
		padding: 0 8px;
 | 
			
		||||
@@ -696,6 +701,10 @@ export default defineComponent({
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .menu {
 | 
			
		||||
			margin-left: auto;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .main {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,17 @@
 | 
			
		||||
<template>
 | 
			
		||||
<MkModal ref="modal" :manual-showing="manualShowing" :src="src" :front="true" @click="$refs.modal.close()" @opening="opening" @close="$emit('close')" @closed="$emit('closed')">
 | 
			
		||||
	<MkEmojiPicker :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" @chosen="chosen" ref="picker"/>
 | 
			
		||||
</MkModal>
 | 
			
		||||
<MkPopup ref="popup" :manual-showing="manualShowing" :src="src" :front="true" @click="$refs.popup.close()" @opening="opening" @close="$emit('close')" @closed="$emit('closed')" #default="{point}">
 | 
			
		||||
	<MkEmojiPicker class="ryghynhb _popup _shadow" :class="{ pointer: point === 'top' }" :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" @chosen="chosen" ref="picker"/>
 | 
			
		||||
</MkPopup>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { defineComponent, markRaw } from 'vue';
 | 
			
		||||
import MkModal from '@client/components/ui/modal.vue';
 | 
			
		||||
import MkPopup from '@client/components/ui/popup.vue';
 | 
			
		||||
import MkEmojiPicker from '@client/components/emoji-picker.vue';
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
	components: {
 | 
			
		||||
		MkModal,
 | 
			
		||||
		MkPopup,
 | 
			
		||||
		MkEmojiPicker,
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -33,7 +33,7 @@ export default defineComponent({
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	emits: ['done', 'closed'],
 | 
			
		||||
	emits: ['done', 'close', 'closed'],
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
@@ -44,7 +44,7 @@ export default defineComponent({
 | 
			
		||||
	methods: {
 | 
			
		||||
		chosen(emoji: any) {
 | 
			
		||||
			this.$emit('done', emoji);
 | 
			
		||||
			this.$refs.modal.close();
 | 
			
		||||
			this.$refs.popup.close();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		opening() {
 | 
			
		||||
@@ -56,145 +56,20 @@ export default defineComponent({
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.omfetrab {
 | 
			
		||||
	$pad: 8px;
 | 
			
		||||
	--eachSize: 40px;
 | 
			
		||||
 | 
			
		||||
	display: flex;
 | 
			
		||||
	flex-direction: column;
 | 
			
		||||
	contain: content;
 | 
			
		||||
 | 
			
		||||
	&.big {
 | 
			
		||||
		--eachSize: 44px;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.w1 {
 | 
			
		||||
		width: calc((var(--eachSize) * 5) + (#{$pad} * 2));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.w2 {
 | 
			
		||||
		width: calc((var(--eachSize) * 6) + (#{$pad} * 2));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.w3 {
 | 
			
		||||
		width: calc((var(--eachSize) * 7) + (#{$pad} * 2));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.h1 {
 | 
			
		||||
		--height: calc((var(--eachSize) * 4) + (#{$pad} * 2));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.h2 {
 | 
			
		||||
		--height: calc((var(--eachSize) * 6) + (#{$pad} * 2));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.h3 {
 | 
			
		||||
		--height: calc((var(--eachSize) * 8) + (#{$pad} * 2));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .search {
 | 
			
		||||
		width: 100%;
 | 
			
		||||
		padding: 12px;
 | 
			
		||||
		box-sizing: border-box;
 | 
			
		||||
		font-size: 1em;
 | 
			
		||||
		outline: none;
 | 
			
		||||
		border: none;
 | 
			
		||||
		background: transparent;
 | 
			
		||||
		color: var(--fg);
 | 
			
		||||
 | 
			
		||||
		&:not(.filled) {
 | 
			
		||||
			order: 1;
 | 
			
		||||
			z-index: 2;
 | 
			
		||||
			box-shadow: 0px -1px 0 0px var(--divider);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .emojis {
 | 
			
		||||
		height: var(--height);
 | 
			
		||||
		overflow-y: auto;
 | 
			
		||||
		overflow-x: hidden;
 | 
			
		||||
 | 
			
		||||
		scrollbar-width: none;
 | 
			
		||||
 | 
			
		||||
		&::-webkit-scrollbar {
 | 
			
		||||
			display: none;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .index {
 | 
			
		||||
			min-height: var(--height);
 | 
			
		||||
			position: relative;
 | 
			
		||||
			border-bottom: solid 0.5px var(--divider);
 | 
			
		||||
				
 | 
			
		||||
			> .arrow {
 | 
			
		||||
				position: absolute;
 | 
			
		||||
				bottom: 0;
 | 
			
		||||
				left: 0;
 | 
			
		||||
				width: 100%;
 | 
			
		||||
				padding: 16px 0;
 | 
			
		||||
				text-align: center;
 | 
			
		||||
				opacity: 0.5;
 | 
			
		||||
				pointer-events: none;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		section {
 | 
			
		||||
			> header {
 | 
			
		||||
				position: sticky;
 | 
			
		||||
				top: 0;
 | 
			
		||||
				left: 0;
 | 
			
		||||
				z-index: 1;
 | 
			
		||||
				padding: 8px;
 | 
			
		||||
				font-size: 12px;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			> div {
 | 
			
		||||
				padding: $pad;
 | 
			
		||||
 | 
			
		||||
				> button {
 | 
			
		||||
					position: relative;
 | 
			
		||||
					padding: 0;
 | 
			
		||||
					width: var(--eachSize);
 | 
			
		||||
					height: var(--eachSize);
 | 
			
		||||
					border-radius: 4px;
 | 
			
		||||
 | 
			
		||||
					&:focus {
 | 
			
		||||
						outline: solid 2px var(--focus);
 | 
			
		||||
						z-index: 1;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					&:hover {
 | 
			
		||||
						background: rgba(0, 0, 0, 0.05);
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					&:active {
 | 
			
		||||
						background: var(--accent);
 | 
			
		||||
						box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15);
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					> * {
 | 
			
		||||
						font-size: 24px;
 | 
			
		||||
						height: 1.25em;
 | 
			
		||||
						vertical-align: -.25em;
 | 
			
		||||
						pointer-events: none;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			&.result {
 | 
			
		||||
				border-bottom: solid 0.5px var(--divider);
 | 
			
		||||
 | 
			
		||||
				&:empty {
 | 
			
		||||
					display: none;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			&.unicode {
 | 
			
		||||
				min-height: 384px;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			&.custom {
 | 
			
		||||
				min-height: 64px;
 | 
			
		||||
			}
 | 
			
		||||
.ryghynhb {
 | 
			
		||||
	&.pointer {
 | 
			
		||||
		&:before {
 | 
			
		||||
			--size: 8px;
 | 
			
		||||
			content: '';
 | 
			
		||||
			display: block;
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			top: calc(0px - (var(--size) * 2));
 | 
			
		||||
			left: 0;
 | 
			
		||||
			right: 0;
 | 
			
		||||
			width: 0;
 | 
			
		||||
			margin: auto;
 | 
			
		||||
			border: solid var(--size) transparent;
 | 
			
		||||
			border-bottom-color: var(--popup);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="omfetrab _popup" :class="['w' + width, 'h' + height, { big }]">
 | 
			
		||||
<div class="omfetrab" :class="['w' + width, 'h' + height, { big }]">
 | 
			
		||||
	<input ref="search" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" v-model.trim="q" :placeholder="$ts.search" @paste.stop="paste" @keyup.enter="done()">
 | 
			
		||||
	<div class="emojis" ref="emojis">
 | 
			
		||||
		<section class="result">
 | 
			
		||||
@@ -346,7 +346,6 @@ export default defineComponent({
 | 
			
		||||
 | 
			
		||||
	display: flex;
 | 
			
		||||
	flex-direction: column;
 | 
			
		||||
	contain: content;
 | 
			
		||||
 | 
			
		||||
	&.big {
 | 
			
		||||
		--eachSize: 44px;
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { defineComponent } from 'vue';
 | 
			
		||||
import { defineComponent, markRaw } from 'vue';
 | 
			
		||||
import * as os from '@client/os';
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
@@ -71,7 +71,7 @@ export default defineComponent({
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.connection = os.stream.useChannel('main');
 | 
			
		||||
		this.connection = markRaw(os.stream.useChannel('main'));
 | 
			
		||||
 | 
			
		||||
		this.connection.on('follow', this.onFollowChange);
 | 
			
		||||
		this.connection.on('unfollow', this.onFollowChange);
 | 
			
		||||
 
 | 
			
		||||
@@ -9,14 +9,14 @@
 | 
			
		||||
 | 
			
		||||
	<form class="_monolithic_" @submit.prevent="onSubmit" v-if="$instance.enableEmail">
 | 
			
		||||
		<div class="_section">
 | 
			
		||||
			<MkInput v-model:value="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required>
 | 
			
		||||
				<span>{{ $ts.username }}</span>
 | 
			
		||||
			<MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required>
 | 
			
		||||
				<template #label>{{ $ts.username }}</template>
 | 
			
		||||
				<template #prefix>@</template>
 | 
			
		||||
			</MkInput>
 | 
			
		||||
 | 
			
		||||
			<MkInput v-model:value="email" type="email" spellcheck="false" required>
 | 
			
		||||
				<span>{{ $ts.emailAddress }}</span>
 | 
			
		||||
				<template #desc>{{ $ts._forgotPassword.enterEmail }}</template>
 | 
			
		||||
			<MkInput v-model="email" type="email" spellcheck="false" required>
 | 
			
		||||
				<template #label>{{ $ts.emailAddress }}</template>
 | 
			
		||||
				<template #caption>{{ $ts._forgotPassword.enterEmail }}</template>
 | 
			
		||||
			</MkInput>
 | 
			
		||||
 | 
			
		||||
			<MkButton type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ $ts.send }}</MkButton>
 | 
			
		||||
 
 | 
			
		||||
@@ -32,8 +32,8 @@
 | 
			
		||||
	margin: -8px calc(var(--formXPadding) * -1) 0 calc(var(--formXPadding) * -1);
 | 
			
		||||
	padding: 8px calc(var(--formContentHMargin) + var(--formXPadding)) 8px calc(var(--formContentHMargin) + var(--formXPadding));
 | 
			
		||||
	background: var(--X17);
 | 
			
		||||
	-webkit-backdrop-filter: blur(10px);
 | 
			
		||||
	backdrop-filter: blur(10px);
 | 
			
		||||
	-webkit-backdrop-filter: var(--blur, blur(10px));
 | 
			
		||||
	backdrop-filter: var(--blur, blur(10px));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
._themeChanging_ ._formLabel {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="yxspomdl" :class="{ inline, colored }">
 | 
			
		||||
<div class="yxspomdl" :class="{ inline, colored, mini }">
 | 
			
		||||
	<div class="ring"></div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -18,7 +18,12 @@ export default defineComponent({
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: true
 | 
			
		||||
		}
 | 
			
		||||
		},
 | 
			
		||||
		mini: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
@@ -38,6 +43,8 @@ export default defineComponent({
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	cursor: wait;
 | 
			
		||||
 | 
			
		||||
	--size: 48px;
 | 
			
		||||
 | 
			
		||||
	&.colored {
 | 
			
		||||
		color: var(--accent);
 | 
			
		||||
	}
 | 
			
		||||
@@ -45,19 +52,12 @@ export default defineComponent({
 | 
			
		||||
	&.inline {
 | 
			
		||||
		display: inline;
 | 
			
		||||
		padding: 0;
 | 
			
		||||
		--size: 32px;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		> .ring:after {
 | 
			
		||||
			width: 32px;
 | 
			
		||||
			height: 32px;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .ring {
 | 
			
		||||
			&:before,
 | 
			
		||||
			&:after {
 | 
			
		||||
				width: 32px;
 | 
			
		||||
				height: 32px;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	&.mini {
 | 
			
		||||
		padding: 16px;
 | 
			
		||||
		--size: 32px;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .ring {
 | 
			
		||||
@@ -70,8 +70,8 @@ export default defineComponent({
 | 
			
		||||
			content: " ";
 | 
			
		||||
			display: block;
 | 
			
		||||
			box-sizing: border-box;
 | 
			
		||||
			width: 48px;
 | 
			
		||||
			height: 48px;
 | 
			
		||||
			width: var(--size);
 | 
			
		||||
			height: var(--size);
 | 
			
		||||
			border-radius: 50%;
 | 
			
		||||
			border: solid 4px;
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -117,6 +117,11 @@ export default defineComponent({
 | 
			
		||||
	75% { transform: scale3d(1.05, 0.95, 1); }
 | 
			
		||||
	to { transform: scale3d(1, 1, 1); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes mfm-rainbow {
 | 
			
		||||
	0% { filter: hue-rotate(0deg) contrast(150%) saturate(150%); }
 | 
			
		||||
	100% { filter: hue-rotate(360deg) contrast(150%) saturate(150%); }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="zbcjwnqg" style="margin-top: -8px;">
 | 
			
		||||
	<div class="selects" style="display: flex;">
 | 
			
		||||
		<MkSelect v-model:value="chartSrc" style="margin: 0; flex: 1;">
 | 
			
		||||
		<MkSelect v-model="chartSrc" style="margin: 0; flex: 1;">
 | 
			
		||||
			<optgroup :label="$ts.federation">
 | 
			
		||||
				<option value="federation-instances">{{ $ts._charts.federationInstancesIncDec }}</option>
 | 
			
		||||
				<option value="federation-instances-total">{{ $ts._charts.federationInstancesTotal }}</option>
 | 
			
		||||
@@ -24,7 +24,7 @@
 | 
			
		||||
				<option value="drive-total">{{ $ts._charts.storageUsageTotal }}</option>
 | 
			
		||||
			</optgroup>
 | 
			
		||||
		</MkSelect>
 | 
			
		||||
		<MkSelect v-model:value="chartSpan" style="margin: 0;">
 | 
			
		||||
		<MkSelect v-model="chartSpan" style="margin: 0;">
 | 
			
		||||
			<option value="hour">{{ $ts.perHour }}</option>
 | 
			
		||||
			<option value="day">{{ $ts.perDay }}</option>
 | 
			
		||||
		</MkSelect>
 | 
			
		||||
 
 | 
			
		||||
@@ -165,6 +165,10 @@ export default defineComponent({
 | 
			
		||||
								class: '_mfm_blur_',
 | 
			
		||||
							}, genEl(token.children));
 | 
			
		||||
						}
 | 
			
		||||
						case 'rainbow': {
 | 
			
		||||
							style = this.$store.state.animatedMfm ? 'animation: mfm-rainbow 1s linear infinite;' : '';
 | 
			
		||||
							break;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					if (style == null) {
 | 
			
		||||
						return h('span', {}, ['[', token.props.name, ...genEl(token.children), ']']);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,10 @@
 | 
			
		||||
<template>
 | 
			
		||||
<MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')">
 | 
			
		||||
	<div class="hrmcaedk _popup _narrow_" :style="{ width: `${width}px`, height: (height ? `min(${height}px, 100%)` : '100%') }">
 | 
			
		||||
	<div class="hrmcaedk _window _narrow_" :style="{ width: `${width}px`, height: (height ? `min(${height}px, 100%)` : '100%') }">
 | 
			
		||||
		<div class="header" @contextmenu="onContextmenu">
 | 
			
		||||
			<button class="_button" @click="back()" v-if="history.length > 0"><i class="fas fa-chevron-left"></i></button>
 | 
			
		||||
			<button class="_button" style="pointer-events: none;" v-else><!-- マージンのバランスを取るためのダミー --></button>
 | 
			
		||||
			<span class="title">
 | 
			
		||||
				<XHeader :info="pageInfo" :with-back="false"/>
 | 
			
		||||
				<XHeader :info="pageInfo" :back-button="history.length > 0" @back="back()" :close-button="true" @close="$refs.modal.close()"/>
 | 
			
		||||
			</span>
 | 
			
		||||
			<button class="_button" @click="$refs.modal.close()"><i class="fas fa-times"></i></button>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="body _flat_">
 | 
			
		||||
			<keep-alive>
 | 
			
		||||
@@ -177,35 +174,19 @@ export default defineComponent({
 | 
			
		||||
		flex-shrink: 0;
 | 
			
		||||
		box-shadow: 0px 1px var(--divider);
 | 
			
		||||
 | 
			
		||||
		> button {
 | 
			
		||||
			height: $height;
 | 
			
		||||
			width: $height;
 | 
			
		||||
 | 
			
		||||
			@media (max-width: 500px) {
 | 
			
		||||
				height: $height-narrow;
 | 
			
		||||
				width: $height-narrow;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .title {
 | 
			
		||||
			flex: 1;
 | 
			
		||||
			line-height: $height;
 | 
			
		||||
			padding-left: 32px;
 | 
			
		||||
			height: $height;
 | 
			
		||||
			font-weight: bold;
 | 
			
		||||
			white-space: nowrap;
 | 
			
		||||
			overflow: hidden;
 | 
			
		||||
			text-overflow: ellipsis;
 | 
			
		||||
			pointer-events: none;
 | 
			
		||||
 | 
			
		||||
			@media (max-width: 500px) {
 | 
			
		||||
				line-height: $height-narrow;
 | 
			
		||||
				height: $height-narrow;
 | 
			
		||||
				padding-left: 16px;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> button + .title {
 | 
			
		||||
			padding-left: 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .body {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div
 | 
			
		||||
	class="note _block"
 | 
			
		||||
	class="lxwezrsl _block"
 | 
			
		||||
	v-if="!muted"
 | 
			
		||||
	v-show="!isDeleted"
 | 
			
		||||
	:tabindex="!isDeleted ? '-1' : null"
 | 
			
		||||
@@ -67,6 +67,13 @@
 | 
			
		||||
						<MkA class="reply" v-if="appearNote.replyId" :to="`/notes/${appearNote.replyId}`"><i class="fas fa-reply"></i></MkA>
 | 
			
		||||
						<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
 | 
			
		||||
						<a class="rp" v-if="appearNote.renote != null">RN:</a>
 | 
			
		||||
						<div class="translation" v-if="translating || translation">
 | 
			
		||||
							<MkLoading v-if="translating" mini/>
 | 
			
		||||
							<div class="translated" v-else>
 | 
			
		||||
								<b>{{ $t('translatedFrom', { x: translation.sourceLang }) }}:</b>
 | 
			
		||||
								{{ translation.text }}
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="files" v-if="appearNote.files.length > 0">
 | 
			
		||||
						<XMediaList :media-list="appearNote.files"/>
 | 
			
		||||
@@ -79,8 +86,8 @@
 | 
			
		||||
			</div>
 | 
			
		||||
			<footer class="footer">
 | 
			
		||||
				<div class="info">
 | 
			
		||||
					<span class="mobile" v-if="note.viaMobile"><i class="fas fa-mobile-alt"></i></span>
 | 
			
		||||
					<MkTime class="created-at" :time="note.createdAt" mode="detail"/>
 | 
			
		||||
					<span class="mobile" v-if="appearNote.viaMobile"><i class="fas fa-mobile-alt"></i></span>
 | 
			
		||||
					<MkTime class="created-at" :time="appearNote.createdAt" mode="detail"/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<XReactionsViewer :note="appearNote" ref="reactionsViewer"/>
 | 
			
		||||
				<button @click="reply()" class="button _button">
 | 
			
		||||
@@ -178,6 +185,8 @@ export default defineComponent({
 | 
			
		||||
			showContent: false,
 | 
			
		||||
			isDeleted: false,
 | 
			
		||||
			muted: false,
 | 
			
		||||
			translation: null,
 | 
			
		||||
			translating: false,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -454,7 +463,7 @@ export default defineComponent({
 | 
			
		||||
		renote(viaKeyboard = false) {
 | 
			
		||||
			pleaseLogin();
 | 
			
		||||
			this.blur();
 | 
			
		||||
			os.modalMenu([{
 | 
			
		||||
			os.popupMenu([{
 | 
			
		||||
				text: this.$ts.renote,
 | 
			
		||||
				icon: 'fas fa-retweet',
 | 
			
		||||
				action: () => {
 | 
			
		||||
@@ -619,6 +628,11 @@ export default defineComponent({
 | 
			
		||||
					text: this.$ts.share,
 | 
			
		||||
					action: this.share
 | 
			
		||||
				},
 | 
			
		||||
				this.$instance.translatorAvailable ? {
 | 
			
		||||
					icon: 'fas fa-language',
 | 
			
		||||
					text: this.$ts.translate,
 | 
			
		||||
					action: this.translate
 | 
			
		||||
				} : undefined,
 | 
			
		||||
				null,
 | 
			
		||||
				statePromise.then(state => state.isFavorited ? {
 | 
			
		||||
					icon: 'fas fa-star',
 | 
			
		||||
@@ -743,14 +757,14 @@ export default defineComponent({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		menu(viaKeyboard = false) {
 | 
			
		||||
			os.modalMenu(this.getMenu(), this.$refs.menuButton, {
 | 
			
		||||
			os.popupMenu(this.getMenu(), this.$refs.menuButton, {
 | 
			
		||||
				viaKeyboard
 | 
			
		||||
			}).then(this.focus);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		showRenoteMenu(viaKeyboard = false) {
 | 
			
		||||
			if (!this.isMyRenote) return;
 | 
			
		||||
			os.modalMenu([{
 | 
			
		||||
			os.popupMenu([{
 | 
			
		||||
				text: this.$ts.unrenote,
 | 
			
		||||
				icon: 'fas fa-trash-alt',
 | 
			
		||||
				danger: true,
 | 
			
		||||
@@ -794,7 +808,7 @@ export default defineComponent({
 | 
			
		||||
 | 
			
		||||
		async clip() {
 | 
			
		||||
			const clips = await os.api('clips/list');
 | 
			
		||||
			os.modalMenu([{
 | 
			
		||||
			os.popupMenu([{
 | 
			
		||||
				icon: 'fas fa-plus',
 | 
			
		||||
				text: this.$ts.createNew,
 | 
			
		||||
				action: async () => {
 | 
			
		||||
@@ -852,6 +866,17 @@ export default defineComponent({
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		async translate() {
 | 
			
		||||
			if (this.translation != null) return;
 | 
			
		||||
			this.translating = true;
 | 
			
		||||
			const res = await os.api('notes/translate', {
 | 
			
		||||
				noteId: this.appearNote.id,
 | 
			
		||||
				targetLang: localStorage.getItem('lang') || navigator.language,
 | 
			
		||||
			});
 | 
			
		||||
			this.translating = false;
 | 
			
		||||
			this.translation = res;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		focus() {
 | 
			
		||||
			this.$el.focus();
 | 
			
		||||
		},
 | 
			
		||||
@@ -874,7 +899,7 @@ export default defineComponent({
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.note {
 | 
			
		||||
.lxwezrsl {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	transition: box-shadow 0.1s ease;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
@@ -1050,6 +1075,13 @@ export default defineComponent({
 | 
			
		||||
							font-style: oblique;
 | 
			
		||||
							color: var(--renote);
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						> .translation {
 | 
			
		||||
							border: solid 0.5px var(--divider);
 | 
			
		||||
							border-radius: var(--radius);
 | 
			
		||||
							padding: 12px;
 | 
			
		||||
							margin-top: 8px;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					> .url-preview {
 | 
			
		||||
 
 | 
			
		||||
@@ -24,8 +24,8 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { defineComponent } from 'vue';
 | 
			
		||||
import notePage from '../filters/note';
 | 
			
		||||
import { userPage } from '../filters/user';
 | 
			
		||||
import notePage from '@client/filters/note';
 | 
			
		||||
import { userPage } from '@client/filters/user';
 | 
			
		||||
import * as os from '@client/os';
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
 
 | 
			
		||||
@@ -51,6 +51,13 @@
 | 
			
		||||
						<MkA class="reply" v-if="appearNote.replyId" :to="`/notes/${appearNote.replyId}`"><i class="fas fa-reply"></i></MkA>
 | 
			
		||||
						<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
 | 
			
		||||
						<a class="rp" v-if="appearNote.renote != null">RN:</a>
 | 
			
		||||
						<div class="translation" v-if="translating || translation">
 | 
			
		||||
							<MkLoading v-if="translating" mini/>
 | 
			
		||||
							<div class="translated" v-else>
 | 
			
		||||
								<b>{{ $t('translatedFrom', { x: translation.sourceLang }) }}:</b>
 | 
			
		||||
								{{ translation.text }}
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="files" v-if="appearNote.files.length > 0">
 | 
			
		||||
						<XMediaList :media-list="appearNote.files"/>
 | 
			
		||||
@@ -164,6 +171,8 @@ export default defineComponent({
 | 
			
		||||
			collapsed: false,
 | 
			
		||||
			isDeleted: false,
 | 
			
		||||
			muted: false,
 | 
			
		||||
			translation: null,
 | 
			
		||||
			translating: false,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -429,7 +438,7 @@ export default defineComponent({
 | 
			
		||||
		renote(viaKeyboard = false) {
 | 
			
		||||
			pleaseLogin();
 | 
			
		||||
			this.blur();
 | 
			
		||||
			os.modalMenu([{
 | 
			
		||||
			os.popupMenu([{
 | 
			
		||||
				text: this.$ts.renote,
 | 
			
		||||
				icon: 'fas fa-retweet',
 | 
			
		||||
				action: () => {
 | 
			
		||||
@@ -594,6 +603,11 @@ export default defineComponent({
 | 
			
		||||
					text: this.$ts.share,
 | 
			
		||||
					action: this.share
 | 
			
		||||
				},
 | 
			
		||||
				this.$instance.translatorAvailable ? {
 | 
			
		||||
					icon: 'fas fa-language',
 | 
			
		||||
					text: this.$ts.translate,
 | 
			
		||||
					action: this.translate
 | 
			
		||||
				} : undefined,
 | 
			
		||||
				null,
 | 
			
		||||
				statePromise.then(state => state.isFavorited ? {
 | 
			
		||||
					icon: 'fas fa-star',
 | 
			
		||||
@@ -718,14 +732,14 @@ export default defineComponent({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		menu(viaKeyboard = false) {
 | 
			
		||||
			os.modalMenu(this.getMenu(), this.$refs.menuButton, {
 | 
			
		||||
			os.popupMenu(this.getMenu(), this.$refs.menuButton, {
 | 
			
		||||
				viaKeyboard
 | 
			
		||||
			}).then(this.focus);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		showRenoteMenu(viaKeyboard = false) {
 | 
			
		||||
			if (!this.isMyRenote) return;
 | 
			
		||||
			os.modalMenu([{
 | 
			
		||||
			os.popupMenu([{
 | 
			
		||||
				text: this.$ts.unrenote,
 | 
			
		||||
				icon: 'fas fa-trash-alt',
 | 
			
		||||
				danger: true,
 | 
			
		||||
@@ -769,7 +783,7 @@ export default defineComponent({
 | 
			
		||||
 | 
			
		||||
		async clip() {
 | 
			
		||||
			const clips = await os.api('clips/list');
 | 
			
		||||
			os.modalMenu([{
 | 
			
		||||
			os.popupMenu([{
 | 
			
		||||
				icon: 'fas fa-plus',
 | 
			
		||||
				text: this.$ts.createNew,
 | 
			
		||||
				action: async () => {
 | 
			
		||||
@@ -827,6 +841,17 @@ export default defineComponent({
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		async translate() {
 | 
			
		||||
			if (this.translation != null) return;
 | 
			
		||||
			this.translating = true;
 | 
			
		||||
			const res = await os.api('notes/translate', {
 | 
			
		||||
				noteId: this.appearNote.id,
 | 
			
		||||
				targetLang: localStorage.getItem('lang') || navigator.language,
 | 
			
		||||
			});
 | 
			
		||||
			this.translating = false;
 | 
			
		||||
			this.translation = res;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		focus() {
 | 
			
		||||
			this.$el.focus();
 | 
			
		||||
		},
 | 
			
		||||
@@ -1053,6 +1078,13 @@ export default defineComponent({
 | 
			
		||||
							font-style: oblique;
 | 
			
		||||
							color: var(--renote);
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						> .translation {
 | 
			
		||||
							border: solid 0.5px var(--divider);
 | 
			
		||||
							border-radius: var(--radius);
 | 
			
		||||
							padding: 12px;
 | 
			
		||||
							margin-top: 8px;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					> .url-preview {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
		<div>{{ $ts.noNotes }}</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div v-else>
 | 
			
		||||
	<div v-else class="giivymft" :class="{ noGap }">
 | 
			
		||||
		<div v-show="more && reversed" style="margin-bottom: var(--margin);">
 | 
			
		||||
			<MkButton style="margin: 0 auto;" @click="fetchMoreFeature" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
 | 
			
		||||
				<template v-if="!moreFetching">{{ $ts.loadMore }}</template>
 | 
			
		||||
@@ -17,8 +17,8 @@
 | 
			
		||||
			</MkButton>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<XList ref="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed" :no-gap="noGap" :ad="true">
 | 
			
		||||
			<XNote :note="note" class="_block" @update:note="updated(note, $event)" :key="note._featuredId_ || note._prId_ || note.id"/>
 | 
			
		||||
		<XList ref="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed" :no-gap="noGap" :ad="true" class="notes">
 | 
			
		||||
			<XNote class="qtqtichx" :note="note" @update:note="updated(note, $event)" :key="note._featuredId_ || note._prId_ || note.id"/>
 | 
			
		||||
		</XList>
 | 
			
		||||
 | 
			
		||||
		<div v-show="more && !reversed" style="margin-top: var(--margin);">
 | 
			
		||||
@@ -108,4 +108,23 @@ export default defineComponent({
 | 
			
		||||
.fade-leave-to {
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.giivymft {
 | 
			
		||||
	&.noGap {
 | 
			
		||||
		> .notes {
 | 
			
		||||
			background: var(--panel);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&:not(.noGap) {
 | 
			
		||||
		> .notes {
 | 
			
		||||
			background: var(--bg);
 | 
			
		||||
 | 
			
		||||
			.qtqtichx {
 | 
			
		||||
				background: var(--panel);
 | 
			
		||||
				border-radius: var(--radius);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -11,16 +11,16 @@
 | 
			
		||||
	<template #header>{{ $ts.notificationSetting }}</template>
 | 
			
		||||
	<div class="_monolithic_">
 | 
			
		||||
		<div v-if="showGlobalToggle" class="_section">
 | 
			
		||||
			<MkSwitch v-model:value="useGlobalSetting">
 | 
			
		||||
			<MkSwitch v-model="useGlobalSetting">
 | 
			
		||||
				{{ $ts.useGlobalSetting }}
 | 
			
		||||
				<template #desc>{{ $ts.useGlobalSettingDesc }}</template>
 | 
			
		||||
				<template #caption>{{ $ts.useGlobalSettingDesc }}</template>
 | 
			
		||||
			</MkSwitch>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div v-if="!useGlobalSetting" class="_section">
 | 
			
		||||
			<MkInfo>{{ $ts.notificationSettingDesc }}</MkInfo>
 | 
			
		||||
			<MkButton inline @click="disableAll">{{ $ts.disableAll }}</MkButton>
 | 
			
		||||
			<MkButton inline @click="enableAll">{{ $ts.enableAll }}</MkButton>
 | 
			
		||||
			<MkSwitch v-for="type in notificationTypes" :key="type" v-model:value="typesMap[type]">{{ $t(`_notification._types.${type}`) }}</MkSwitch>
 | 
			
		||||
			<MkSwitch v-for="type in notificationTypes" :key="type" v-model="typesMap[type]">{{ $t(`_notification._types.${type}`) }}</MkSwitch>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</XModalWindow>
 | 
			
		||||
 
 | 
			
		||||
@@ -58,12 +58,12 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { defineComponent } from 'vue';
 | 
			
		||||
import { defineComponent, markRaw } from 'vue';
 | 
			
		||||
import { getNoteSummary } from '@/misc/get-note-summary';
 | 
			
		||||
import XReactionIcon from './reaction-icon.vue';
 | 
			
		||||
import MkFollowButton from './follow-button.vue';
 | 
			
		||||
import notePage from '../filters/note';
 | 
			
		||||
import { userPage } from '../filters/user';
 | 
			
		||||
import notePage from '@client/filters/note';
 | 
			
		||||
import { userPage } from '@client/filters/user';
 | 
			
		||||
import { i18n } from '@client/i18n';
 | 
			
		||||
import * as os from '@client/os';
 | 
			
		||||
 | 
			
		||||
@@ -109,7 +109,7 @@ export default defineComponent({
 | 
			
		||||
 | 
			
		||||
			this.readObserver.observe(this.$el);
 | 
			
		||||
 | 
			
		||||
			this.connection = os.stream.useChannel('main');
 | 
			
		||||
			this.connection = markRaw(os.stream.useChannel('main'));
 | 
			
		||||
			this.connection.on('readAllNotifications', () => this.readObserver.unobserve(this.$el));
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
	<p class="mfcuwfyp" v-else-if="empty">{{ $ts.noNotifications }}</p>
 | 
			
		||||
 | 
			
		||||
	<div v-else>
 | 
			
		||||
		<XList class="notifications" :items="items" v-slot="{ item: notification }" :no-gap="true">
 | 
			
		||||
		<XList class="elsfgstc" :items="items" v-slot="{ item: notification }" :no-gap="true">
 | 
			
		||||
			<XNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :note="notification.note" @update:note="noteUpdated(notification.note, $event)" :key="notification.id"/>
 | 
			
		||||
			<XNotification v-else :notification="notification" :with-time="true" :full="true" class="_panel notification" :key="notification.id"/>
 | 
			
		||||
		</XList>
 | 
			
		||||
@@ -21,7 +21,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { defineComponent, PropType } from 'vue';
 | 
			
		||||
import { defineComponent, PropType, markRaw } from 'vue';
 | 
			
		||||
import paging from '@client/scripts/paging';
 | 
			
		||||
import XNotification from './notification.vue';
 | 
			
		||||
import XList from './date-separated-list.vue';
 | 
			
		||||
@@ -89,7 +89,7 @@ export default defineComponent({
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.connection = os.stream.useChannel('main');
 | 
			
		||||
		this.connection = markRaw(os.stream.useChannel('main'));
 | 
			
		||||
		this.connection.on('notification', this.onNotification);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -141,4 +141,8 @@ export default defineComponent({
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	color: var(--fg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.elsfgstc {
 | 
			
		||||
	background: var(--panel);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { defineComponent } from 'vue';
 | 
			
		||||
import { userName } from '../filters/user';
 | 
			
		||||
import { userName } from '@client/filters/user';
 | 
			
		||||
import * as os from '@client/os';
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
 
 | 
			
		||||
@@ -3,16 +3,12 @@
 | 
			
		||||
	:initial-width="500"
 | 
			
		||||
	:initial-height="500"
 | 
			
		||||
	:can-resize="true"
 | 
			
		||||
	:close-right="true"
 | 
			
		||||
	:close-button="false"
 | 
			
		||||
	:contextmenu="contextmenu"
 | 
			
		||||
	@closed="$emit('closed')"
 | 
			
		||||
>
 | 
			
		||||
	<template #header>
 | 
			
		||||
		<XHeader :info="pageInfo" :with-back="false"/>
 | 
			
		||||
	</template>
 | 
			
		||||
	<template #buttons>
 | 
			
		||||
		<button class="_button" @click="back()" v-if="history.length > 0"><i class="fas fa-chevron-left"></i></button>
 | 
			
		||||
		<button class="_button" style="pointer-events: none;" v-else><!-- マージンのバランスを取るためのダミー --></button>
 | 
			
		||||
		<XHeader :info="pageInfo" :back-button="history.length > 0" @back="back()" :close-button="true" @close="close()"/>
 | 
			
		||||
	</template>
 | 
			
		||||
	<div class="yrolvcoq _flat_">
 | 
			
		||||
		<component :is="component" v-bind="props" :ref="changePage"/>
 | 
			
		||||
@@ -139,6 +135,10 @@ export default defineComponent({
 | 
			
		||||
			this.navigate(this.history.pop(), false);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		close() {
 | 
			
		||||
			this.$refs.window.close();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		expand() {
 | 
			
		||||
			this.$router.push(this.path);
 | 
			
		||||
			this.$refs.window.close();
 | 
			
		||||
@@ -155,6 +155,5 @@ export default defineComponent({
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.yrolvcoq {
 | 
			
		||||
	min-height: 100%;
 | 
			
		||||
	background: var(--bg);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div>
 | 
			
		||||
	<MkInput class="kudkigyw" :value="value" @update:value="updateValue($event)" type="number">{{ hpml.interpolate(block.text) }}</MkInput>
 | 
			
		||||
	<MkInput class="kudkigyw" :model-value="value" @update:modelValue="updateValue($event)" type="number">
 | 
			
		||||
		<template #label>{{ hpml.interpolate(block.text) }}</template>
 | 
			
		||||
	</MkInput>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="ngbfujlo">
 | 
			
		||||
	<MkTextarea :value="text" readonly style="margin: 0;"></MkTextarea>
 | 
			
		||||
	<MkTextarea :model-value="text" readonly style="margin: 0;"></MkTextarea>
 | 
			
		||||
	<MkButton class="button" primary @click="post()" :disabled="posting || posted">
 | 
			
		||||
		<i v-if="posted" class="fas fa-check"></i>
 | 
			
		||||
		<i v-else class="fas fa-paper-plane"></i>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="hkcxmtwj">
 | 
			
		||||
	<MkSwitch :value="value" @update:value="updateValue($event)">{{ hpml.interpolate(block.text) }}</MkSwitch>
 | 
			
		||||
	<MkSwitch :model-value="value" @update:modelValue="updateValue($event)">{{ hpml.interpolate(block.text) }}</MkSwitch>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div>
 | 
			
		||||
	<MkInput class="kudkigyw" :value="value" @update:value="updateValue($event)" type="text">{{ hpml.interpolate(block.text) }}</MkInput>
 | 
			
		||||
	<MkInput class="kudkigyw" :model-value="value" @update:modelValue="updateValue($event)" type="text">
 | 
			
		||||
		<template #label>{{ hpml.interpolate(block.text) }}</template>
 | 
			
		||||
	</MkInput>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div>
 | 
			
		||||
	<MkTextarea :value="value" @update:value="updateValue($event)">{{ hpml.interpolate(block.text) }}</MkTextarea>
 | 
			
		||||
	<MkTextarea :model-value="value" @update:modelValue="updateValue($event)">
 | 
			
		||||
		<template #label>{{ hpml.interpolate(block.text) }}</template>
 | 
			
		||||
	</MkTextarea>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<MkTextarea :value="text" readonly></MkTextarea>
 | 
			
		||||
<MkTextarea :model-value="text" readonly></MkTextarea>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,7 @@
 | 
			
		||||
	</p>
 | 
			
		||||
	<ul ref="choices">
 | 
			
		||||
		<li v-for="(choice, i) in choices" :key="i">
 | 
			
		||||
			<MkInput class="input" :value="choice" @update:value="onInput(i, $event)">
 | 
			
		||||
				<span>{{ $t('_poll.choiceN', { n: i + 1 }) }}</span>
 | 
			
		||||
			<MkInput class="input" :model-value="choice" @update:modelValue="onInput(i, $event)" :placeholder="$t('_poll.choiceN', { n: i + 1 })">
 | 
			
		||||
			</MkInput>
 | 
			
		||||
			<button @click="remove(i)" class="_button">
 | 
			
		||||
				<i class="fas fa-times"></i>
 | 
			
		||||
@@ -16,27 +15,27 @@
 | 
			
		||||
	<MkButton class="add" v-if="choices.length < 10" @click="add">{{ $ts.add }}</MkButton>
 | 
			
		||||
	<MkButton class="add" v-else disabled>{{ $ts._poll.noMore }}</MkButton>
 | 
			
		||||
	<section>
 | 
			
		||||
		<MkSwitch v-model:value="multiple">{{ $ts._poll.canMultipleVote }}</MkSwitch>
 | 
			
		||||
		<MkSwitch v-model="multiple">{{ $ts._poll.canMultipleVote }}</MkSwitch>
 | 
			
		||||
		<div>
 | 
			
		||||
			<MkSelect v-model:value="expiration">
 | 
			
		||||
			<MkSelect v-model="expiration">
 | 
			
		||||
				<template #label>{{ $ts._poll.expiration }}</template>
 | 
			
		||||
				<option value="infinite">{{ $ts._poll.infinite }}</option>
 | 
			
		||||
				<option value="at">{{ $ts._poll.at }}</option>
 | 
			
		||||
				<option value="after">{{ $ts._poll.after }}</option>
 | 
			
		||||
			</MkSelect>
 | 
			
		||||
			<section v-if="expiration === 'at'">
 | 
			
		||||
				<MkInput v-model:value="atDate" type="date" class="input">
 | 
			
		||||
					<span>{{ $ts._poll.deadlineDate }}</span>
 | 
			
		||||
				<MkInput v-model="atDate" type="date" class="input">
 | 
			
		||||
					<template #label>{{ $ts._poll.deadlineDate }}</template>
 | 
			
		||||
				</MkInput>
 | 
			
		||||
				<MkInput v-model:value="atTime" type="time" class="input">
 | 
			
		||||
					<span>{{ $ts._poll.deadlineTime }}</span>
 | 
			
		||||
				<MkInput v-model="atTime" type="time" class="input">
 | 
			
		||||
					<template #label>{{ $ts._poll.deadlineTime }}</template>
 | 
			
		||||
				</MkInput>
 | 
			
		||||
			</section>
 | 
			
		||||
			<section v-if="expiration === 'after'">
 | 
			
		||||
				<MkInput v-model:value="after" type="number" class="input">
 | 
			
		||||
					<span>{{ $ts._poll.duration }}</span>
 | 
			
		||||
				<MkInput v-model="after" type="number" class="input">
 | 
			
		||||
					<template #label>{{ $ts._poll.duration }}</template>
 | 
			
		||||
				</MkInput>
 | 
			
		||||
				<MkSelect v-model:value="unit">
 | 
			
		||||
				<MkSelect v-model="unit">
 | 
			
		||||
					<option value="second">{{ $ts._time.second }}</option>
 | 
			
		||||
					<option value="minute">{{ $ts._time.minute }}</option>
 | 
			
		||||
					<option value="hour">{{ $ts._time.hour }}</option>
 | 
			
		||||
 
 | 
			
		||||
@@ -112,7 +112,7 @@ export default defineComponent({
 | 
			
		||||
 | 
			
		||||
		showFileMenu(file, ev: MouseEvent) {
 | 
			
		||||
			if (this.menu) return;
 | 
			
		||||
			this.menu = os.modalMenu([{
 | 
			
		||||
			this.menu = os.popupMenu([{
 | 
			
		||||
				text: this.$ts.renameFile,
 | 
			
		||||
				icon: 'fas fa-i-cursor',
 | 
			
		||||
				action: () => { this.rename(file) }
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
				<span v-if="visibility === 'followers'"><i class="fas fa-unlock"></i></span>
 | 
			
		||||
				<span v-if="visibility === 'specified'"><i class="fas fa-envelope"></i></span>
 | 
			
		||||
			</button>
 | 
			
		||||
			<button class="submit _buttonPrimary" :disabled="!canPost" @click="post">{{ submitText }}<i :class="reply ? 'fas fa-reply' : renote ? 'fas fa-quote-right' : 'fas fa-paper-plane'"></i></button>
 | 
			
		||||
			<button class="submit _buttonPrimary" :disabled="!canPost" @click="post" data-cy-open-post-form-submit>{{ submitText }}<i :class="reply ? 'fas fa-reply' : renote ? 'fas fa-quote-right' : 'fas fa-paper-plane'"></i></button>
 | 
			
		||||
		</div>
 | 
			
		||||
	</header>
 | 
			
		||||
	<div class="form" :class="{ fixed }">
 | 
			
		||||
@@ -36,7 +36,8 @@
 | 
			
		||||
		</div>
 | 
			
		||||
		<MkInfo warn v-if="hasNotSpecifiedMentions" class="hasNotSpecifiedMentions">{{ $ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ $ts.add }}</button></MkInfo>
 | 
			
		||||
		<input v-show="useCw" ref="cw" class="cw" v-model="cw" :placeholder="$ts.annotation" @keydown="onKeydown">
 | 
			
		||||
		<textarea v-model="text" class="text" :class="{ withCw: useCw }" ref="text" :disabled="posting" :placeholder="placeholder" @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd" />
 | 
			
		||||
		<textarea v-model="text" class="text" :class="{ withCw: useCw }" ref="text" :disabled="posting" :placeholder="placeholder" @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd" data-cy-post-form-text/>
 | 
			
		||||
		<input v-show="withHashtags" ref="hashtags" class="hashtags" v-model="hashtags" :placeholder="$ts.hashtags" list="hashtags">
 | 
			
		||||
		<XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/>
 | 
			
		||||
		<XPollEditor v-if="poll" :poll="poll" @destroyed="poll = null" @updated="onPollUpdate"/>
 | 
			
		||||
		<footer>
 | 
			
		||||
@@ -44,9 +45,13 @@
 | 
			
		||||
			<button class="_button" @click="togglePoll" :class="{ active: poll }" v-tooltip="$ts.poll"><i class="fas fa-poll-h"></i></button>
 | 
			
		||||
			<button class="_button" @click="useCw = !useCw" :class="{ active: useCw }" v-tooltip="$ts.useCw"><i class="fas fa-eye-slash"></i></button>
 | 
			
		||||
			<button class="_button" @click="insertMention" v-tooltip="$ts.mention"><i class="fas fa-at"></i></button>
 | 
			
		||||
			<button class="_button" @click="withHashtags = !withHashtags" :class="{ active: withHashtags }" v-tooltip="$ts.hashtags"><i class="fas fa-hashtag"></i></button>
 | 
			
		||||
			<button class="_button" @click="insertEmoji" v-tooltip="$ts.emoji"><i class="fas fa-laugh-squint"></i></button>
 | 
			
		||||
			<button class="_button" @click="showActions" v-tooltip="$ts.plugin" v-if="postFormActions.length > 0"><i class="fas fa-plug"></i></button>
 | 
			
		||||
		</footer>
 | 
			
		||||
		<datalist id="hashtags">
 | 
			
		||||
			<option v-for="hashtag in recentHashtags" :value="hashtag" :key="hashtag"/>
 | 
			
		||||
		</datalist>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -67,10 +72,11 @@ import { Autocomplete } from '@client/scripts/autocomplete';
 | 
			
		||||
import { noteVisibilities } from '../../types';
 | 
			
		||||
import * as os from '@client/os';
 | 
			
		||||
import { selectFile } from '@client/scripts/select-file';
 | 
			
		||||
import { notePostInterruptors, postFormActions } from '@client/store';
 | 
			
		||||
import { defaultStore, notePostInterruptors, postFormActions } from '@client/store';
 | 
			
		||||
import { isMobile } from '@client/scripts/is-mobile';
 | 
			
		||||
import { throttle } from 'throttle-debounce';
 | 
			
		||||
import MkInfo from '@client/components/ui/info.vue';
 | 
			
		||||
import { defaultStore } from '@client/store';
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
	components: {
 | 
			
		||||
@@ -212,7 +218,10 @@ export default defineComponent({
 | 
			
		||||
 | 
			
		||||
		max(): number {
 | 
			
		||||
			return this.$instance ? this.$instance.maxNoteTextLength : 1000;
 | 
			
		||||
		}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		withHashtags: defaultStore.makeGetterSetter('postFormWithHashtags'),
 | 
			
		||||
		hashtags: defaultStore.makeGetterSetter('postFormHashtags'),
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	watch: {
 | 
			
		||||
@@ -303,6 +312,7 @@ export default defineComponent({
 | 
			
		||||
		// TODO: detach when unmount
 | 
			
		||||
		new Autocomplete(this.$refs.text, this, { model: 'text' });
 | 
			
		||||
		new Autocomplete(this.$refs.cw, this, { model: 'cw' });
 | 
			
		||||
		new Autocomplete(this.$refs.hashtags, this, { model: 'hashtags' });
 | 
			
		||||
 | 
			
		||||
		this.$nextTick(() => {
 | 
			
		||||
			// 書きかけの投稿を復元
 | 
			
		||||
@@ -329,7 +339,12 @@ export default defineComponent({
 | 
			
		||||
				this.cw = init.cw;
 | 
			
		||||
				this.useCw = init.cw != null;
 | 
			
		||||
				if (init.poll) {
 | 
			
		||||
					this.poll = init.poll;
 | 
			
		||||
					this.poll = {
 | 
			
		||||
						choices: init.poll.choices.map(x => x.text),
 | 
			
		||||
						multiple: init.poll.multiple,
 | 
			
		||||
						expiresAt: init.poll.expiresAt,
 | 
			
		||||
						expiredAfter: init.poll.expiredAfter,
 | 
			
		||||
					};
 | 
			
		||||
				}
 | 
			
		||||
				this.visibility = init.visibility;
 | 
			
		||||
				this.localOnly = init.localOnly;
 | 
			
		||||
@@ -605,6 +620,11 @@ export default defineComponent({
 | 
			
		||||
				viaMobile: isMobile
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			if (this.withHashtags && this.hashtags && this.hashtags.trim() !== '') {
 | 
			
		||||
				const hashtags = this.hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' ');
 | 
			
		||||
				data.text = data.text ? `${data.text} ${hashtags}` : hashtags;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// plugin
 | 
			
		||||
			if (notePostInterruptors.length > 0) {
 | 
			
		||||
				for (const interruptor of notePostInterruptors) {
 | 
			
		||||
@@ -618,8 +638,8 @@ export default defineComponent({
 | 
			
		||||
				this.$nextTick(() => {
 | 
			
		||||
					this.deleteDraft();
 | 
			
		||||
					this.$emit('posted');
 | 
			
		||||
					if (this.text && this.text != '') {
 | 
			
		||||
						const hashtags = mfm.parse(this.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag);
 | 
			
		||||
					if (data.text && data.text != '') {
 | 
			
		||||
						const hashtags = mfm.parse(data.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag);
 | 
			
		||||
						const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[];
 | 
			
		||||
						localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history))));
 | 
			
		||||
					}
 | 
			
		||||
@@ -649,7 +669,7 @@ export default defineComponent({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		showActions(ev) {
 | 
			
		||||
			os.modalMenu(postFormActions.map(action => ({
 | 
			
		||||
			os.popupMenu(postFormActions.map(action => ({
 | 
			
		||||
				text: action.title,
 | 
			
		||||
				action: () => {
 | 
			
		||||
					action.handler({
 | 
			
		||||
@@ -785,6 +805,7 @@ export default defineComponent({
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .cw,
 | 
			
		||||
		> .hashtags,
 | 
			
		||||
		> .text {
 | 
			
		||||
			display: block;
 | 
			
		||||
			box-sizing: border-box;
 | 
			
		||||
@@ -813,6 +834,13 @@ export default defineComponent({
 | 
			
		||||
			border-bottom: solid 0.5px var(--divider);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .hashtags {
 | 
			
		||||
			z-index: 1;
 | 
			
		||||
			padding-top: 8px;
 | 
			
		||||
			padding-bottom: 8px;
 | 
			
		||||
			border-top: solid 0.5px var(--divider);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .text {
 | 
			
		||||
			max-width: 100%;
 | 
			
		||||
			min-width: 100%;
 | 
			
		||||
@@ -872,6 +900,7 @@ export default defineComponent({
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			> .cw,
 | 
			
		||||
			> .hashtags,
 | 
			
		||||
			> .text {
 | 
			
		||||
				padding: 0 16px;
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -170,7 +170,7 @@ export default defineComponent({
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> span {
 | 
			
		||||
			color: #fff;
 | 
			
		||||
			color: var(--fgOnAccent);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="_card">
 | 
			
		||||
	<div class="_content">
 | 
			
		||||
		<MkInput v-model:value="text">
 | 
			
		||||
			<span>Text</span>
 | 
			
		||||
		<MkInput v-model="text">
 | 
			
		||||
			<template #label>Text</template>
 | 
			
		||||
		</MkInput>
 | 
			
		||||
		<MkSwitch v-model:value="flag">
 | 
			
		||||
		<MkSwitch v-model="flag">
 | 
			
		||||
			<span>Switch is now {{ flag ? 'on' : 'off' }}</span>
 | 
			
		||||
		</MkSwitch>
 | 
			
		||||
		<div style="margin: 32px 0;">
 | 
			
		||||
@@ -93,7 +93,7 @@ export default defineComponent({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		async openMenu(ev) {
 | 
			
		||||
			os.modalMenu([{
 | 
			
		||||
			os.popupMenu([{
 | 
			
		||||
				type: 'label',
 | 
			
		||||
				text: 'Fruits'
 | 
			
		||||
			}, {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,15 +3,13 @@
 | 
			
		||||
	<div class="auth _section">
 | 
			
		||||
		<div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div>
 | 
			
		||||
		<div class="normal-signin" v-if="!totpLogin">
 | 
			
		||||
			<MkInput v-model:value="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @update:value="onUsernameChange">
 | 
			
		||||
				<span>{{ $ts.username }}</span>
 | 
			
		||||
			<MkInput v-model="username" :placeholder="$ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @update:modelValue="onUsernameChange" data-cy-signin-username>
 | 
			
		||||
				<template #prefix>@</template>
 | 
			
		||||
				<template #suffix>@{{ host }}</template>
 | 
			
		||||
			</MkInput>
 | 
			
		||||
			<MkInput v-model:value="password" type="password" :with-password-toggle="true" v-if="!user || user && !user.usePasswordLessLogin" required>
 | 
			
		||||
				<span>{{ $ts.password }}</span>
 | 
			
		||||
			<MkInput v-model="password" :placeholder="$ts.password" type="password" :with-password-toggle="true" v-if="!user || user && !user.usePasswordLessLogin" required data-cy-signin-password>
 | 
			
		||||
				<template #prefix><i class="fas fa-lock"></i></template>
 | 
			
		||||
				<template #desc><button class="_textButton" @click="resetPassword">{{ $ts.forgotPassword }}</button></template>
 | 
			
		||||
				<template #caption><button class="_textButton" @click="resetPassword" type="button">{{ $ts.forgotPassword }}</button></template>
 | 
			
		||||
			</MkInput>
 | 
			
		||||
			<MkButton type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton>
 | 
			
		||||
		</div>
 | 
			
		||||
@@ -27,12 +25,12 @@
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="twofa-group totp-group">
 | 
			
		||||
				<p style="margin-bottom:0;">{{ $ts.twoStepAuthentication }}</p>
 | 
			
		||||
				<MkInput v-model:value="password" type="password" :with-password-toggle="true" v-if="user && user.usePasswordLessLogin" required>
 | 
			
		||||
					<span>{{ $ts.password }}</span>
 | 
			
		||||
				<MkInput v-model="password" type="password" :with-password-toggle="true" v-if="user && user.usePasswordLessLogin" required>
 | 
			
		||||
					<template #label>{{ $ts.password }}</template>
 | 
			
		||||
					<template #prefix><i class="fas fa-lock"></i></template>
 | 
			
		||||
				</MkInput>
 | 
			
		||||
				<MkInput v-model:value="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required>
 | 
			
		||||
					<span>{{ $ts.token }}</span>
 | 
			
		||||
				<MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required>
 | 
			
		||||
					<template #label>{{ $ts.token }}</template>
 | 
			
		||||
					<template #prefix><i class="fas fa-gavel"></i></template>
 | 
			
		||||
				</MkInput>
 | 
			
		||||
				<MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,39 +1,39 @@
 | 
			
		||||
<template>
 | 
			
		||||
<form class="mk-signup" @submit.prevent="onSubmit" :autocomplete="Math.random()">
 | 
			
		||||
<form class="qlvuhzng" @submit.prevent="onSubmit" :autocomplete="Math.random()">
 | 
			
		||||
	<template v-if="meta">
 | 
			
		||||
		<MkInput v-if="meta.disableRegistration" v-model:value="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required>
 | 
			
		||||
			<span>{{ $ts.invitationCode }}</span>
 | 
			
		||||
		<MkInput class="_inputNoTopMargin" v-if="meta.disableRegistration" v-model="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required>
 | 
			
		||||
			<template #label>{{ $ts.invitationCode }}</template>
 | 
			
		||||
			<template #prefix><i class="fas fa-key"></i></template>
 | 
			
		||||
		</MkInput>
 | 
			
		||||
		<MkInput v-model:value="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @update:value="onChangeUsername">
 | 
			
		||||
			<span>{{ $ts.username }}</span>
 | 
			
		||||
		<MkInput class="_inputNoTopMargin" v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @update:modelValue="onChangeUsername" data-cy-signup-username>
 | 
			
		||||
			<template #label>{{ $ts.username }} <div class="_button _help" v-tooltip:dialog="$ts.usernameInfo"><i class="far fa-question-circle"></i></div></template>
 | 
			
		||||
			<template #prefix>@</template>
 | 
			
		||||
			<template #suffix>@{{ host }}</template>
 | 
			
		||||
			<template #desc>
 | 
			
		||||
			<template #caption>
 | 
			
		||||
				<span v-if="usernameState == 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ $ts.checking }}</span>
 | 
			
		||||
				<span v-if="usernameState == 'ok'" style="color:#3CB7B5"><i class="fas fa-check fa-fw"></i> {{ $ts.available }}</span>
 | 
			
		||||
				<span v-if="usernameState == 'unavailable'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.unavailable }}</span>
 | 
			
		||||
				<span v-if="usernameState == 'error'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span>
 | 
			
		||||
				<span v-if="usernameState == 'invalid-format'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.usernameInvalidFormat }}</span>
 | 
			
		||||
				<span v-if="usernameState == 'min-range'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooShort }}</span>
 | 
			
		||||
				<span v-if="usernameState == 'max-range'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooLong }}</span>
 | 
			
		||||
				<span v-if="usernameState == 'ok'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.available }}</span>
 | 
			
		||||
				<span v-if="usernameState == 'unavailable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.unavailable }}</span>
 | 
			
		||||
				<span v-if="usernameState == 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span>
 | 
			
		||||
				<span v-if="usernameState == 'invalid-format'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.usernameInvalidFormat }}</span>
 | 
			
		||||
				<span v-if="usernameState == 'min-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooShort }}</span>
 | 
			
		||||
				<span v-if="usernameState == 'max-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooLong }}</span>
 | 
			
		||||
			</template>
 | 
			
		||||
		</MkInput>
 | 
			
		||||
		<MkInput v-model:value="password" type="password" :autocomplete="Math.random()" required @update:value="onChangePassword">
 | 
			
		||||
			<span>{{ $ts.password }}</span>
 | 
			
		||||
		<MkInput v-model="password" type="password" :autocomplete="Math.random()" required @update:modelValue="onChangePassword" data-cy-signup-password>
 | 
			
		||||
			<template #label>{{ $ts.password }}</template>
 | 
			
		||||
			<template #prefix><i class="fas fa-lock"></i></template>
 | 
			
		||||
			<template #desc>
 | 
			
		||||
				<p v-if="passwordStrength == 'low'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.weakPassword }}</p>
 | 
			
		||||
				<p v-if="passwordStrength == 'medium'" style="color:#3CB7B5"><i class="fas fa-check fa-fw"></i> {{ $ts.normalPassword }}</p>
 | 
			
		||||
				<p v-if="passwordStrength == 'high'" style="color:#3CB7B5"><i class="fas fa-check fa-fw"></i> {{ $ts.strongPassword }}</p>
 | 
			
		||||
			<template #caption>
 | 
			
		||||
				<span v-if="passwordStrength == 'low'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.weakPassword }}</span>
 | 
			
		||||
				<span v-if="passwordStrength == 'medium'" style="color: var(--warn)"><i class="fas fa-check fa-fw"></i> {{ $ts.normalPassword }}</span>
 | 
			
		||||
				<span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.strongPassword }}</span>
 | 
			
		||||
			</template>
 | 
			
		||||
		</MkInput>
 | 
			
		||||
		<MkInput v-model:value="retypedPassword" type="password" :autocomplete="Math.random()" required @update:value="onChangePasswordRetype">
 | 
			
		||||
			<span>{{ $ts.password }} ({{ $ts.retype }})</span>
 | 
			
		||||
		<MkInput v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @update:modelValue="onChangePasswordRetype" data-cy-signup-password-retype>
 | 
			
		||||
			<template #label>{{ $ts.password }} ({{ $ts.retype }})</template>
 | 
			
		||||
			<template #prefix><i class="fas fa-lock"></i></template>
 | 
			
		||||
			<template #desc>
 | 
			
		||||
				<p v-if="passwordRetypeState == 'match'" style="color:#3CB7B5"><i class="fas fa-check fa-fw"></i> {{ $ts.passwordMatched }}</p>
 | 
			
		||||
				<p v-if="passwordRetypeState == 'not-match'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.passwordNotMatched }}</p>
 | 
			
		||||
			<template #caption>
 | 
			
		||||
				<span v-if="passwordRetypeState == 'match'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.passwordMatched }}</span>
 | 
			
		||||
				<span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.passwordNotMatched }}</span>
 | 
			
		||||
			</template>
 | 
			
		||||
		</MkInput>
 | 
			
		||||
		<label v-if="meta.tosUrl" class="tou">
 | 
			
		||||
@@ -46,7 +46,7 @@
 | 
			
		||||
		</label>
 | 
			
		||||
		<captcha v-if="meta.enableHcaptcha" class="captcha" provider="hcaptcha" ref="hcaptcha" v-model:value="hCaptchaResponse" :sitekey="meta.hcaptchaSiteKey"/>
 | 
			
		||||
		<captcha v-if="meta.enableRecaptcha" class="captcha" provider="recaptcha" ref="recaptcha" v-model:value="reCaptchaResponse" :sitekey="meta.recaptchaSiteKey"/>
 | 
			
		||||
		<MkButton type="submit" :disabled="shouldDisableSubmitting" primary>{{ $ts.start }}</MkButton>
 | 
			
		||||
		<MkButton type="submit" :disabled="shouldDisableSubmitting" primary data-cy-signup-submit>{{ $ts.start }}</MkButton>
 | 
			
		||||
	</template>
 | 
			
		||||
</form>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -204,7 +204,7 @@ export default defineComponent({
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.mk-signup {
 | 
			
		||||
.qlvuhzng {
 | 
			
		||||
	.captcha {
 | 
			
		||||
		margin: 16px 0;
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { defineComponent } from 'vue';
 | 
			
		||||
import { defineComponent, markRaw } from 'vue';
 | 
			
		||||
import XNotes from './notes.vue';
 | 
			
		||||
import * as os from '@client/os';
 | 
			
		||||
import * as sound from '@client/scripts/sound';
 | 
			
		||||
@@ -92,33 +92,33 @@ export default defineComponent({
 | 
			
		||||
			this.query = {
 | 
			
		||||
				antennaId: this.antenna
 | 
			
		||||
			};
 | 
			
		||||
			this.connection = os.stream.useChannel('antenna', {
 | 
			
		||||
			this.connection = markRaw(os.stream.useChannel('antenna', {
 | 
			
		||||
				antennaId: this.antenna
 | 
			
		||||
			});
 | 
			
		||||
			}));
 | 
			
		||||
			this.connection.on('note', prepend);
 | 
			
		||||
		} else if (this.src == 'home') {
 | 
			
		||||
			endpoint = 'notes/timeline';
 | 
			
		||||
			this.connection = os.stream.useChannel('homeTimeline');
 | 
			
		||||
			this.connection = markRaw(os.stream.useChannel('homeTimeline'));
 | 
			
		||||
			this.connection.on('note', prepend);
 | 
			
		||||
 | 
			
		||||
			this.connection2 = os.stream.useChannel('main');
 | 
			
		||||
			this.connection2 = markRaw(os.stream.useChannel('main'));
 | 
			
		||||
			this.connection2.on('follow', onChangeFollowing);
 | 
			
		||||
			this.connection2.on('unfollow', onChangeFollowing);
 | 
			
		||||
		} else if (this.src == 'local') {
 | 
			
		||||
			endpoint = 'notes/local-timeline';
 | 
			
		||||
			this.connection = os.stream.useChannel('localTimeline');
 | 
			
		||||
			this.connection = markRaw(os.stream.useChannel('localTimeline'));
 | 
			
		||||
			this.connection.on('note', prepend);
 | 
			
		||||
		} else if (this.src == 'social') {
 | 
			
		||||
			endpoint = 'notes/hybrid-timeline';
 | 
			
		||||
			this.connection = os.stream.useChannel('hybridTimeline');
 | 
			
		||||
			this.connection = markRaw(os.stream.useChannel('hybridTimeline'));
 | 
			
		||||
			this.connection.on('note', prepend);
 | 
			
		||||
		} else if (this.src == 'global') {
 | 
			
		||||
			endpoint = 'notes/global-timeline';
 | 
			
		||||
			this.connection = os.stream.useChannel('globalTimeline');
 | 
			
		||||
			this.connection = markRaw(os.stream.useChannel('globalTimeline'));
 | 
			
		||||
			this.connection.on('note', prepend);
 | 
			
		||||
		} else if (this.src == 'mentions') {
 | 
			
		||||
			endpoint = 'notes/mentions';
 | 
			
		||||
			this.connection = os.stream.useChannel('main');
 | 
			
		||||
			this.connection = markRaw(os.stream.useChannel('main'));
 | 
			
		||||
			this.connection.on('mention', prepend);
 | 
			
		||||
		} else if (this.src == 'directs') {
 | 
			
		||||
			endpoint = 'notes/mentions';
 | 
			
		||||
@@ -130,16 +130,16 @@ export default defineComponent({
 | 
			
		||||
					prepend(note);
 | 
			
		||||
				}
 | 
			
		||||
			};
 | 
			
		||||
			this.connection = os.stream.useChannel('main');
 | 
			
		||||
			this.connection = markRaw(os.stream.useChannel('main'));
 | 
			
		||||
			this.connection.on('mention', onNote);
 | 
			
		||||
		} else if (this.src == 'list') {
 | 
			
		||||
			endpoint = 'notes/user-list-timeline';
 | 
			
		||||
			this.query = {
 | 
			
		||||
				listId: this.list
 | 
			
		||||
			};
 | 
			
		||||
			this.connection = os.stream.useChannel('userList', {
 | 
			
		||||
			this.connection = markRaw(os.stream.useChannel('userList', {
 | 
			
		||||
				listId: this.list
 | 
			
		||||
			});
 | 
			
		||||
			}));
 | 
			
		||||
			this.connection.on('note', prepend);
 | 
			
		||||
			this.connection.on('userAdded', onUserAdded);
 | 
			
		||||
			this.connection.on('userRemoved', onUserRemoved);
 | 
			
		||||
@@ -148,9 +148,9 @@ export default defineComponent({
 | 
			
		||||
			this.query = {
 | 
			
		||||
				channelId: this.channel
 | 
			
		||||
			};
 | 
			
		||||
			this.connection = os.stream.useChannel('channel', {
 | 
			
		||||
			this.connection = markRaw(os.stream.useChannel('channel', {
 | 
			
		||||
				channelId: this.channel
 | 
			
		||||
			});
 | 
			
		||||
			}));
 | 
			
		||||
			this.connection.on('note', prepend);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,13 +14,15 @@
 | 
			
		||||
		<MkInfo warn>{{ information }}</MkInfo>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="_section">
 | 
			
		||||
		<MkInput v-model:value="name">{{ $ts.name }}</MkInput>
 | 
			
		||||
		<MkInput v-model="name">
 | 
			
		||||
			<template #label>{{ $ts.name }}</template>
 | 
			
		||||
		</MkInput>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="_section">
 | 
			
		||||
		<div style="margin-bottom: 16px;"><b>{{ $ts.permission }}</b></div>
 | 
			
		||||
		<MkButton inline @click="disableAll">{{ $ts.disableAll }}</MkButton>
 | 
			
		||||
		<MkButton inline @click="enableAll">{{ $ts.enableAll }}</MkButton>
 | 
			
		||||
		<MkSwitch v-for="kind in (initialPermissions || kinds)" :key="kind" v-model:value="permissions[kind]">{{ $t(`_permissions.${kind}`) }}</MkSwitch>
 | 
			
		||||
		<MkSwitch v-for="kind in (initialPermissions || kinds)" :key="kind" v-model="permissions[kind]">{{ $t(`_permissions.${kind}`) }}</MkSwitch>
 | 
			
		||||
	</div>
 | 
			
		||||
</XModalWindow>
 | 
			
		||||
</template>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<component class="bghgjjyj _button"
 | 
			
		||||
	:is="link ? 'a' : 'button'"
 | 
			
		||||
	:is="link ? 'MkA' : 'button'"
 | 
			
		||||
	:class="{ inline, primary, danger, full }"
 | 
			
		||||
	:type="type"
 | 
			
		||||
	@click="$emit('click', $event)"
 | 
			
		||||
@@ -115,6 +115,7 @@ export default defineComponent({
 | 
			
		||||
	z-index: 1; // 他コンポーネントのbox-shadowに隠されないようにするため
 | 
			
		||||
	display: block;
 | 
			
		||||
	min-width: 100px;
 | 
			
		||||
	width: max-content;
 | 
			
		||||
	padding: 8px 14px;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	font-weight: normal;
 | 
			
		||||
@@ -125,6 +126,8 @@ export default defineComponent({
 | 
			
		||||
	background: var(--buttonBg);
 | 
			
		||||
	border-radius: 999px;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
	transition: background 0.1s ease;
 | 
			
		||||
 | 
			
		||||
	&:not(:disabled):hover {
 | 
			
		||||
		background: var(--buttonHoverBg);
 | 
			
		||||
@@ -140,7 +143,7 @@ export default defineComponent({
 | 
			
		||||
 | 
			
		||||
	&.primary {
 | 
			
		||||
		font-weight: bold;
 | 
			
		||||
		color: #fff !important;
 | 
			
		||||
		color: var(--fgOnAccent) !important;
 | 
			
		||||
		background: var(--accent);
 | 
			
		||||
 | 
			
		||||
		&:not(:disabled):hover {
 | 
			
		||||
@@ -174,17 +177,8 @@ export default defineComponent({
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&:focus {
 | 
			
		||||
		&:after {
 | 
			
		||||
			content: "";
 | 
			
		||||
			pointer-events: none;
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			top: -5px;
 | 
			
		||||
			right: -5px;
 | 
			
		||||
			bottom: -5px;
 | 
			
		||||
			left: -5px;
 | 
			
		||||
			border: 2px solid var(--accentAlpha03);
 | 
			
		||||
			border-radius: 10px;
 | 
			
		||||
		}
 | 
			
		||||
		outline: solid 2px var(--focus);
 | 
			
		||||
		outline-offset: 2px;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.inline + .bghgjjyj {
 | 
			
		||||
 
 | 
			
		||||
@@ -99,9 +99,12 @@ export default defineComponent({
 | 
			
		||||
		z-index: 10;
 | 
			
		||||
		position: sticky;
 | 
			
		||||
		top: var(--stickyTop, 0px);
 | 
			
		||||
		background: var(--panel);
 | 
			
		||||
		/* TODO panelの半透明バージョンをプログラマティックに作りたい
 | 
			
		||||
		background: var(--X17);
 | 
			
		||||
		-webkit-backdrop-filter: blur(8px);
 | 
			
		||||
		backdrop-filter: blur(20px);
 | 
			
		||||
		-webkit-backdrop-filter: var(--blur, blur(8px));
 | 
			
		||||
		backdrop-filter: var(--blur, blur(20px));
 | 
			
		||||
		*/
 | 
			
		||||
 | 
			
		||||
		> .title {
 | 
			
		||||
			margin: 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,32 +1,9 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="juejbjww" :class="{ focused, filled, inline, disabled }">
 | 
			
		||||
	<div class="icon" ref="icon"><slot name="icon"></slot></div>
 | 
			
		||||
	<div class="input">
 | 
			
		||||
		<span class="label" ref="labelEl"><slot></slot></span>
 | 
			
		||||
		<span class="title" ref="title">
 | 
			
		||||
			<slot name="title"></slot>
 | 
			
		||||
			<span class="warning" v-if="invalid"><i class="fas fa-exclamation-circle"></i>{{ $refs.input.validationMessage }}</span>
 | 
			
		||||
		</span>
 | 
			
		||||
<div class="matxzzsk">
 | 
			
		||||
	<div class="label" @click="focus"><slot name="label"></slot></div>
 | 
			
		||||
	<div class="input" :class="{ inline, disabled, focused }">
 | 
			
		||||
		<div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div>
 | 
			
		||||
		<input v-if="debounce" ref="inputEl"
 | 
			
		||||
			v-debounce="500"
 | 
			
		||||
			:type="type"
 | 
			
		||||
			v-model.lazy="v"
 | 
			
		||||
			:disabled="disabled"
 | 
			
		||||
			:required="required"
 | 
			
		||||
			:readonly="readonly"
 | 
			
		||||
			:placeholder="placeholder"
 | 
			
		||||
			:pattern="pattern"
 | 
			
		||||
			:autocomplete="autocomplete"
 | 
			
		||||
			:spellcheck="spellcheck"
 | 
			
		||||
			:step="step"
 | 
			
		||||
			@focus="focused = true"
 | 
			
		||||
			@blur="focused = false"
 | 
			
		||||
			@keydown="onKeydown($event)"
 | 
			
		||||
			@input="onInput"
 | 
			
		||||
			:list="id"
 | 
			
		||||
		>
 | 
			
		||||
		<input v-else ref="inputEl"
 | 
			
		||||
		<input ref="inputEl"
 | 
			
		||||
			:type="type"
 | 
			
		||||
			v-model="v"
 | 
			
		||||
			:disabled="disabled"
 | 
			
		||||
@@ -48,23 +25,25 @@
 | 
			
		||||
		</datalist>
 | 
			
		||||
		<div class="suffix" ref="suffixEl"><slot name="suffix"></slot></div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<button class="save _textButton" v-if="save && changed" @click="() => { changed = false; save(); }">{{ $ts.save }}</button>
 | 
			
		||||
	<div class="desc _caption"><slot name="desc"></slot></div>
 | 
			
		||||
	<div class="caption"><slot name="caption"></slot></div>
 | 
			
		||||
 | 
			
		||||
	<MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue';
 | 
			
		||||
import debounce from 'v-debounce';
 | 
			
		||||
import * as os from '@client/os';
 | 
			
		||||
import MkButton from './button.vue';
 | 
			
		||||
import { debounce } from 'throttle-debounce';
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
	directives: {
 | 
			
		||||
		debounce
 | 
			
		||||
	components: {
 | 
			
		||||
		MkButton,
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
		value: {
 | 
			
		||||
			required: false
 | 
			
		||||
		modelValue: {
 | 
			
		||||
			required: true
 | 
			
		||||
		},
 | 
			
		||||
		type: {
 | 
			
		||||
			type: String,
 | 
			
		||||
@@ -104,9 +83,6 @@ export default defineComponent({
 | 
			
		||||
		step: {
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		debounce: {
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		datalist: {
 | 
			
		||||
			type: Array,
 | 
			
		||||
			required: false,
 | 
			
		||||
@@ -116,15 +92,23 @@ export default defineComponent({
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
		save: {
 | 
			
		||||
			type: Function,
 | 
			
		||||
		debounce: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
		manualSave: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	emits: ['change', 'keydown', 'enter'],
 | 
			
		||||
 | 
			
		||||
	emits: ['change', 'keydown', 'enter', 'update:modelValue'],
 | 
			
		||||
 | 
			
		||||
	setup(props, context) {
 | 
			
		||||
		const { value, type, autofocus } = toRefs(props);
 | 
			
		||||
		const v = ref(value.value);
 | 
			
		||||
		const { modelValue, type, autofocus } = toRefs(props);
 | 
			
		||||
		const v = ref(modelValue.value);
 | 
			
		||||
		const id = Math.random().toString(); // TODO: uuid?
 | 
			
		||||
		const focused = ref(false);
 | 
			
		||||
		const changed = ref(false);
 | 
			
		||||
@@ -133,7 +117,6 @@ export default defineComponent({
 | 
			
		||||
		const inputEl = ref(null);
 | 
			
		||||
		const prefixEl = ref(null);
 | 
			
		||||
		const suffixEl = ref(null);
 | 
			
		||||
		const labelEl = ref(null);
 | 
			
		||||
 | 
			
		||||
		const focus = () => inputEl.value.focus();
 | 
			
		||||
		const onInput = (ev) => {
 | 
			
		||||
@@ -148,15 +131,28 @@ export default defineComponent({
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		watch(value, newValue => {
 | 
			
		||||
		const updated = () => {
 | 
			
		||||
			changed.value = false;
 | 
			
		||||
			if (type?.value === 'number') {
 | 
			
		||||
				context.emit('update:modelValue', parseFloat(v.value));
 | 
			
		||||
			} else {
 | 
			
		||||
				context.emit('update:modelValue', v.value);
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		const debouncedUpdated = debounce(1000, updated);
 | 
			
		||||
 | 
			
		||||
		watch(modelValue, newValue => {
 | 
			
		||||
			v.value = newValue;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		watch(v, newValue => {
 | 
			
		||||
			if (type?.value === 'number') {
 | 
			
		||||
				context.emit('update:value', parseFloat(newValue));
 | 
			
		||||
			} else {
 | 
			
		||||
				context.emit('update:value', newValue);
 | 
			
		||||
			if (!props.manualSave) {
 | 
			
		||||
				if (props.debounce) {
 | 
			
		||||
					debouncedUpdated();
 | 
			
		||||
				} else {
 | 
			
		||||
					updated();
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			invalid.value = inputEl.value.validity.badInput;
 | 
			
		||||
@@ -172,7 +168,6 @@ export default defineComponent({
 | 
			
		||||
				// 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する
 | 
			
		||||
				const clock = setInterval(() => {
 | 
			
		||||
					if (prefixEl.value) {
 | 
			
		||||
						labelEl.value.style.left = (prefixEl.value.offsetLeft + prefixEl.value.offsetWidth) + 'px';
 | 
			
		||||
						if (prefixEl.value.offsetWidth) {
 | 
			
		||||
							inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px';
 | 
			
		||||
						}
 | 
			
		||||
@@ -200,148 +195,78 @@ export default defineComponent({
 | 
			
		||||
			inputEl,
 | 
			
		||||
			prefixEl,
 | 
			
		||||
			suffixEl,
 | 
			
		||||
			labelEl,
 | 
			
		||||
			focus,
 | 
			
		||||
			onInput,
 | 
			
		||||
			onKeydown,
 | 
			
		||||
			updated,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.juejbjww {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	margin: 32px 0;
 | 
			
		||||
.matxzzsk {
 | 
			
		||||
	margin: 1.5em 0;
 | 
			
		||||
 | 
			
		||||
	&:not(.inline):first-child {
 | 
			
		||||
		margin-top: 8px;
 | 
			
		||||
	> .label {
 | 
			
		||||
		font-size: 0.85em;
 | 
			
		||||
		padding: 0 0 8px 12px;
 | 
			
		||||
		user-select: none;
 | 
			
		||||
 | 
			
		||||
		&:empty {
 | 
			
		||||
			display: none;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&:not(.inline):last-child {
 | 
			
		||||
		margin-bottom: 8px;
 | 
			
		||||
	}
 | 
			
		||||
	> .caption {
 | 
			
		||||
		font-size: 0.8em;
 | 
			
		||||
		padding: 8px 0 0 12px;
 | 
			
		||||
		color: var(--fgTransparentWeak);
 | 
			
		||||
 | 
			
		||||
	> .icon {
 | 
			
		||||
		position: absolute;
 | 
			
		||||
		top: 0;
 | 
			
		||||
		left: 0;
 | 
			
		||||
		width: 24px;
 | 
			
		||||
		text-align: center;
 | 
			
		||||
		line-height: 32px;
 | 
			
		||||
 | 
			
		||||
		&:not(:empty) + .input {
 | 
			
		||||
			margin-left: 28px;
 | 
			
		||||
		&:empty {
 | 
			
		||||
			display: none;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .input {
 | 
			
		||||
		$height: 42px;
 | 
			
		||||
		position: relative;
 | 
			
		||||
 | 
			
		||||
		&:before {
 | 
			
		||||
			content: '';
 | 
			
		||||
			display: block;
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			bottom: 0;
 | 
			
		||||
			left: 0;
 | 
			
		||||
			right: 0;
 | 
			
		||||
			height: 1px;
 | 
			
		||||
			background: var(--inputBorder);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		&:after {
 | 
			
		||||
			content: '';
 | 
			
		||||
			display: block;
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			bottom: 0;
 | 
			
		||||
			left: 0;
 | 
			
		||||
			right: 0;
 | 
			
		||||
			height: 2px;
 | 
			
		||||
			background: var(--accent);
 | 
			
		||||
			opacity: 0;
 | 
			
		||||
			transform: scaleX(0.12);
 | 
			
		||||
			transition: border 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
 | 
			
		||||
			will-change: border opacity transform;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .label {
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			z-index: 1;
 | 
			
		||||
			top: 0;
 | 
			
		||||
			left: 0;
 | 
			
		||||
			pointer-events: none;
 | 
			
		||||
			transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
 | 
			
		||||
			transition-duration: 0.3s;
 | 
			
		||||
			font-size: 1em;
 | 
			
		||||
			line-height: 32px;
 | 
			
		||||
			color: var(--inputLabel);
 | 
			
		||||
			pointer-events: none;
 | 
			
		||||
			//will-change transform
 | 
			
		||||
			transform-origin: top left;
 | 
			
		||||
			transform: scale(1);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .title {
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			z-index: 1;
 | 
			
		||||
			top: -17px;
 | 
			
		||||
			left: 0 !important;
 | 
			
		||||
			pointer-events: none;
 | 
			
		||||
			font-size: 1em;
 | 
			
		||||
			line-height: 32px;
 | 
			
		||||
			color: var(--inputLabel);
 | 
			
		||||
			pointer-events: none;
 | 
			
		||||
			//will-change transform
 | 
			
		||||
			transform-origin: top left;
 | 
			
		||||
			transform: scale(.75);
 | 
			
		||||
			white-space: nowrap;
 | 
			
		||||
			width: 133%;
 | 
			
		||||
			overflow: hidden;
 | 
			
		||||
			text-overflow: ellipsis;
 | 
			
		||||
 | 
			
		||||
			> .warning {
 | 
			
		||||
				margin-left: 0.5em;
 | 
			
		||||
				color: var(--infoWarnFg);
 | 
			
		||||
 | 
			
		||||
				> svg {
 | 
			
		||||
					margin-right: 0.1em;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> input {
 | 
			
		||||
			$height: 32px;
 | 
			
		||||
			appearance: none;
 | 
			
		||||
			-webkit-appearance: none;
 | 
			
		||||
			display: block;
 | 
			
		||||
			height: $height;
 | 
			
		||||
			width: 100%;
 | 
			
		||||
			margin: 0;
 | 
			
		||||
			padding: 0;
 | 
			
		||||
			padding: 0 12px;
 | 
			
		||||
			font: inherit;
 | 
			
		||||
			font-weight: normal;
 | 
			
		||||
			font-size: 1em;
 | 
			
		||||
			line-height: $height;
 | 
			
		||||
			color: var(--inputText);
 | 
			
		||||
			background: transparent;
 | 
			
		||||
			border: none;
 | 
			
		||||
			border-radius: 0;
 | 
			
		||||
			color: var(--fg);
 | 
			
		||||
			background: var(--panel);
 | 
			
		||||
			border: solid 1px var(--inputBorder);
 | 
			
		||||
			border-radius: 6px;
 | 
			
		||||
			outline: none;
 | 
			
		||||
			box-shadow: none;
 | 
			
		||||
			box-sizing: border-box;
 | 
			
		||||
			transition: border-color 0.1s ease-out;
 | 
			
		||||
 | 
			
		||||
			&[type='file'] {
 | 
			
		||||
				display: none;
 | 
			
		||||
			&:hover {
 | 
			
		||||
				border-color: var(--inputBorderHover);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .prefix,
 | 
			
		||||
		> .suffix {
 | 
			
		||||
			display: block;
 | 
			
		||||
			display: flex;
 | 
			
		||||
			align-items: center;
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			z-index: 1;
 | 
			
		||||
			top: 0;
 | 
			
		||||
			padding: 0 12px;
 | 
			
		||||
			font-size: 1em;
 | 
			
		||||
			line-height: 32px;
 | 
			
		||||
			color: var(--inputLabel);
 | 
			
		||||
			height: $height;
 | 
			
		||||
			pointer-events: none;
 | 
			
		||||
 | 
			
		||||
			&:empty {
 | 
			
		||||
@@ -360,67 +285,33 @@ export default defineComponent({
 | 
			
		||||
 | 
			
		||||
		> .prefix {
 | 
			
		||||
			left: 0;
 | 
			
		||||
			padding-right: 4px;
 | 
			
		||||
			padding-right: 6px;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .suffix {
 | 
			
		||||
			right: 0;
 | 
			
		||||
			padding-left: 4px;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .save {
 | 
			
		||||
		margin: 6px 0 0 0;
 | 
			
		||||
		font-size: 0.8em;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .desc {
 | 
			
		||||
		margin: 6px 0 0 0;
 | 
			
		||||
 | 
			
		||||
		&:empty {
 | 
			
		||||
			display: none;
 | 
			
		||||
			padding-left: 6px;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		* {
 | 
			
		||||
		&.inline {
 | 
			
		||||
			display: inline-block;
 | 
			
		||||
			margin: 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.focused {
 | 
			
		||||
		> .input {
 | 
			
		||||
			&:after {
 | 
			
		||||
				opacity: 1;
 | 
			
		||||
				transform: scaleX(1);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			> .label {
 | 
			
		||||
				color: var(--accent);
 | 
			
		||||
		&.focused {
 | 
			
		||||
			> input {
 | 
			
		||||
				border-color: var(--accent);
 | 
			
		||||
				//box-shadow: 0 0 0 4px var(--focus);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.focused,
 | 
			
		||||
	&.filled {
 | 
			
		||||
		> .input {
 | 
			
		||||
			> .label {
 | 
			
		||||
				top: -17px;
 | 
			
		||||
				left: 0 !important;
 | 
			
		||||
				transform: scale(0.75);
 | 
			
		||||
		&.disabled {
 | 
			
		||||
			opacity: 0.7;
 | 
			
		||||
 | 
			
		||||
			&, * {
 | 
			
		||||
				cursor: not-allowed !important;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.inline {
 | 
			
		||||
		display: inline-block;
 | 
			
		||||
		margin: 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.disabled {
 | 
			
		||||
		opacity: 0.7;
 | 
			
		||||
 | 
			
		||||
		&, * {
 | 
			
		||||
			cursor: not-allowed !important;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="rrevdjwt" :class="{ left: align === 'left' }"
 | 
			
		||||
<div class="rrevdjwt" :class="{ left: align === 'left', pointer: point === 'top' }"
 | 
			
		||||
	ref="items"
 | 
			
		||||
	@contextmenu.self="e => e.preventDefault()"
 | 
			
		||||
	v-hotkey="keymap"
 | 
			
		||||
@@ -58,7 +58,11 @@ export default defineComponent({
 | 
			
		||||
		align: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			requried: false
 | 
			
		||||
		}
 | 
			
		||||
		},
 | 
			
		||||
		point: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			requried: false
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	emits: ['close'],
 | 
			
		||||
	data() {
 | 
			
		||||
@@ -137,6 +141,22 @@ export default defineComponent({
 | 
			
		||||
.rrevdjwt {
 | 
			
		||||
	padding: 8px 0;
 | 
			
		||||
 | 
			
		||||
	&.pointer {
 | 
			
		||||
		&:before {
 | 
			
		||||
			--size: 8px;
 | 
			
		||||
			content: '';
 | 
			
		||||
			display: block;
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			top: calc(0px - (var(--size) * 2));
 | 
			
		||||
			left: 0;
 | 
			
		||||
			right: 0;
 | 
			
		||||
			width: 0;
 | 
			
		||||
			margin: auto;
 | 
			
		||||
			border: solid var(--size) transparent;
 | 
			
		||||
			border-bottom-color: var(--popup);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.left {
 | 
			
		||||
		> .item {
 | 
			
		||||
			text-align: left;
 | 
			
		||||
@@ -171,13 +191,13 @@ export default defineComponent({
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		&:hover {
 | 
			
		||||
			color: #fff;
 | 
			
		||||
			color: var(--fgOnAccent);
 | 
			
		||||
			background: var(--accent);
 | 
			
		||||
			text-decoration: none;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		&:active {
 | 
			
		||||
			color: #fff;
 | 
			
		||||
			color: var(--fgOnAccent);
 | 
			
		||||
			background: var(--accentDarken);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')">
 | 
			
		||||
	<div class="ebkgoccj _popup _narrow_" @keydown="onKeydown" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) :  (height ? `min(${height}px, 100%)` : '100%') }">
 | 
			
		||||
	<div class="ebkgoccj _window _narrow_" @keydown="onKeydown" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) :  (height ? `min(${height}px, 100%)` : '100%') }">
 | 
			
		||||
		<div class="header">
 | 
			
		||||
			<button class="_button" v-if="withOkButton" @click="$emit('close')"><i class="fas fa-times"></i></button>
 | 
			
		||||
			<span class="title">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,20 @@
 | 
			
		||||
<template>
 | 
			
		||||
<MkModal ref="modal" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')">
 | 
			
		||||
	<MkMenu :items="items" :align="align" @close="$refs.modal.close()" class="_popup"/>
 | 
			
		||||
</MkModal>
 | 
			
		||||
<MkPopup ref="popup" :src="src" @closed="$emit('closed')" #default="{point}">
 | 
			
		||||
	<MkMenu :items="items" :align="align" :point="point" @close="$refs.popup.close()" class="_popup _shadow"/>
 | 
			
		||||
</MkPopup>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { defineComponent } from 'vue';
 | 
			
		||||
import MkModal from './modal.vue';
 | 
			
		||||
import MkPopup from './popup.vue';
 | 
			
		||||
import MkMenu from './menu.vue';
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
	components: {
 | 
			
		||||
		MkModal,
 | 
			
		||||
		MkPopup,
 | 
			
		||||
		MkMenu,
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
		items: {
 | 
			
		||||
			type: Array,
 | 
			
		||||
@@ -31,17 +32,7 @@ export default defineComponent({
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	emits: ['closed'],
 | 
			
		||||
	computed: {
 | 
			
		||||
		keymap(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				'esc': () => this.$refs.modal.close(),
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	emits: ['close', 'closed'],
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										216
									
								
								src/client/components/ui/popup.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								src/client/components/ui/popup.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,216 @@
 | 
			
		||||
<template>
 | 
			
		||||
<transition :name="$store.state.animation ? 'popup-menu' : ''" appear @after-leave="onClosed" @enter="$emit('opening')" @after-enter="childRendered">
 | 
			
		||||
	<div v-show="manualShowing != null ? manualShowing : showing" class="ccczpooj" :class="{ front, fixed, top: position === 'top' }" ref="content" :style="{ pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
 | 
			
		||||
		<slot :point="point"></slot>
 | 
			
		||||
	</div>
 | 
			
		||||
</transition>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { defineComponent, PropType } from 'vue';
 | 
			
		||||
 | 
			
		||||
function getFixedContainer(el: Element | null): Element | null {
 | 
			
		||||
	if (el == null || el.tagName === 'BODY') return null;
 | 
			
		||||
	const position = window.getComputedStyle(el).getPropertyValue('position');
 | 
			
		||||
	if (position === 'fixed') {
 | 
			
		||||
		return el;
 | 
			
		||||
	} else {
 | 
			
		||||
		return getFixedContainer(el.parentElement);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
	props: {
 | 
			
		||||
		manualShowing: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: null,
 | 
			
		||||
		},
 | 
			
		||||
		srcCenter: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		src: {
 | 
			
		||||
			type: Object as PropType<HTMLElement>,
 | 
			
		||||
			required: false,
 | 
			
		||||
		},
 | 
			
		||||
		position: {
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		front: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false,
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	emits: ['opening', 'click', 'esc', 'close', 'closed'],
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			showing: true,
 | 
			
		||||
			fixed: false,
 | 
			
		||||
			transformOrigin: 'center',
 | 
			
		||||
			contentClicking: false,
 | 
			
		||||
			point: null,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.$watch('src', () => {
 | 
			
		||||
			if (this.src) {
 | 
			
		||||
				this.src.style.pointerEvents = 'none';
 | 
			
		||||
			}
 | 
			
		||||
			this.fixed = getFixedContainer(this.src) != null;
 | 
			
		||||
			this.$nextTick(() => {
 | 
			
		||||
				this.align();
 | 
			
		||||
			});
 | 
			
		||||
		}, { immediate: true });
 | 
			
		||||
 | 
			
		||||
		this.$nextTick(() => {
 | 
			
		||||
			const popover = this.$refs.content as any;
 | 
			
		||||
			new ResizeObserver((entries, observer) => {
 | 
			
		||||
				this.align();
 | 
			
		||||
			}).observe(popover);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		document.addEventListener('mousedown', this.onDocumentClick, { passive: true });
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	beforeUnmount() {
 | 
			
		||||
		document.removeEventListener('mousedown', this.onDocumentClick);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		align() {
 | 
			
		||||
			if (this.src == null) return;
 | 
			
		||||
 | 
			
		||||
			const popover = this.$refs.content as any;
 | 
			
		||||
 | 
			
		||||
			if (popover == null) return;
 | 
			
		||||
 | 
			
		||||
			const rect = this.src.getBoundingClientRect();
 | 
			
		||||
			
 | 
			
		||||
			const width = popover.offsetWidth;
 | 
			
		||||
			const height = popover.offsetHeight;
 | 
			
		||||
 | 
			
		||||
			let left;
 | 
			
		||||
			let top;
 | 
			
		||||
 | 
			
		||||
			if (this.srcCenter) {
 | 
			
		||||
				const x = rect.left + (this.fixed ? 0 : window.pageXOffset) + (this.src.offsetWidth / 2);
 | 
			
		||||
				const y = rect.top + (this.fixed ? 0 : window.pageYOffset) + (this.src.offsetHeight / 2);
 | 
			
		||||
				left = (x - (width / 2));
 | 
			
		||||
				top = (y - (height / 2));
 | 
			
		||||
			} else {
 | 
			
		||||
				const x = rect.left + (this.fixed ? 0 : window.pageXOffset) + (this.src.offsetWidth / 2);
 | 
			
		||||
				const y = rect.top + (this.fixed ? 0 : window.pageYOffset) + this.src.offsetHeight;
 | 
			
		||||
				left = (x - (width / 2));
 | 
			
		||||
				top = y;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (this.fixed) {
 | 
			
		||||
				if (left + width > window.innerWidth) {
 | 
			
		||||
					left = window.innerWidth - width;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (top + height > window.innerHeight) {
 | 
			
		||||
					top = window.innerHeight - height;
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				if (left + width - window.pageXOffset > window.innerWidth) {
 | 
			
		||||
					left = window.innerWidth - width + window.pageXOffset - 1;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (top + height - window.pageYOffset > window.innerHeight) {
 | 
			
		||||
					top = window.innerHeight - height + window.pageYOffset - 1;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (top < 0) {
 | 
			
		||||
				top = 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (left < 0) {
 | 
			
		||||
				left = 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (top > rect.top + (this.fixed ? 0 : window.pageYOffset)) {
 | 
			
		||||
				this.point = 'top';
 | 
			
		||||
				this.transformOrigin = 'center top';
 | 
			
		||||
			} else {
 | 
			
		||||
				this.point = null;
 | 
			
		||||
				this.transformOrigin = 'center';
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			popover.style.left = left + 'px';
 | 
			
		||||
			popover.style.top = top + 'px';
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		childRendered() {
 | 
			
		||||
			// モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する
 | 
			
		||||
			const content = this.$refs.content.children[0];
 | 
			
		||||
			content.addEventListener('mousedown', e => {
 | 
			
		||||
				this.contentClicking = true;
 | 
			
		||||
				window.addEventListener('mouseup', e => {
 | 
			
		||||
					// click イベントより先に mouseup イベントが発生するかもしれないのでちょっと待つ
 | 
			
		||||
					setTimeout(() => {
 | 
			
		||||
						this.contentClicking = false;
 | 
			
		||||
					}, 100);
 | 
			
		||||
				}, { passive: true, once: true });
 | 
			
		||||
			}, { passive: true });
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		close() {
 | 
			
		||||
			if (this.src) this.src.style.pointerEvents = 'auto';
 | 
			
		||||
			this.showing = false;
 | 
			
		||||
			this.$emit('close');
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onClosed() {
 | 
			
		||||
			this.$emit('closed');
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onDocumentClick(ev) {
 | 
			
		||||
			const flyoutElement = this.$refs.content;
 | 
			
		||||
			let targetElement = ev.target;
 | 
			
		||||
			do {
 | 
			
		||||
				if (targetElement === flyoutElement) {
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
				targetElement = targetElement.parentNode;
 | 
			
		||||
			} while (targetElement);
 | 
			
		||||
			this.close();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.popup-menu-enter-active {
 | 
			
		||||
	transform-origin: var(--transformOrigin);
 | 
			
		||||
	transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1), transform 0.2s cubic-bezier(0, 0, 0.2, 1) !important;
 | 
			
		||||
}
 | 
			
		||||
.popup-menu-leave-active {
 | 
			
		||||
	transform-origin: var(--transformOrigin);
 | 
			
		||||
	transition: opacity 0.2s cubic-bezier(0.4, 0, 1, 1), transform 0.2s cubic-bezier(0.4, 0, 1, 1) !important;
 | 
			
		||||
}
 | 
			
		||||
.popup-menu-enter-from, .popup-menu-leave-to {
 | 
			
		||||
	pointer-events: none;
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
	transform: scale(0.9);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ccczpooj {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	z-index: 10000;
 | 
			
		||||
 | 
			
		||||
	&.fixed {
 | 
			
		||||
		position: fixed;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.front {
 | 
			
		||||
		z-index: 20000;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,185 +1,218 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="eiipwacr" :class="{ focused, disabled, filled, inline }">
 | 
			
		||||
	<div class="icon" ref="icon"><slot name="icon"></slot></div>
 | 
			
		||||
	<div class="input" @click="focus">
 | 
			
		||||
		<span class="label" ref="label"><slot name="label"></slot></span>
 | 
			
		||||
		<div class="prefix" ref="prefix"><slot name="prefix"></slot></div>
 | 
			
		||||
		<select ref="input"
 | 
			
		||||
<div class="vblkjoeq">
 | 
			
		||||
	<div class="label" @click="focus"><slot name="label"></slot></div>
 | 
			
		||||
	<div class="input" :class="{ inline, disabled, focused }">
 | 
			
		||||
		<div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div>
 | 
			
		||||
		<select ref="inputEl"
 | 
			
		||||
			v-model="v"
 | 
			
		||||
			:required="required"
 | 
			
		||||
			:disabled="disabled"
 | 
			
		||||
			:required="required"
 | 
			
		||||
			:readonly="readonly"
 | 
			
		||||
			:placeholder="placeholder"
 | 
			
		||||
			@focus="focused = true"
 | 
			
		||||
			@blur="focused = false"
 | 
			
		||||
			@input="onInput"
 | 
			
		||||
		>
 | 
			
		||||
			<slot></slot>
 | 
			
		||||
		</select>
 | 
			
		||||
		<div class="suffix">
 | 
			
		||||
			<slot name="suffix">
 | 
			
		||||
				<i class="fas fa-chevron-down"></i>
 | 
			
		||||
			</slot>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="suffix" ref="suffixEl"><i class="fas fa-chevron-down"></i></div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="text"><slot name="text"></slot></div>
 | 
			
		||||
	<div class="caption"><slot name="caption"></slot></div>
 | 
			
		||||
 | 
			
		||||
	<MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { defineComponent } from 'vue';
 | 
			
		||||
import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue';
 | 
			
		||||
import MkButton from './button.vue';
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
	components: {
 | 
			
		||||
		MkButton,
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
		value: {
 | 
			
		||||
			required: false
 | 
			
		||||
		modelValue: {
 | 
			
		||||
			required: true
 | 
			
		||||
		},
 | 
			
		||||
		required: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		readonly: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		disabled: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		placeholder: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		autofocus: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
		inline: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
		manualSave: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
 | 
			
		||||
	emits: ['change', 'update:modelValue'],
 | 
			
		||||
 | 
			
		||||
	setup(props, context) {
 | 
			
		||||
		const { modelValue, autofocus } = toRefs(props);
 | 
			
		||||
		const v = ref(modelValue.value);
 | 
			
		||||
		const focused = ref(false);
 | 
			
		||||
		const changed = ref(false);
 | 
			
		||||
		const invalid = ref(false);
 | 
			
		||||
		const filled = computed(() => v.value !== '' && v.value != null);
 | 
			
		||||
		const inputEl = ref(null);
 | 
			
		||||
		const prefixEl = ref(null);
 | 
			
		||||
		const suffixEl = ref(null);
 | 
			
		||||
 | 
			
		||||
		const focus = () => inputEl.value.focus();
 | 
			
		||||
		const onInput = (ev) => {
 | 
			
		||||
			changed.value = true;
 | 
			
		||||
			context.emit('change', ev);
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		const updated = () => {
 | 
			
		||||
			changed.value = false;
 | 
			
		||||
			context.emit('update:modelValue', v.value);
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		watch(modelValue, newValue => {
 | 
			
		||||
			v.value = newValue;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		watch(v, newValue => {
 | 
			
		||||
			if (!props.manualSave) {
 | 
			
		||||
				updated();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			invalid.value = inputEl.value.validity.badInput;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		onMounted(() => {
 | 
			
		||||
			nextTick(() => {
 | 
			
		||||
				if (autofocus.value) {
 | 
			
		||||
					focus();
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// このコンポーネントが作成された時、非表示状態である場合がある
 | 
			
		||||
				// 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する
 | 
			
		||||
				const clock = setInterval(() => {
 | 
			
		||||
					if (prefixEl.value) {
 | 
			
		||||
						if (prefixEl.value.offsetWidth) {
 | 
			
		||||
							inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px';
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					if (suffixEl.value) {
 | 
			
		||||
						if (suffixEl.value.offsetWidth) {
 | 
			
		||||
							inputEl.value.style.paddingRight = suffixEl.value.offsetWidth + 'px';
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}, 100);
 | 
			
		||||
 | 
			
		||||
				onUnmounted(() => {
 | 
			
		||||
					clearInterval(clock);
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		return {
 | 
			
		||||
			focused: false,
 | 
			
		||||
			v,
 | 
			
		||||
			focused,
 | 
			
		||||
			invalid,
 | 
			
		||||
			changed,
 | 
			
		||||
			filled,
 | 
			
		||||
			inputEl,
 | 
			
		||||
			prefixEl,
 | 
			
		||||
			suffixEl,
 | 
			
		||||
			focus,
 | 
			
		||||
			onInput,
 | 
			
		||||
			updated,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	computed: {
 | 
			
		||||
		v: {
 | 
			
		||||
			get() {
 | 
			
		||||
				return this.value;
 | 
			
		||||
			},
 | 
			
		||||
			set(v) {
 | 
			
		||||
				this.$emit('update:value', v);
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		filled(): boolean {
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		if (this.$refs.prefix) {
 | 
			
		||||
			this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px';
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		focus() {
 | 
			
		||||
			this.$refs.input.focus();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.eiipwacr {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	margin: 32px 0;
 | 
			
		||||
.vblkjoeq {
 | 
			
		||||
	margin: 1.5em 0;
 | 
			
		||||
 | 
			
		||||
	&:not(.inline):first-child {
 | 
			
		||||
		margin-top: 8px;
 | 
			
		||||
	> .label {
 | 
			
		||||
		font-size: 0.85em;
 | 
			
		||||
		padding: 0 0 8px 12px;
 | 
			
		||||
		user-select: none;
 | 
			
		||||
 | 
			
		||||
		&:empty {
 | 
			
		||||
			display: none;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&:not(.inline):last-child {
 | 
			
		||||
		margin-bottom: 8px;
 | 
			
		||||
	}
 | 
			
		||||
	> .caption {
 | 
			
		||||
		font-size: 0.8em;
 | 
			
		||||
		padding: 8px 0 0 12px;
 | 
			
		||||
		color: var(--fgTransparentWeak);
 | 
			
		||||
 | 
			
		||||
	> .icon {
 | 
			
		||||
		position: absolute;
 | 
			
		||||
		top: 0;
 | 
			
		||||
		left: 0;
 | 
			
		||||
		width: 24px;
 | 
			
		||||
		text-align: center;
 | 
			
		||||
		line-height: 32px;
 | 
			
		||||
 | 
			
		||||
		&:not(:empty) + .input {
 | 
			
		||||
			margin-left: 28px;
 | 
			
		||||
		&:empty {
 | 
			
		||||
			display: none;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .input {
 | 
			
		||||
		display: flex;
 | 
			
		||||
		$height: 42px;
 | 
			
		||||
		position: relative;
 | 
			
		||||
 | 
			
		||||
		&:before {
 | 
			
		||||
			content: '';
 | 
			
		||||
			display: block;
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			bottom: 0;
 | 
			
		||||
			left: 0;
 | 
			
		||||
			right: 0;
 | 
			
		||||
			height: 1px;
 | 
			
		||||
			background: var(--inputBorder);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		&:after {
 | 
			
		||||
			content: '';
 | 
			
		||||
			display: block;
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			bottom: 0;
 | 
			
		||||
			left: 0;
 | 
			
		||||
			right: 0;
 | 
			
		||||
			height: 2px;
 | 
			
		||||
			background: var(--accent);
 | 
			
		||||
			opacity: 0;
 | 
			
		||||
			transform: scaleX(0.12);
 | 
			
		||||
			transition: border 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
 | 
			
		||||
			will-change: border opacity transform;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .label {
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			top: 0;
 | 
			
		||||
			left: 0;
 | 
			
		||||
			pointer-events: none;
 | 
			
		||||
			transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
 | 
			
		||||
			transition-duration: 0.3s;
 | 
			
		||||
			font-size: 1em;
 | 
			
		||||
			line-height: 32px;
 | 
			
		||||
			pointer-events: none;
 | 
			
		||||
			//will-change transform
 | 
			
		||||
			transform-origin: top left;
 | 
			
		||||
			transform: scale(1);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> select {
 | 
			
		||||
			appearance: none;
 | 
			
		||||
			-webkit-appearance: none;
 | 
			
		||||
			display: block;
 | 
			
		||||
			flex: 1;
 | 
			
		||||
			height: $height;
 | 
			
		||||
			width: 100%;
 | 
			
		||||
			padding: 0;
 | 
			
		||||
			margin: 0;
 | 
			
		||||
			padding: 0 12px;
 | 
			
		||||
			font: inherit;
 | 
			
		||||
			font-weight: normal;
 | 
			
		||||
			font-size: 1em;
 | 
			
		||||
			height: 32px;
 | 
			
		||||
			background: none;
 | 
			
		||||
			border: none;
 | 
			
		||||
			border-radius: 0;
 | 
			
		||||
			color: var(--fg);
 | 
			
		||||
			background: var(--panel);
 | 
			
		||||
			border: solid 1px var(--inputBorder);
 | 
			
		||||
			border-radius: 6px;
 | 
			
		||||
			outline: none;
 | 
			
		||||
			box-shadow: none;
 | 
			
		||||
			appearance: none;
 | 
			
		||||
			-webkit-appearance: none;
 | 
			
		||||
			color: var(--fg);
 | 
			
		||||
			box-sizing: border-box;
 | 
			
		||||
			cursor: pointer;
 | 
			
		||||
			transition: border-color 0.1s ease-out;
 | 
			
		||||
 | 
			
		||||
			option,
 | 
			
		||||
			optgroup {
 | 
			
		||||
				color: var(--fg);
 | 
			
		||||
				background: var(--bg);
 | 
			
		||||
			&:hover {
 | 
			
		||||
				border-color: var(--inputBorderHover);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .prefix,
 | 
			
		||||
		> .suffix {
 | 
			
		||||
			display: block;
 | 
			
		||||
			align-self: center;
 | 
			
		||||
			justify-self: center;
 | 
			
		||||
			display: flex;
 | 
			
		||||
			align-items: center;
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			z-index: 1;
 | 
			
		||||
			top: 0;
 | 
			
		||||
			padding: 0 12px;
 | 
			
		||||
			font-size: 1em;
 | 
			
		||||
			line-height: 32px;
 | 
			
		||||
			color: var(--inputLabel);
 | 
			
		||||
			height: $height;
 | 
			
		||||
			pointer-events: none;
 | 
			
		||||
 | 
			
		||||
			&:empty {
 | 
			
		||||
@@ -187,53 +220,41 @@ export default defineComponent({
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			> * {
 | 
			
		||||
				display: block;
 | 
			
		||||
				display: inline-block;
 | 
			
		||||
				min-width: 16px;
 | 
			
		||||
				max-width: 150px;
 | 
			
		||||
				overflow: hidden;
 | 
			
		||||
				white-space: nowrap;
 | 
			
		||||
				text-overflow: ellipsis;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .prefix {
 | 
			
		||||
			padding-right: 4px;
 | 
			
		||||
			left: 0;
 | 
			
		||||
			padding-right: 6px;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .suffix {
 | 
			
		||||
			padding-left: 4px;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .text {
 | 
			
		||||
		margin: 6px 0;
 | 
			
		||||
		font-size: 0.8em;
 | 
			
		||||
 | 
			
		||||
		&:empty {
 | 
			
		||||
			display: none;
 | 
			
		||||
			right: 0;
 | 
			
		||||
			padding-left: 6px;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		* {
 | 
			
		||||
		&.inline {
 | 
			
		||||
			display: inline-block;
 | 
			
		||||
			margin: 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.focused {
 | 
			
		||||
		> .input {
 | 
			
		||||
			&:after {
 | 
			
		||||
				opacity: 1;
 | 
			
		||||
				transform: scaleX(1);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			> .label {
 | 
			
		||||
				color: var(--accent);
 | 
			
		||||
		&.focused {
 | 
			
		||||
			> select {
 | 
			
		||||
				border-color: var(--accent);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.focused,
 | 
			
		||||
	&.filled {
 | 
			
		||||
		> .input {
 | 
			
		||||
			> .label {
 | 
			
		||||
				top: -17px;
 | 
			
		||||
				left: 0 !important;
 | 
			
		||||
				transform: scale(0.75);
 | 
			
		||||
		&.disabled {
 | 
			
		||||
			opacity: 0.7;
 | 
			
		||||
 | 
			
		||||
			&, * {
 | 
			
		||||
				cursor: not-allowed !important;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
	</span>
 | 
			
		||||
	<span class="label">
 | 
			
		||||
		<span><slot></slot></span>
 | 
			
		||||
		<p><slot name="desc"></slot></p>
 | 
			
		||||
		<p><slot name="caption"></slot></p>
 | 
			
		||||
	</span>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -28,7 +28,7 @@ import { defineComponent } from 'vue';
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
	props: {
 | 
			
		||||
		value: {
 | 
			
		||||
		modelValue: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
@@ -39,13 +39,13 @@ export default defineComponent({
 | 
			
		||||
	},
 | 
			
		||||
	computed: {
 | 
			
		||||
		checked(): boolean {
 | 
			
		||||
			return this.value;
 | 
			
		||||
			return this.modelValue;
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		toggle() {
 | 
			
		||||
			if (this.disabled) return;
 | 
			
		||||
			this.$emit('update:value', !this.checked);
 | 
			
		||||
			this.$emit('update:modelValue', !this.checked);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
@@ -136,7 +136,7 @@ export default defineComponent({
 | 
			
		||||
 | 
			
		||||
		> p {
 | 
			
		||||
			margin: 0;
 | 
			
		||||
			opacity: 0.7;
 | 
			
		||||
			color: var(--fgTransparentWeak);
 | 
			
		||||
			font-size: 90%;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,30 +1,45 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="adhpbeos" :class="{ focused, filled, tall, pre }">
 | 
			
		||||
	<div class="input">
 | 
			
		||||
		<span class="label" ref="label"><slot></slot></span>
 | 
			
		||||
		<textarea ref="input" :class="{ code, _monospace: code }"
 | 
			
		||||
			:value="value"
 | 
			
		||||
<div class="adhpbeos">
 | 
			
		||||
	<div class="label" @click="focus"><slot name="label"></slot></div>
 | 
			
		||||
	<div class="input" :class="{ disabled, focused, tall, pre }">
 | 
			
		||||
		<textarea ref="inputEl"
 | 
			
		||||
			:class="{ code, _monospace: code }"
 | 
			
		||||
			v-model="v"
 | 
			
		||||
			:disabled="disabled"
 | 
			
		||||
			:required="required"
 | 
			
		||||
			:readonly="readonly"
 | 
			
		||||
			:placeholder="placeholder"
 | 
			
		||||
			:pattern="pattern"
 | 
			
		||||
			:autocomplete="autocomplete"
 | 
			
		||||
			:spellcheck="!code"
 | 
			
		||||
			@input="onInput"
 | 
			
		||||
			:spellcheck="spellcheck"
 | 
			
		||||
			@focus="focused = true"
 | 
			
		||||
			@blur="focused = false"
 | 
			
		||||
			@keydown="onKeydown($event)"
 | 
			
		||||
			@input="onInput"
 | 
			
		||||
		></textarea>
 | 
			
		||||
	</div>
 | 
			
		||||
	<button class="save _textButton" v-if="save && changed" @click="() => { changed = false; save(); }">{{ $ts.save }}</button>
 | 
			
		||||
	<div class="desc _caption"><slot name="desc"></slot></div>
 | 
			
		||||
	<div class="caption"><slot name="caption"></slot></div>
 | 
			
		||||
 | 
			
		||||
	<MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { defineComponent } from 'vue';
 | 
			
		||||
import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue';
 | 
			
		||||
import MkButton from './button.vue';
 | 
			
		||||
import { debounce } from 'throttle-debounce';
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
	components: {
 | 
			
		||||
		MkButton,
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
		value: {
 | 
			
		||||
		modelValue: {
 | 
			
		||||
			required: true
 | 
			
		||||
		},
 | 
			
		||||
		type: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		required: {
 | 
			
		||||
@@ -35,14 +50,29 @@ export default defineComponent({
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		disabled: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		pattern: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		autocomplete: {
 | 
			
		||||
		placeholder: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		autofocus: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
		autocomplete: {
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		spellcheck: {
 | 
			
		||||
			required: false
 | 
			
		||||
		},
 | 
			
		||||
		code: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false
 | 
			
		||||
@@ -57,169 +87,164 @@ export default defineComponent({
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
		save: {
 | 
			
		||||
			type: Function,
 | 
			
		||||
		debounce: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
		manualSave: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
 | 
			
		||||
	emits: ['change', 'keydown', 'enter', 'update:modelValue'],
 | 
			
		||||
 | 
			
		||||
	setup(props, context) {
 | 
			
		||||
		const { modelValue, autofocus } = toRefs(props);
 | 
			
		||||
		const v = ref(modelValue.value);
 | 
			
		||||
		const focused = ref(false);
 | 
			
		||||
		const changed = ref(false);
 | 
			
		||||
		const invalid = ref(false);
 | 
			
		||||
		const filled = computed(() => v.value !== '' && v.value != null);
 | 
			
		||||
		const inputEl = ref(null);
 | 
			
		||||
 | 
			
		||||
		const focus = () => inputEl.value.focus();
 | 
			
		||||
		const onInput = (ev) => {
 | 
			
		||||
			changed.value = true;
 | 
			
		||||
			context.emit('change', ev);
 | 
			
		||||
		};
 | 
			
		||||
		const onKeydown = (ev: KeyboardEvent) => {
 | 
			
		||||
			context.emit('keydown', ev);
 | 
			
		||||
 | 
			
		||||
			if (ev.code === 'Enter') {
 | 
			
		||||
				context.emit('enter');
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		const updated = () => {
 | 
			
		||||
			changed.value = false;
 | 
			
		||||
			context.emit('update:modelValue', v.value);
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		const debouncedUpdated = debounce(1000, updated);
 | 
			
		||||
 | 
			
		||||
		watch(modelValue, newValue => {
 | 
			
		||||
			v.value = newValue;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		watch(v, newValue => {
 | 
			
		||||
			if (!props.manualSave) {
 | 
			
		||||
				if (props.debounce) {
 | 
			
		||||
					debouncedUpdated();
 | 
			
		||||
				} else {
 | 
			
		||||
					updated();
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			invalid.value = inputEl.value.validity.badInput;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		onMounted(() => {
 | 
			
		||||
			nextTick(() => {
 | 
			
		||||
				if (autofocus.value) {
 | 
			
		||||
					focus();
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		return {
 | 
			
		||||
			focused: false,
 | 
			
		||||
			changed: false,
 | 
			
		||||
		}
 | 
			
		||||
			v,
 | 
			
		||||
			focused,
 | 
			
		||||
			invalid,
 | 
			
		||||
			changed,
 | 
			
		||||
			filled,
 | 
			
		||||
			inputEl,
 | 
			
		||||
			focus,
 | 
			
		||||
			onInput,
 | 
			
		||||
			onKeydown,
 | 
			
		||||
			updated,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	computed: {
 | 
			
		||||
		filled(): boolean {
 | 
			
		||||
			return this.value != '' && this.value != null;
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		focus() {
 | 
			
		||||
			this.$refs.input.focus();
 | 
			
		||||
		},
 | 
			
		||||
		onInput(ev) {
 | 
			
		||||
			this.changed = true;
 | 
			
		||||
			this.$emit('update:value', ev.target.value);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.adhpbeos {
 | 
			
		||||
	margin: 42px 0 32px 0;
 | 
			
		||||
	position: relative;
 | 
			
		||||
	margin: 1.5em 0;
 | 
			
		||||
 | 
			
		||||
	&:first-child {
 | 
			
		||||
		margin-top: 16px;
 | 
			
		||||
	> .label {
 | 
			
		||||
		font-size: 0.85em;
 | 
			
		||||
		padding: 0 0 8px 12px;
 | 
			
		||||
		user-select: none;
 | 
			
		||||
 | 
			
		||||
		&:empty {
 | 
			
		||||
			display: none;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&:last-child {
 | 
			
		||||
		margin-bottom: 0;
 | 
			
		||||
	> .caption {
 | 
			
		||||
		font-size: 0.8em;
 | 
			
		||||
		padding: 8px 0 0 12px;
 | 
			
		||||
		color: var(--fgTransparentWeak);
 | 
			
		||||
 | 
			
		||||
		&:empty {
 | 
			
		||||
			display: none;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .input {
 | 
			
		||||
		position: relative;
 | 
			
		||||
	
 | 
			
		||||
		&:before {
 | 
			
		||||
			content: '';
 | 
			
		||||
			display: block;
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			top: 0;
 | 
			
		||||
			bottom: 0;
 | 
			
		||||
			left: 0;
 | 
			
		||||
			right: 0;
 | 
			
		||||
			background: none;
 | 
			
		||||
			border: solid 1px var(--inputBorder);
 | 
			
		||||
			border-radius: 3px;
 | 
			
		||||
			pointer-events: none;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		&:after {
 | 
			
		||||
			content: '';
 | 
			
		||||
			display: block;
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			top: 0;
 | 
			
		||||
			bottom: 0;
 | 
			
		||||
			left: 0;
 | 
			
		||||
			right: 0;
 | 
			
		||||
			background: none;
 | 
			
		||||
			border: solid 2px var(--accent);
 | 
			
		||||
			border-radius: 3px;
 | 
			
		||||
			opacity: 0;
 | 
			
		||||
			transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
 | 
			
		||||
			pointer-events: none;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .label {
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			top: 6px;
 | 
			
		||||
			left: 12px;
 | 
			
		||||
			pointer-events: none;
 | 
			
		||||
			transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
 | 
			
		||||
			transition-duration: 0.3s;
 | 
			
		||||
			font-size: 1em;
 | 
			
		||||
			line-height: 32px;
 | 
			
		||||
			pointer-events: none;
 | 
			
		||||
			//will-change transform
 | 
			
		||||
			transform-origin: top left;
 | 
			
		||||
			transform: scale(1);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> textarea {
 | 
			
		||||
			appearance: none;
 | 
			
		||||
			-webkit-appearance: none;
 | 
			
		||||
			display: block;
 | 
			
		||||
			width: 100%;
 | 
			
		||||
			min-width: 100%;
 | 
			
		||||
			max-width: 100%;
 | 
			
		||||
			min-height: 130px;
 | 
			
		||||
			margin: 0;
 | 
			
		||||
			padding: 12px;
 | 
			
		||||
			box-sizing: border-box;
 | 
			
		||||
			font: inherit;
 | 
			
		||||
			font-weight: normal;
 | 
			
		||||
			font-size: 1em;
 | 
			
		||||
			background: transparent;
 | 
			
		||||
			border: none;
 | 
			
		||||
			border-radius: 0;
 | 
			
		||||
			color: var(--fg);
 | 
			
		||||
			background: var(--panel);
 | 
			
		||||
			border: solid 1px var(--inputBorder);
 | 
			
		||||
			border-radius: 6px;
 | 
			
		||||
			outline: none;
 | 
			
		||||
			box-shadow: none;
 | 
			
		||||
			color: var(--fg);
 | 
			
		||||
			box-sizing: border-box;
 | 
			
		||||
			transition: border-color 0.1s ease-out;
 | 
			
		||||
 | 
			
		||||
			&.code {
 | 
			
		||||
				tab-size: 2;
 | 
			
		||||
			&:hover {
 | 
			
		||||
				border-color: var(--inputBorderHover);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .save {
 | 
			
		||||
		margin: 6px 0 0 0;
 | 
			
		||||
		font-size: 0.8em;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .desc {
 | 
			
		||||
		margin: 6px 0 0 0;
 | 
			
		||||
 | 
			
		||||
		&:empty {
 | 
			
		||||
			display: none;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		* {
 | 
			
		||||
			margin: 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.focused {
 | 
			
		||||
		> .input {
 | 
			
		||||
			&:after {
 | 
			
		||||
				opacity: 1;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			> .label {
 | 
			
		||||
				color: var(--accent);
 | 
			
		||||
		&.focused {
 | 
			
		||||
			> textarea {
 | 
			
		||||
				border-color: var(--accent);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.focused,
 | 
			
		||||
	&.filled {
 | 
			
		||||
		> .input {
 | 
			
		||||
			> .label {
 | 
			
		||||
				top: -24px;
 | 
			
		||||
				left: 0 !important;
 | 
			
		||||
				transform: scale(0.75);
 | 
			
		||||
		&.disabled {
 | 
			
		||||
			opacity: 0.7;
 | 
			
		||||
 | 
			
		||||
			&, * {
 | 
			
		||||
				cursor: not-allowed !important;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.tall {
 | 
			
		||||
		> .input {
 | 
			
		||||
		&.tall {
 | 
			
		||||
			> textarea {
 | 
			
		||||
				min-height: 200px;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.pre {
 | 
			
		||||
		> .input {
 | 
			
		||||
		&.pre {
 | 
			
		||||
			> textarea {
 | 
			
		||||
				white-space: pre;
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,13 @@
 | 
			
		||||
<template>
 | 
			
		||||
<transition :name="$store.state.animation ? 'window' : ''" appear @after-leave="$emit('closed')">
 | 
			
		||||
	<div class="ebkgocck" :class="{ front }" v-if="showing">
 | 
			
		||||
		<div class="body _popup _shadow _narrow_" @mousedown="onBodyMousedown" @keydown="onKeydown">
 | 
			
		||||
		<div class="body _window _shadow _narrow_" @mousedown="onBodyMousedown" @keydown="onKeydown">
 | 
			
		||||
			<div class="header" :class="{ mini }" @contextmenu.prevent.stop="onContextmenu">
 | 
			
		||||
				<slot v-if="closeRight" name="buttons"><button class="_button" style="pointer-events: none;"></button></slot>
 | 
			
		||||
				<button v-else class="_button" @click="close()"><i class="fas fa-times"></i></button>
 | 
			
		||||
				<button v-if="closeButton" class="_button" @click="close()"><i class="fas fa-times"></i></button>
 | 
			
		||||
 | 
			
		||||
				<span class="title" @mousedown.prevent="onHeaderMousedown" @touchstart.prevent="onHeaderMousedown">
 | 
			
		||||
					<slot name="header"></slot>
 | 
			
		||||
				</span>
 | 
			
		||||
 | 
			
		||||
				<button v-if="closeRight" class="_button" @click="close()"><i class="fas fa-times"></i></button>
 | 
			
		||||
				<slot v-else name="buttons"><button class="_button" style="pointer-events: none;"></button></slot>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="body" v-if="padding">
 | 
			
		||||
				<div class="_section">
 | 
			
		||||
@@ -86,10 +82,10 @@ export default defineComponent({
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false,
 | 
			
		||||
		},
 | 
			
		||||
		closeRight: {
 | 
			
		||||
		closeButton: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false,
 | 
			
		||||
			default: true,
 | 
			
		||||
		},
 | 
			
		||||
		mini: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
@@ -420,6 +416,7 @@ export default defineComponent({
 | 
			
		||||
			flex-shrink: 0;
 | 
			
		||||
			user-select: none;
 | 
			
		||||
			height: var(--height);
 | 
			
		||||
			border-bottom: solid 1px var(--divider);
 | 
			
		||||
 | 
			
		||||
			> ::v-deep(button) {
 | 
			
		||||
				height: var(--height);
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user