Merge branch 'misskey-dev:develop' into sonic
This commit is contained in:
		
						commit
						76867098ae
					
				
					 63 changed files with 1185 additions and 385 deletions
				
			
		
							
								
								
									
										17
									
								
								.github/dependabot.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								.github/dependabot.yml
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -5,7 +5,18 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
version: 2
 | 
					version: 2
 | 
				
			||||||
updates:
 | 
					updates:
 | 
				
			||||||
  - package-ecosystem: "npm" # See documentation for possible values
 | 
					- package-ecosystem: npm
 | 
				
			||||||
    directory: "/" # Location of package manifests
 | 
					  directory: "/"
 | 
				
			||||||
  schedule:
 | 
					  schedule:
 | 
				
			||||||
      interval: "daily"
 | 
					    interval: daily
 | 
				
			||||||
 | 
					  open-pull-requests-limit: 0
 | 
				
			||||||
 | 
					- package-ecosystem: npm
 | 
				
			||||||
 | 
					  directory: "/packages/backend"
 | 
				
			||||||
 | 
					  schedule:
 | 
				
			||||||
 | 
					    interval: daily
 | 
				
			||||||
 | 
					  open-pull-requests-limit: 0
 | 
				
			||||||
 | 
					- package-ecosystem: npm
 | 
				
			||||||
 | 
					  directory: "/packages/client"
 | 
				
			||||||
 | 
					  schedule:
 | 
				
			||||||
 | 
					    interval: daily
 | 
				
			||||||
 | 
					  open-pull-requests-limit: 0
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										43
									
								
								CHANGELOG.md
									
										
									
									
									
								
							
							
						
						
									
										43
									
								
								CHANGELOG.md
									
										
									
									
									
								
							| 
						 | 
					@ -10,6 +10,49 @@
 | 
				
			||||||
You should also include the user name that made the change.
 | 
					You should also include the user name that made the change.
 | 
				
			||||||
-->
 | 
					-->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 12.107.0 (2022/02/12)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Improvements
 | 
				
			||||||
 | 
					- クライアント: テーマを追加 @syuilo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Bugfixes
 | 
				
			||||||
 | 
					- API: stats APIで内部エラーが発生する問題を修正 @syuilo
 | 
				
			||||||
 | 
					- クライアント: ソフトミュートですべてがマッチしてしまう場合があるのを修正 @tamaina
 | 
				
			||||||
 | 
					- クライアント: デバイスのスクリーンのセーフエリアを考慮するように @syuilo
 | 
				
			||||||
 | 
					- クライアント: 一部環境でサイドバーの投稿ボタンが表示されない問題を修正 @syuilo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 12.106.3 (2022/02/11)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Improvements
 | 
				
			||||||
 | 
					- クライアント: スマートフォンでの余白を調整 @syuilo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Bugfixes
 | 
				
			||||||
 | 
					- クライアント: ノートの詳細が表示されない問題を修正 @syuilo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 12.106.2 (2022/02/11)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Bugfixes
 | 
				
			||||||
 | 
					- クライアント: 削除したノートがタイムラインから自動で消えない問題を修正 @syuilo
 | 
				
			||||||
 | 
					- クライアント: リアクション数が正しくないことがある問題を修正 @syuilo
 | 
				
			||||||
 | 
					- 一部環境でマイグレーションが動作しない問題を修正 @syuilo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 12.106.1 (2022/02/11)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Bugfixes
 | 
				
			||||||
 | 
					- クライアント: ワードミュートが保存できない問題を修正 @syuilo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 12.106.0 (2022/02/11)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Improvements
 | 
				
			||||||
 | 
					- Improve federation chart @syuilo
 | 
				
			||||||
 | 
					- クライアント: リアクションピッカーのサイズを設定できるように @syuilo
 | 
				
			||||||
 | 
					- クライアント: リアクションピッカーの幅、高さ制限を緩和 @syuilo
 | 
				
			||||||
 | 
					- Docker: Update to Node v16.13.2 @mei23
 | 
				
			||||||
 | 
					- Update dependencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Bugfixes
 | 
				
			||||||
 | 
					- validate regular expressions in word mutes @Johann150
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 12.105.0 (2022/02/09)
 | 
					## 12.105.0 (2022/02/09)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Improvements
 | 
					### Improvements
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -595,6 +595,8 @@ smtpSecure: "SMTP সংযোগের জন্য SSL/TLS ব্যবহা
 | 
				
			||||||
smtpSecureInfo: "STARTTLS ব্যবহার করার সময় এটি বন্ধ করুন।"
 | 
					smtpSecureInfo: "STARTTLS ব্যবহার করার সময় এটি বন্ধ করুন।"
 | 
				
			||||||
testEmail: "ইমেল বিতরণ পরীক্ষা করুন"
 | 
					testEmail: "ইমেল বিতরণ পরীক্ষা করুন"
 | 
				
			||||||
wordMute: "বিশেষ কোন শব্দকে মিউট করুন"
 | 
					wordMute: "বিশেষ কোন শব্দকে মিউট করুন"
 | 
				
			||||||
 | 
					regexpError: "রেগুলার এক্সপ্রেশন ত্রুটি"
 | 
				
			||||||
 | 
					regexpErrorDescription: "{tab} ওয়ার্ড মিউটের {line} লাইনে রেগুলার এক্সপ্রেশনে একটি ত্রুটি ছিল:"
 | 
				
			||||||
instanceMute: "মিউট করা ইন্সত্যান্সগুলি"
 | 
					instanceMute: "মিউট করা ইন্সত্যান্সগুলি"
 | 
				
			||||||
userSaysSomething: "{name} কিছু বলেছে"
 | 
					userSaysSomething: "{name} কিছু বলেছে"
 | 
				
			||||||
makeActive: "সক্রিয় করা"
 | 
					makeActive: "সক্রিয় করা"
 | 
				
			||||||
| 
						 | 
					@ -824,6 +826,11 @@ leaveGroupConfirm: "\"{name}\" গ্রুপ ছেড়ে চলে যেত
 | 
				
			||||||
useDrawerReactionPickerForMobile: "মোবাইলে রিঅ্যাকশন পিকারকে ড্রয়ারে প্রদর্শন করুন"
 | 
					useDrawerReactionPickerForMobile: "মোবাইলে রিঅ্যাকশন পিকারকে ড্রয়ারে প্রদর্শন করুন"
 | 
				
			||||||
welcomeBackWithName: "আবার স্বাগতম, {name}"
 | 
					welcomeBackWithName: "আবার স্বাগতম, {name}"
 | 
				
			||||||
clickToFinishEmailVerification: " [{ok}] ক্লিক করার মাধ্যমে আপনার ইমেল ঠিকানা নিশ্চিত করুন।"
 | 
					clickToFinishEmailVerification: " [{ok}] ক্লিক করার মাধ্যমে আপনার ইমেল ঠিকানা নিশ্চিত করুন।"
 | 
				
			||||||
 | 
					overridedDeviceKind: "ডিভাইসের ধরন"
 | 
				
			||||||
 | 
					smartphone: "স্মার্টফোন"
 | 
				
			||||||
 | 
					tablet: "ট্যাবলেট"
 | 
				
			||||||
 | 
					auto: "স্বয়ংক্রিয়"
 | 
				
			||||||
 | 
					themeColor: "থিমের রং"
 | 
				
			||||||
_emailUnavailable:
 | 
					_emailUnavailable:
 | 
				
			||||||
  used: "এই ইমেইল ঠিকানাটি ইতোমধ্যে ব্যবহৃত হয়েছে"
 | 
					  used: "এই ইমেইল ঠিকানাটি ইতোমধ্যে ব্যবহৃত হয়েছে"
 | 
				
			||||||
  format: "এই ইমেল ঠিকানাটি সঠিকভাবে লিখা হয়নি"
 | 
					  format: "এই ইমেল ঠিকানাটি সঠিকভাবে লিখা হয়নি"
 | 
				
			||||||
| 
						 | 
					@ -1112,10 +1119,47 @@ _2fa:
 | 
				
			||||||
  registerDevice: "নতুন ডিভাইস নিবন্ধন করুন"
 | 
					  registerDevice: "নতুন ডিভাইস নিবন্ধন করুন"
 | 
				
			||||||
  registerKey: "সিকিউরিটি কী নিবন্ধন করুন"
 | 
					  registerKey: "সিকিউরিটি কী নিবন্ধন করুন"
 | 
				
			||||||
  step1: "প্রথমে, আপনার ডিভাইসে {a} বা {b} এর মতো একটি অথেনটিকেশন অ্যাপ ইনস্টল করুন৷"
 | 
					  step1: "প্রথমে, আপনার ডিভাইসে {a} বা {b} এর মতো একটি অথেনটিকেশন অ্যাপ ইনস্টল করুন৷"
 | 
				
			||||||
 | 
					  step2: "এরপরে, অ্যাপের সাহায্যে প্রদর্শিত QR কোডটি স্ক্যান করুন।"
 | 
				
			||||||
 | 
					  step3: "অ্যাপে প্রদর্শিত টোকেনটি লিখুন এবং আপনার কাজ শেষ।"
 | 
				
			||||||
 | 
					  step4: "আপনাকে এখন থেকে লগ ইন করার সময়, এইভাবে টোকেন লিখতে হবে।"
 | 
				
			||||||
 | 
					  securityKeyInfo: "আপনি একটি হার্ডওয়্যার সিকিউরিটি কী ব্যবহার করে লগ ইন করতে পারেন যা FIDO2 বা ডিভাইসের ফিঙ্গারপ্রিন্ট সেন্সর বা পিন সমর্থন করে৷"
 | 
				
			||||||
_permissions:
 | 
					_permissions:
 | 
				
			||||||
 | 
					  "read:account": "অ্যাকাউন্টের তথ্য দেখুন"
 | 
				
			||||||
 | 
					  "write:account": "অ্যাকাউন্টের তথ্য সম্পাদন করুন"
 | 
				
			||||||
 | 
					  "read:blocks": "ব্লক করা ব্যাবহারকারীদের তালিকা দেখুন"
 | 
				
			||||||
 | 
					  "write:blocks": "ব্লক করা ব্যাবহারকারীদের তালিকা সম্পাদনা করুন"
 | 
				
			||||||
 | 
					  "read:drive": "ড্রাইভের ফাইল এবং ফোল্ডারসমূহ পড়া"
 | 
				
			||||||
 | 
					  "write:drive": "ড্রাইভের ফাইল এবং ফোল্ডারসমূহ সম্পাদনা করা"
 | 
				
			||||||
 | 
					  "read:favorites": "পছন্দের তালিকা পড়া"
 | 
				
			||||||
 | 
					  "write:favorites": "পছন্দের তালিকা সম্পাদনা করা"
 | 
				
			||||||
 | 
					  "read:following": "অনুসরণ তথ্য দেখুন"
 | 
				
			||||||
 | 
					  "write:following": "অনুসরণ তথ্য সম্পাদনা করা"
 | 
				
			||||||
 | 
					  "read:messaging": "চ্যাটগুলি দেখুন"
 | 
				
			||||||
 | 
					  "write:messaging": "চ্যাটগুলি সম্পাদনা করুন"
 | 
				
			||||||
 | 
					  "read:mutes": "মিউটের লিস্ট দেখুন"
 | 
				
			||||||
 | 
					  "write:mutes": "মিউটের লিস্ট সম্পাদনা করুন"
 | 
				
			||||||
 | 
					  "write:notes": "নোট লিখা"
 | 
				
			||||||
 | 
					  "read:notifications": "বিজ্ঞপ্তিগুলি দেখুন"
 | 
				
			||||||
 | 
					  "write:notifications": "বিজ্ঞপ্তি নিয়ে কাজ করে"
 | 
				
			||||||
 | 
					  "read:reactions": "রিঅ্যাকশনগুলি দেখুন"
 | 
				
			||||||
 | 
					  "write:reactions": "রিঅ্যাকশনগুলি সম্পাদনা করুন"
 | 
				
			||||||
 | 
					  "write:votes": "ভোট দিন"
 | 
				
			||||||
  "read:pages": "আপনার পেজগুলি দেখুন"
 | 
					  "read:pages": "আপনার পেজগুলি দেখুন"
 | 
				
			||||||
  "write:pages": "পেজগুলি সম্পাদনা বা ডিলিট করুন"
 | 
					  "write:pages": "পেজগুলি সম্পাদনা বা ডিলিট করুন"
 | 
				
			||||||
 | 
					  "read:page-likes": "পৃষ্ঠায় দেয়া পছন্দগুলি দেখুন"
 | 
				
			||||||
 | 
					  "write:page-likes": "পৃষ্ঠায় দেয়া পছন্দগুলি সম্পাদনা করুন"
 | 
				
			||||||
 | 
					  "read:user-groups": "ব্যাবহারকারী গ্রুপগুলি দেখুন"
 | 
				
			||||||
 | 
					  "write:user-groups": "ব্যাবহারকারী গ্রুপগুলি সম্পাদনা করুন"
 | 
				
			||||||
 | 
					  "read:channels": "চ্যানেলগুলি দেখুন"
 | 
				
			||||||
 | 
					  "write:channels": "চ্যানেলগুলি সম্পাদনা করুন"
 | 
				
			||||||
 | 
					  "read:gallery": "গ্যালারী দেখুন"
 | 
				
			||||||
 | 
					  "write:gallery": "গ্যালারী সম্পাদনা করুন"
 | 
				
			||||||
 | 
					  "read:gallery-likes": "গ্যালারীর পছন্দগুলি দেখুন"
 | 
				
			||||||
 | 
					  "write:gallery-likes": "গ্যালারীর পছন্দগুলি সম্পাদনা করুন"
 | 
				
			||||||
_auth:
 | 
					_auth:
 | 
				
			||||||
 | 
					  shareAccess: "\"{name}\" কে অ্যাকাউন্টের অ্যাক্সেস দিবেন?"
 | 
				
			||||||
 | 
					  shareAccessAsk: "অ্যাপ্লিকেশনটিকে অ্যাকাউন্টের অ্যাক্সেস দিবেন?"
 | 
				
			||||||
 | 
					  permissionAsk: "এই অ্যাপ্লিকেশনটি নিম্নলিখিত অনুমতি চাই"
 | 
				
			||||||
  pleaseGoBack: "দয়া করে অ্যাপ্লিকেশনে ফিরে যান"
 | 
					  pleaseGoBack: "দয়া করে অ্যাপ্লিকেশনে ফিরে যান"
 | 
				
			||||||
  callback: "অ্যাপ্লিকেশনে ফিরে যাচ্ছি"
 | 
					  callback: "অ্যাপ্লিকেশনে ফিরে যাচ্ছি"
 | 
				
			||||||
  denied: "প্রবেশ নিষেধ"
 | 
					  denied: "প্রবেশ নিষেধ"
 | 
				
			||||||
| 
						 | 
					@ -1178,48 +1222,342 @@ _poll:
 | 
				
			||||||
  closed: "শেষ হয়ে গেছে"
 | 
					  closed: "শেষ হয়ে গেছে"
 | 
				
			||||||
  remainingDays: "আর {d} দিন {h} ঘণ্টা বাকি আছে"
 | 
					  remainingDays: "আর {d} দিন {h} ঘণ্টা বাকি আছে"
 | 
				
			||||||
  remainingHours: "আর {h} ঘণ্টা {m} মিনিট বাকি আছে"
 | 
					  remainingHours: "আর {h} ঘণ্টা {m} মিনিট বাকি আছে"
 | 
				
			||||||
 | 
					  remainingMinutes: "আর বাকি আছে {m} মিনিট {s} সেকেন্ড"
 | 
				
			||||||
 | 
					  remainingSeconds: "আর বাকি আছে {s} সেকেন্ড"
 | 
				
			||||||
_visibility:
 | 
					_visibility:
 | 
				
			||||||
 | 
					  public: "সর্বজনীন"
 | 
				
			||||||
 | 
					  publicDescription: "সবাই আপনার নোটগুলি দেখতে পাবে"
 | 
				
			||||||
  home: "মূল পাতা"
 | 
					  home: "মূল পাতা"
 | 
				
			||||||
 | 
					  homeDescription: "শুধুমাত্র হোম টাইমলাইনে আপনার নোটগুলি পোস্ট করুন"
 | 
				
			||||||
  followers: "অনুসরণকারী"
 | 
					  followers: "অনুসরণকারী"
 | 
				
			||||||
 | 
					  followersDescription: "শুধুমাত্র আপনার অনুসরণকারীদের নিকট পোস্ট করুন"
 | 
				
			||||||
 | 
					  specified: "ডাইরেক্ট নোট"
 | 
				
			||||||
 | 
					  specifiedDescription: "শুধুমাত্র নির্দিষ্ট ব্যাবহারকারীর নিকট পাঠান"
 | 
				
			||||||
 | 
					  localOnly: "শুধুমাত্র লোকাল"
 | 
				
			||||||
 | 
					  localOnlyDescription: "রিমোট ব্যাবহারকারীদের নিকট দৃশ্যমান নয়"
 | 
				
			||||||
 | 
					_postForm:
 | 
				
			||||||
 | 
					  replyPlaceholder: "নোটটির জবাব দিন..."
 | 
				
			||||||
 | 
					  quotePlaceholder: "নোটটিকে উদ্ধৃত করুন..."
 | 
				
			||||||
 | 
					  channelPlaceholder: "চ্যানেলে পোস্ট করুন..."
 | 
				
			||||||
 | 
					  _placeholders:
 | 
				
			||||||
 | 
					    a: "আপনি এখন কি করছেন?"
 | 
				
			||||||
 | 
					    b: "আপনার আশে পাশে কি হচ্ছে?"
 | 
				
			||||||
 | 
					    c: "আপনি কি ভাবছেন?"
 | 
				
			||||||
 | 
					    d: "আপনি কি বলতে চান?"
 | 
				
			||||||
 | 
					    e: "লেখা শুরু করুন..."
 | 
				
			||||||
 | 
					    f: "আপনার লেখার জন্য অপেক্ষা করছি..."
 | 
				
			||||||
_profile:
 | 
					_profile:
 | 
				
			||||||
  name: "নাম"
 | 
					  name: "নাম"
 | 
				
			||||||
  username: "ব্যবহারকারীর নাম"
 | 
					  username: "ব্যবহারকারীর নাম"
 | 
				
			||||||
 | 
					  description: "আপনার সম্পর্কে"
 | 
				
			||||||
 | 
					  youCanIncludeHashtags: "হ্যাশট্যাগ অন্তর্ভুক্ত করা যেতে পারে।"
 | 
				
			||||||
 | 
					  metadata: "অতিরিক্ত তথ্য"
 | 
				
			||||||
 | 
					  metadataEdit: "অতিরিক্ত তথ্য সম্পাদনা করুন"
 | 
				
			||||||
 | 
					  metadataDescription: "আপনি আপনার প্রোফাইলে একটি টেবিল হিসাবে চারটি অতিরিক্ত তথ্য দেখাতে পারেন।"
 | 
				
			||||||
 | 
					  metadataLabel: "লেবেল"
 | 
				
			||||||
 | 
					  metadataContent: "বিষয়বস্তু"
 | 
				
			||||||
 | 
					  changeAvatar: "অ্যাভাটার পরিবর্তন করুন"
 | 
				
			||||||
 | 
					  changeBanner: "ব্যানার পরিবর্তন করুন"
 | 
				
			||||||
_exportOrImport:
 | 
					_exportOrImport:
 | 
				
			||||||
  allNotes: "সকল নোট"
 | 
					  allNotes: "সকল নোট"
 | 
				
			||||||
  followingList: "অনুসরণ করা হচ্ছে"
 | 
					  followingList: "অনুসরণ করা হচ্ছে"
 | 
				
			||||||
  muteList: "মিউট"
 | 
					  muteList: "মিউট"
 | 
				
			||||||
  blockingList: "ব্লক"
 | 
					  blockingList: "ব্লক"
 | 
				
			||||||
  userLists: "লিস্ট"
 | 
					  userLists: "লিস্ট"
 | 
				
			||||||
 | 
					  excludeMutingUsers: "মিউটকৃত ব্যবহারকারীদের বাদ দিন"
 | 
				
			||||||
 | 
					  excludeInactiveUsers: "অব্যাবহৃত অ্যাকাউন্ট বাদ দিন"
 | 
				
			||||||
_charts:
 | 
					_charts:
 | 
				
			||||||
  federation: "ফেডিভার্স"
 | 
					  federation: "ফেডিভার্স"
 | 
				
			||||||
 | 
					  apRequest: "অনুরোধসমূহ"
 | 
				
			||||||
 | 
					  usersIncDec: "ব্যবহারকারীদের সংখ্যার পরিবর্তন"
 | 
				
			||||||
 | 
					  usersTotal: "ব্যবহারকারীদের সংখ্যা"
 | 
				
			||||||
 | 
					  activeUsers: "সক্রিয় ব্যাবহারকারী"
 | 
				
			||||||
 | 
					  notesIncDec: "নোটের সংখ্যার পরিবর্তন"
 | 
				
			||||||
 | 
					  localNotesIncDec: "লোকাল নোটের সংখ্যার পরিবর্তন"
 | 
				
			||||||
 | 
					  remoteNotesIncDec: "রিমোট নোটের সংখ্যার পরিবর্তন"
 | 
				
			||||||
 | 
					  notesTotal: "নোটের সংখ্যা"
 | 
				
			||||||
 | 
					  filesIncDec: "ফাইলের সংখ্যার পরিবর্তন"
 | 
				
			||||||
 | 
					  filesTotal: "ফাইলের সংখ্যা"
 | 
				
			||||||
 | 
					  storageUsageIncDec: "স্টোরেজের ব্যাবহারের পরিবর্তন"
 | 
				
			||||||
 | 
					  storageUsageTotal: "মোট স্টোরেজের ব্যাবহার"
 | 
				
			||||||
 | 
					_instanceCharts:
 | 
				
			||||||
 | 
					  requests: "অনুরোধসমূহ"
 | 
				
			||||||
 | 
					  users: "ব্যবহারকারীদের সংখ্যার পরিবর্তন"
 | 
				
			||||||
 | 
					  usersTotal: "ক্রমবর্ধমান ব্যবহারকারীদের সংখ্যা"
 | 
				
			||||||
 | 
					  notes: "নোটের সংখ্যার পরিবর্তন"
 | 
				
			||||||
 | 
					  notesTotal: "ক্রমবর্ধমান নোটের সংখ্যা"
 | 
				
			||||||
 | 
					  ff: "অনুসরণকারী / অনুসরণ করা ব্যাবহারকারীদের সংখ্যার পরিবর্তন"
 | 
				
			||||||
 | 
					  ffTotal: "অনুসরণকারী / অনুসরণ করা ব্যাবহারকারীদের ক্রমবর্ধমান সংখ্যা"
 | 
				
			||||||
 | 
					  cacheSize: "ক্যাশ সাইজের পরিবর্তন"
 | 
				
			||||||
 | 
					  cacheSizeTotal: "ক্রমবর্ধমান ক্যাশ সাইজ"
 | 
				
			||||||
 | 
					  files: "ফাইলের সংখ্যার পরিবর্তন"
 | 
				
			||||||
 | 
					  filesTotal: "ক্রমবর্ধমান ফাইলের সংখ্যা"
 | 
				
			||||||
_timelines:
 | 
					_timelines:
 | 
				
			||||||
  home: "মূল পাতা"
 | 
					  home: "মূল পাতা"
 | 
				
			||||||
 | 
					  local: "স্থানীয়"
 | 
				
			||||||
 | 
					  social: "সামাজিক"
 | 
				
			||||||
 | 
					  global: "গ্লোবাল"
 | 
				
			||||||
_pages:
 | 
					_pages:
 | 
				
			||||||
 | 
					  newPage: "নতুন পৃষ্ঠা বানান"
 | 
				
			||||||
 | 
					  editPage: "পৃষ্ঠাটি সম্পাদনা করুন"
 | 
				
			||||||
 | 
					  readPage: "উৎস দেখছেন"
 | 
				
			||||||
 | 
					  created: "পৃষ্ঠা তৈরি করা হয়েছে"
 | 
				
			||||||
 | 
					  updated: "পৃষ্ঠা সম্পাদনা করা হয়েছে"
 | 
				
			||||||
 | 
					  deleted: "পৃষ্ঠা মুছে ফেলা হয়েছে"
 | 
				
			||||||
 | 
					  pageSetting: "পৃষ্ঠার সেটিংস"
 | 
				
			||||||
 | 
					  nameAlreadyExists: "পৃষ্ঠার URLটি ইতিমধ্যেই ব্যাবহার করা হয়েছে"
 | 
				
			||||||
 | 
					  invalidNameTitle: "পৃষ্ঠার URL অবৈধ"
 | 
				
			||||||
 | 
					  invalidNameText: "নিশ্চিত করুন যে এটি ফাঁকা নয়"
 | 
				
			||||||
 | 
					  editThisPage: "পৃষ্ঠাটি সম্পাদনা করুন"
 | 
				
			||||||
 | 
					  viewSource: "উৎস দেখুন"
 | 
				
			||||||
  viewPage: "আপনার পেজগুলি দেখুন"
 | 
					  viewPage: "আপনার পেজগুলি দেখুন"
 | 
				
			||||||
 | 
					  like: "পছন্দ"
 | 
				
			||||||
 | 
					  unlike: "পছন্দ সরান"
 | 
				
			||||||
 | 
					  my: "আমার পৃষ্ঠাগুলি"
 | 
				
			||||||
 | 
					  liked: "পছন্দ করা পৃষ্ঠাগুলি"
 | 
				
			||||||
 | 
					  featured: "জনপ্রিয়"
 | 
				
			||||||
 | 
					  inspector: "ইনিস্পেক্টর"
 | 
				
			||||||
 | 
					  contents: "বিষয়বস্তু"
 | 
				
			||||||
 | 
					  content: "পৃষ্ঠার ব্লক"
 | 
				
			||||||
 | 
					  variables: "চলকগুলি"
 | 
				
			||||||
 | 
					  title: "শিরোনাম"
 | 
				
			||||||
 | 
					  url: "পৃষ্ঠার URL"
 | 
				
			||||||
 | 
					  summary: "পৃষ্ঠার বর্ণনা"
 | 
				
			||||||
 | 
					  alignCenter: "সেন্টার"
 | 
				
			||||||
 | 
					  hideTitleWhenPinned: "পিন করা হলে টাইটেল লুকান"
 | 
				
			||||||
 | 
					  font: "ফন্ট"
 | 
				
			||||||
 | 
					  fontSerif: "সেরিফ"
 | 
				
			||||||
 | 
					  fontSansSerif: "স্যান্স সেরিফ"
 | 
				
			||||||
 | 
					  eyeCatchingImageSet: "থাম্বনেইল সেট করুন"
 | 
				
			||||||
 | 
					  eyeCatchingImageRemove: "থাম্বনেইল সরান"
 | 
				
			||||||
 | 
					  chooseBlock: "ব্লক যোগ করুন"
 | 
				
			||||||
 | 
					  selectType: "ধরন নির্বাচন করুন"
 | 
				
			||||||
 | 
					  enterVariableName: "চলকের নাম লিখুন"
 | 
				
			||||||
 | 
					  variableNameIsAlreadyUsed: "চলকের নামটি ইতিপূর্বে ব্যাবহৃত হয়েছে"
 | 
				
			||||||
 | 
					  contentBlocks: "বিষয়বস্তু"
 | 
				
			||||||
 | 
					  inputBlocks: "ইনপুট"
 | 
				
			||||||
 | 
					  specialBlocks: "বিশেষ"
 | 
				
			||||||
  blocks:
 | 
					  blocks:
 | 
				
			||||||
 | 
					    text: "লেখা"
 | 
				
			||||||
 | 
					    textarea: "টেক্সট এরিয়া"
 | 
				
			||||||
 | 
					    section: "বিভাগ"
 | 
				
			||||||
    image: "ছবি"
 | 
					    image: "ছবি"
 | 
				
			||||||
 | 
					    button: "বাটন"
 | 
				
			||||||
 | 
					    if: "যদি"
 | 
				
			||||||
 | 
					    _if:
 | 
				
			||||||
 | 
					      variable: "চলকগুলি"
 | 
				
			||||||
 | 
					    post: "নোট লিখুন"
 | 
				
			||||||
 | 
					    _post:
 | 
				
			||||||
 | 
					      text: "বিষয়বস্তু"
 | 
				
			||||||
 | 
					      attachCanvasImage: "ক্যানভাস ছবিসহ পোস্ট করুন"
 | 
				
			||||||
 | 
					      canvasId: "ক্যানভাস ID"
 | 
				
			||||||
 | 
					    textInput: "টেক্সট ইনপুট"
 | 
				
			||||||
 | 
					    _textInput:
 | 
				
			||||||
 | 
					      name: "চলকের নাম"
 | 
				
			||||||
 | 
					      text: "শিরোনাম"
 | 
				
			||||||
 | 
					      default: "ডিফল্ট মান"
 | 
				
			||||||
 | 
					    textareaInput: "একাধিক লাইনের টেক্সট ইনপুট"
 | 
				
			||||||
 | 
					    _textareaInput:
 | 
				
			||||||
 | 
					      name: "চলকের নাম"
 | 
				
			||||||
 | 
					      text: "শিরোনাম"
 | 
				
			||||||
 | 
					      default: "ডিফল্ট মান"
 | 
				
			||||||
 | 
					    numberInput: "সংখ্যা ইনপুট"
 | 
				
			||||||
 | 
					    _numberInput:
 | 
				
			||||||
 | 
					      name: "চলকের নাম"
 | 
				
			||||||
 | 
					      text: "শিরোনাম"
 | 
				
			||||||
 | 
					      default: "ডিফল্ট মান"
 | 
				
			||||||
 | 
					    canvas: "ক্যানভাস"
 | 
				
			||||||
 | 
					    _canvas:
 | 
				
			||||||
 | 
					      id: "ক্যানভাস ID"
 | 
				
			||||||
 | 
					      width: "প্রস্থ"
 | 
				
			||||||
 | 
					      height: "উচ্চতা"
 | 
				
			||||||
 | 
					    note: "এম্বেড নোট"
 | 
				
			||||||
 | 
					    _note:
 | 
				
			||||||
 | 
					      id: "নোট ID"
 | 
				
			||||||
 | 
					      idDescription: "আপনি এর বদলে নোটের URL পেস্ট করতে পারেন."
 | 
				
			||||||
 | 
					      detailed: "বিস্তারিত দেখুন"
 | 
				
			||||||
 | 
					    switch: "সুইচ"
 | 
				
			||||||
 | 
					    _switch:
 | 
				
			||||||
 | 
					      name: "চলকের নাম"
 | 
				
			||||||
 | 
					      text: "শিরোনাম"
 | 
				
			||||||
 | 
					      default: "ডিফল্ট মান"
 | 
				
			||||||
 | 
					    counter: "কাউন্টার"
 | 
				
			||||||
 | 
					    _counter:
 | 
				
			||||||
 | 
					      name: "চলকের নাম"
 | 
				
			||||||
 | 
					      text: "শিরোনাম"
 | 
				
			||||||
 | 
					      inc: "এভাবে মান বাড়ান"
 | 
				
			||||||
 | 
					    _button:
 | 
				
			||||||
 | 
					      text: "শিরোনাম"
 | 
				
			||||||
 | 
					      colored: "রঙ্গিন"
 | 
				
			||||||
 | 
					      action: "বাটনে ক্লিক করলে যা হবে"
 | 
				
			||||||
 | 
					      _action:
 | 
				
			||||||
 | 
					        dialog: "ডায়ালগ দেখান "
 | 
				
			||||||
 | 
					        _dialog:
 | 
				
			||||||
 | 
					          content: "বিষয়বস্তু"
 | 
				
			||||||
 | 
					        resetRandom: "র্যানডম সিড রিসেট করুন"
 | 
				
			||||||
 | 
					        pushEvent: "ইভেন্ট পাঠান"
 | 
				
			||||||
 | 
					        _pushEvent:
 | 
				
			||||||
 | 
					          event: "ইভেন্টের নাম"
 | 
				
			||||||
 | 
					          message: "চালু হলে প্রদর্শনের জন্য বার্তা"
 | 
				
			||||||
 | 
					          variable: "পাঠানো চলক"
 | 
				
			||||||
 | 
					          no-variable: "কিছুই না"
 | 
				
			||||||
 | 
					        callAiScript: "AiScript চালান"
 | 
				
			||||||
 | 
					        _callAiScript:
 | 
				
			||||||
 | 
					          functionName: "ফাংশনের নাম"
 | 
				
			||||||
 | 
					    radioButton: "বহুনির্বাচনী"
 | 
				
			||||||
 | 
					    _radioButton:
 | 
				
			||||||
 | 
					      name: "চলকের নাম"
 | 
				
			||||||
 | 
					      title: "শিরোনাম"
 | 
				
			||||||
 | 
					      values: "বিকল্পগুলিকে আলাদা লাইনে লিখুন"
 | 
				
			||||||
 | 
					      default: "ডিফল্ট মান"
 | 
				
			||||||
  script:
 | 
					  script:
 | 
				
			||||||
    categories:
 | 
					    categories:
 | 
				
			||||||
 | 
					      flow: "নিয়ন্ত্রণ"
 | 
				
			||||||
 | 
					      logical: "লজিক্যাল অপারেশন"
 | 
				
			||||||
 | 
					      operation: "হিসাব-নিকাশ"
 | 
				
			||||||
 | 
					      comparison: "তুলনা"
 | 
				
			||||||
 | 
					      random: "র্যান্ডম"
 | 
				
			||||||
 | 
					      value: "মান"
 | 
				
			||||||
 | 
					      fn: "ফাংশন"
 | 
				
			||||||
 | 
					      text: "টেক্সট ম্যানিপুলেশন"
 | 
				
			||||||
 | 
					      convert: "রুপান্তর"
 | 
				
			||||||
      list: "লিস্ট"
 | 
					      list: "লিস্ট"
 | 
				
			||||||
    blocks:
 | 
					    blocks:
 | 
				
			||||||
 | 
					      text: "লেখা"
 | 
				
			||||||
 | 
					      multiLineText: "লেখা (একাধিক লাইন)"
 | 
				
			||||||
 | 
					      textList: "লেখার লিস্ট"
 | 
				
			||||||
 | 
					      _textList:
 | 
				
			||||||
 | 
					        info: "প্রতিটি এন্ট্রিকে আলাদা লাইনে লিখুন"
 | 
				
			||||||
 | 
					      strLen: "লেখার দৈর্ঘ্য"
 | 
				
			||||||
 | 
					      _strLen:
 | 
				
			||||||
 | 
					        arg1: "লেখা"
 | 
				
			||||||
 | 
					      strPick: "অক্ষর বের করে আনুন"
 | 
				
			||||||
 | 
					      _strPick:
 | 
				
			||||||
 | 
					        arg1: "লেখা"
 | 
				
			||||||
 | 
					        arg2: "অক্ষরের অবস্থান"
 | 
				
			||||||
 | 
					      strReplace: "লেখা প্রতিস্থাপন"
 | 
				
			||||||
 | 
					      _strReplace:
 | 
				
			||||||
 | 
					        arg1: "লেখা"
 | 
				
			||||||
 | 
					        arg2: "যে লেখা প্রতিস্থাপন করা হবে"
 | 
				
			||||||
 | 
					        arg3: "যা দ্বারা প্রতিস্থাপন করা হবে"
 | 
				
			||||||
 | 
					      strReverse: "লেখা উল্টান"
 | 
				
			||||||
 | 
					      _strReverse:
 | 
				
			||||||
 | 
					        arg1: "লেখা"
 | 
				
			||||||
 | 
					      join: "লেখা যুক্ত করুন"
 | 
				
			||||||
      _join:
 | 
					      _join:
 | 
				
			||||||
        arg1: "লিস্ট"
 | 
					        arg1: "লিস্ট"
 | 
				
			||||||
 | 
					        arg2: "বিভাজক"
 | 
				
			||||||
 | 
					      add: "যোগ"
 | 
				
			||||||
 | 
					      _add:
 | 
				
			||||||
 | 
					        arg1: "A"
 | 
				
			||||||
 | 
					        arg2: "B"
 | 
				
			||||||
 | 
					      subtract: "বিয়োগ"
 | 
				
			||||||
 | 
					      _subtract:
 | 
				
			||||||
 | 
					        arg1: "A"
 | 
				
			||||||
 | 
					        arg2: "B"
 | 
				
			||||||
 | 
					      multiply: "গুন"
 | 
				
			||||||
 | 
					      _multiply:
 | 
				
			||||||
 | 
					        arg1: "A"
 | 
				
			||||||
 | 
					        arg2: "B"
 | 
				
			||||||
 | 
					      divide: "ভাগ"
 | 
				
			||||||
 | 
					      _divide:
 | 
				
			||||||
 | 
					        arg1: "A"
 | 
				
			||||||
 | 
					        arg2: "B"
 | 
				
			||||||
 | 
					      mod: "ভাগশেষ"
 | 
				
			||||||
 | 
					      _mod:
 | 
				
			||||||
 | 
					        arg1: "A"
 | 
				
			||||||
 | 
					        arg2: "B"
 | 
				
			||||||
 | 
					      round: "দশমিক রাউন্ড করুন"
 | 
				
			||||||
 | 
					      _round:
 | 
				
			||||||
 | 
					        arg1: "সংখ্যা"
 | 
				
			||||||
 | 
					      eq: "A ও B সমান"
 | 
				
			||||||
 | 
					      _eq:
 | 
				
			||||||
 | 
					        arg1: "A"
 | 
				
			||||||
 | 
					        arg2: "B"
 | 
				
			||||||
 | 
					      notEq: "A ও B সমান না"
 | 
				
			||||||
 | 
					      _notEq:
 | 
				
			||||||
 | 
					        arg1: "A"
 | 
				
			||||||
 | 
					        arg2: "B"
 | 
				
			||||||
 | 
					      and: "A এবং B"
 | 
				
			||||||
 | 
					      _and:
 | 
				
			||||||
 | 
					        arg1: "A"
 | 
				
			||||||
 | 
					        arg2: "B"
 | 
				
			||||||
 | 
					      or: "A অথবা B"
 | 
				
			||||||
 | 
					      _or:
 | 
				
			||||||
 | 
					        arg1: "A"
 | 
				
			||||||
 | 
					        arg2: "B"
 | 
				
			||||||
 | 
					      lt: "< A , B হতে কম"
 | 
				
			||||||
 | 
					      _lt:
 | 
				
			||||||
 | 
					        arg1: "A"
 | 
				
			||||||
 | 
					        arg2: "B"
 | 
				
			||||||
 | 
					      gt: "> A , B হতে বেশী"
 | 
				
			||||||
 | 
					      _gt:
 | 
				
			||||||
 | 
					        arg1: "A"
 | 
				
			||||||
 | 
					        arg2: "B"
 | 
				
			||||||
 | 
					      ltEq: "<= A , B হতে কম বা সমান"
 | 
				
			||||||
 | 
					      _ltEq:
 | 
				
			||||||
 | 
					        arg1: "A"
 | 
				
			||||||
 | 
					        arg2: "B"
 | 
				
			||||||
 | 
					      gtEq: ">= A , B হতে বেশী বা সমান"
 | 
				
			||||||
 | 
					      _gtEq:
 | 
				
			||||||
 | 
					        arg1: "A"
 | 
				
			||||||
 | 
					        arg2: "B"
 | 
				
			||||||
 | 
					      if: "যদি"
 | 
				
			||||||
 | 
					      _if:
 | 
				
			||||||
 | 
					        arg1: "যদি"
 | 
				
			||||||
 | 
					        arg2: "তাহলে"
 | 
				
			||||||
 | 
					        arg3: "তাছাড়া"
 | 
				
			||||||
 | 
					      not: "না"
 | 
				
			||||||
 | 
					      _not:
 | 
				
			||||||
 | 
					        arg1: "না"
 | 
				
			||||||
 | 
					      random: "র্যান্ডম"
 | 
				
			||||||
 | 
					      _random:
 | 
				
			||||||
 | 
					        arg1: "সম্ভাব্যতা"
 | 
				
			||||||
 | 
					      rannum: "র্যানডম সংখ্যা"
 | 
				
			||||||
 | 
					      _rannum:
 | 
				
			||||||
 | 
					        arg1: "ন্যূনতম মান"
 | 
				
			||||||
 | 
					        arg2: "সর্বোচ্চ মান"
 | 
				
			||||||
 | 
					      randomPick: "তালিকা থেকে দৈবচয়ন করুন"
 | 
				
			||||||
      _randomPick:
 | 
					      _randomPick:
 | 
				
			||||||
        arg1: "লিস্ট"
 | 
					        arg1: "লিস্ট"
 | 
				
			||||||
 | 
					      dailyRandom: "র্যান্ডম সংখ্যা (প্রতিটি ব্যবহারকারীর জন্য প্রতিদিন পরিবর্তীত হয়)"
 | 
				
			||||||
      _dailyRandom:
 | 
					      _dailyRandom:
 | 
				
			||||||
        arg1: "সম্ভাব্যতা"
 | 
					        arg1: "সম্ভাব্যতা"
 | 
				
			||||||
      dailyRannum: "র্যান্ডম সংখ্যা (প্রতিটি ব্যবহারকারীর জন্য প্রতিদিন পরিবর্তীত হয়)"
 | 
					      dailyRannum: "র্যান্ডম সংখ্যা (প্রতিটি ব্যবহারকারীর জন্য প্রতিদিন পরিবর্তীত হয়)"
 | 
				
			||||||
      _dailyRannum:
 | 
					      _dailyRannum:
 | 
				
			||||||
        arg1: "ন্যূনতম মান"
 | 
					        arg1: "ন্যূনতম মান"
 | 
				
			||||||
        arg2: "সর্বোচ্চ মান"
 | 
					        arg2: "সর্বোচ্চ মান"
 | 
				
			||||||
 | 
					      dailyRandomPick: "তালিকা থেকে এলোমেলোভাবে নির্বাচন করুন (প্রতিটি ব্যবহারকারীর জন্য প্রতিদিন পরিবর্তীত হয়)"
 | 
				
			||||||
      _dailyRandomPick:
 | 
					      _dailyRandomPick:
 | 
				
			||||||
        arg1: "লিস্ট"
 | 
					        arg1: "লিস্ট"
 | 
				
			||||||
 | 
					      seedRandom: "র্যানডম (সীড দ্বারা)"
 | 
				
			||||||
 | 
					      _seedRandom:
 | 
				
			||||||
 | 
					        arg1: "সীড"
 | 
				
			||||||
 | 
					        arg2: "সম্ভাব্যতা"
 | 
				
			||||||
 | 
					      seedRannum: "র্যানডম সংখ্যা (সীড দ্বারা)"
 | 
				
			||||||
 | 
					      _seedRannum:
 | 
				
			||||||
 | 
					        arg1: "সীড"
 | 
				
			||||||
 | 
					        arg2: "ন্যূনতম মান"
 | 
				
			||||||
 | 
					        arg3: "সর্বোচ্চ মান"
 | 
				
			||||||
 | 
					      seedRandomPick: "তালিকা থেকে দৈবচয়ন করুন (সীড দ্বারা)"
 | 
				
			||||||
      _seedRandomPick:
 | 
					      _seedRandomPick:
 | 
				
			||||||
 | 
					        arg1: "সীড"
 | 
				
			||||||
        arg2: "লিস্ট"
 | 
					        arg2: "লিস্ট"
 | 
				
			||||||
 | 
					      DRPWPM: "সম্ভাব্যতা সহ একটি তালিকা থেকে এলোমেলোভাবে নির্বাচন করুন (প্রতিটি ব্যবহারকারীর জন্য প্রতিদিন)"
 | 
				
			||||||
 | 
					      _DRPWPM:
 | 
				
			||||||
 | 
					        arg1: "লেখার লিস্ট"
 | 
				
			||||||
 | 
					      pick: "তালিকা থেকে নির্বাচন করুন"
 | 
				
			||||||
      _pick:
 | 
					      _pick:
 | 
				
			||||||
        arg1: "লিস্ট"
 | 
					        arg1: "লিস্ট"
 | 
				
			||||||
 | 
					        arg2: "অবস্থান"
 | 
				
			||||||
 | 
					      listLen: "লিস্টের দৈর্ঘ্য পান"
 | 
				
			||||||
      _listLen:
 | 
					      _listLen:
 | 
				
			||||||
        arg1: "লিস্ট"
 | 
					        arg1: "লিস্ট"
 | 
				
			||||||
 | 
					      number: "সংখ্যা"
 | 
				
			||||||
 | 
					      stringToNumber: "পাঠ্য থেকে সংখ্যা"
 | 
				
			||||||
 | 
					      _stringToNumber:
 | 
				
			||||||
 | 
					        arg1: "লেখা"
 | 
				
			||||||
 | 
					      numberToString: "সংখ্যা থেকে পাঠ্য"
 | 
				
			||||||
      _numberToString:
 | 
					      _numberToString:
 | 
				
			||||||
        arg1: "সংখ্যা"
 | 
					        arg1: "সংখ্যা"
 | 
				
			||||||
      splitStrByLine: "পাঠ্যকে লাইনে বিভক্ত করুন"
 | 
					      splitStrByLine: "পাঠ্যকে লাইনে বিভক্ত করুন"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -595,6 +595,8 @@ smtpSecure: "Für SMTP-Verbindungen implizit SSL/TLS verwenden"
 | 
				
			||||||
smtpSecureInfo: "Schalte dies aus, falls du STARTTLS verwendest"
 | 
					smtpSecureInfo: "Schalte dies aus, falls du STARTTLS verwendest"
 | 
				
			||||||
testEmail: "Email-Versand testen"
 | 
					testEmail: "Email-Versand testen"
 | 
				
			||||||
wordMute: "Wort-Stummschaltung"
 | 
					wordMute: "Wort-Stummschaltung"
 | 
				
			||||||
 | 
					regexpError: "Regular Expression error"
 | 
				
			||||||
 | 
					regexpErrorDescription: "Error in the regular expression on line {line} in your {tab} word mutes:"
 | 
				
			||||||
instanceMute: "Instanzstummschaltungen"
 | 
					instanceMute: "Instanzstummschaltungen"
 | 
				
			||||||
userSaysSomething: "{name} hat etwas gesagt"
 | 
					userSaysSomething: "{name} hat etwas gesagt"
 | 
				
			||||||
makeActive: "Aktivieren"
 | 
					makeActive: "Aktivieren"
 | 
				
			||||||
| 
						 | 
					@ -828,6 +830,9 @@ overridedDeviceKind: "Gerätetyp"
 | 
				
			||||||
smartphone: "Smartphone"
 | 
					smartphone: "Smartphone"
 | 
				
			||||||
tablet: "Tablet"
 | 
					tablet: "Tablet"
 | 
				
			||||||
auto: "Automatisch"
 | 
					auto: "Automatisch"
 | 
				
			||||||
 | 
					themeColor: "Instanzfarbe"
 | 
				
			||||||
 | 
					size: "Größe"
 | 
				
			||||||
 | 
					numberOfColumn: "Spaltenanzahl"
 | 
				
			||||||
_emailUnavailable:
 | 
					_emailUnavailable:
 | 
				
			||||||
  used: "Diese Email-Adresse wird bereits verwendet"
 | 
					  used: "Diese Email-Adresse wird bereits verwendet"
 | 
				
			||||||
  format: "Das Format dieser Email-Adresse ist ungültig"
 | 
					  format: "Das Format dieser Email-Adresse ist ungültig"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -828,6 +828,9 @@ overridedDeviceKind: "Device type"
 | 
				
			||||||
smartphone: "Smartphone"
 | 
					smartphone: "Smartphone"
 | 
				
			||||||
tablet: "Tablet"
 | 
					tablet: "Tablet"
 | 
				
			||||||
auto: "Auto"
 | 
					auto: "Auto"
 | 
				
			||||||
 | 
					themeColor: "Theme Color"
 | 
				
			||||||
 | 
					size: "Size"
 | 
				
			||||||
 | 
					numberOfColumn: "Number of columns"
 | 
				
			||||||
_emailUnavailable:
 | 
					_emailUnavailable:
 | 
				
			||||||
  used: "This email address is already being used"
 | 
					  used: "This email address is already being used"
 | 
				
			||||||
  format: "The format of this email address is invalid"
 | 
					  format: "The format of this email address is invalid"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,7 @@ username: "Uzantnomo"
 | 
				
			||||||
password: "Pasvorto"
 | 
					password: "Pasvorto"
 | 
				
			||||||
forgotPassword: "Ĉu vi forgesis pasvorton?"
 | 
					forgotPassword: "Ĉu vi forgesis pasvorton?"
 | 
				
			||||||
fetchingAsApObject: "Informpetado de la Fediverso…"
 | 
					fetchingAsApObject: "Informpetado de la Fediverso…"
 | 
				
			||||||
ok: "OK"
 | 
					ok: "Bone"
 | 
				
			||||||
gotIt: "Kompreni"
 | 
					gotIt: "Kompreni"
 | 
				
			||||||
cancel: "Nuligi"
 | 
					cancel: "Nuligi"
 | 
				
			||||||
enterUsername: "Entajpu uzantnomon"
 | 
					enterUsername: "Entajpu uzantnomon"
 | 
				
			||||||
| 
						 | 
					@ -75,7 +75,7 @@ following: "Sekvata"
 | 
				
			||||||
followers: "Sekvantoj"
 | 
					followers: "Sekvantoj"
 | 
				
			||||||
followsYou: "Sekvas vin"
 | 
					followsYou: "Sekvas vin"
 | 
				
			||||||
createList: "Krei liston"
 | 
					createList: "Krei liston"
 | 
				
			||||||
manageLists: "Bonteni liston"
 | 
					manageLists: "Bonteni la listojn"
 | 
				
			||||||
error: "Eraro"
 | 
					error: "Eraro"
 | 
				
			||||||
somethingHappened: "Problemo okazis"
 | 
					somethingHappened: "Problemo okazis"
 | 
				
			||||||
retry: "Provi denove"
 | 
					retry: "Provi denove"
 | 
				
			||||||
| 
						 | 
					@ -137,6 +137,7 @@ settingGuide: "Agordaj rekomendoj"
 | 
				
			||||||
cacheRemoteFiles: "Stapli forajn dosierojn"
 | 
					cacheRemoteFiles: "Stapli forajn dosierojn"
 | 
				
			||||||
flagAsBot: "Marki kiel esti uzanto de roboto"
 | 
					flagAsBot: "Marki kiel esti uzanto de roboto"
 | 
				
			||||||
flagAsCat: "Marki kiel esti kato"
 | 
					flagAsCat: "Marki kiel esti kato"
 | 
				
			||||||
 | 
					flagAsCatDescription: "Flagu por montri ke la konton havas kato."
 | 
				
			||||||
flagShowTimelineReplies: "Montri respondon de notoj en templinio."
 | 
					flagShowTimelineReplies: "Montri respondon de notoj en templinio."
 | 
				
			||||||
autoAcceptFollowed: "Aŭtomate akcepti la peton de sekvado far uzantoj kiujn vi sekvas"
 | 
					autoAcceptFollowed: "Aŭtomate akcepti la peton de sekvado far uzantoj kiujn vi sekvas"
 | 
				
			||||||
addAccount: "Aldoni konton"
 | 
					addAccount: "Aldoni konton"
 | 
				
			||||||
| 
						 | 
					@ -184,7 +185,7 @@ noUsers: "Neniu uzanto"
 | 
				
			||||||
editProfile: "Redakti profilon"
 | 
					editProfile: "Redakti profilon"
 | 
				
			||||||
noteDeleteConfirm: "Ĉu vi certas ke vi volas forviŝi la noton?"
 | 
					noteDeleteConfirm: "Ĉu vi certas ke vi volas forviŝi la noton?"
 | 
				
			||||||
pinLimitExceeded: "Vi ne povas alpingli pli"
 | 
					pinLimitExceeded: "Vi ne povas alpingli pli"
 | 
				
			||||||
intro: "Instalado de Misskey finiĝis! Kreu administran konton."
 | 
					intro: "La instalado de Misskey finiĝis! Volu krei administran konton."
 | 
				
			||||||
done: "Fini"
 | 
					done: "Fini"
 | 
				
			||||||
processing: "Prilaborado…"
 | 
					processing: "Prilaborado…"
 | 
				
			||||||
preview: "Antaŭmontro"
 | 
					preview: "Antaŭmontro"
 | 
				
			||||||
| 
						 | 
					@ -223,6 +224,7 @@ resetAreYouSure: "Ĉu vi certas restarigi?"
 | 
				
			||||||
saved: "Konservita"
 | 
					saved: "Konservita"
 | 
				
			||||||
messaging: "Retbabili"
 | 
					messaging: "Retbabili"
 | 
				
			||||||
upload: "Alŝuti"
 | 
					upload: "Alŝuti"
 | 
				
			||||||
 | 
					keepOriginalUploading: "Konservi la originalan bildon"
 | 
				
			||||||
fromDrive: "De la disko"
 | 
					fromDrive: "De la disko"
 | 
				
			||||||
fromUrl: "De URL"
 | 
					fromUrl: "De URL"
 | 
				
			||||||
uploadFromUrl: "Alŝuti de URL"
 | 
					uploadFromUrl: "Alŝuti de URL"
 | 
				
			||||||
| 
						 | 
					@ -491,7 +493,7 @@ deletedNote: "Forviŝita noto"
 | 
				
			||||||
invisibleNote: "Malpublikigita noto"
 | 
					invisibleNote: "Malpublikigita noto"
 | 
				
			||||||
enableInfiniteScroll: "Ebligi infinitan rulumon"
 | 
					enableInfiniteScroll: "Ebligi infinitan rulumon"
 | 
				
			||||||
visibility: "Videbleco"
 | 
					visibility: "Videbleco"
 | 
				
			||||||
poll: "Balotujo"
 | 
					poll: "Enketo"
 | 
				
			||||||
useCw: "Kaŝi enhavo"
 | 
					useCw: "Kaŝi enhavo"
 | 
				
			||||||
enablePlayer: "Vidigi la filmeton"
 | 
					enablePlayer: "Vidigi la filmeton"
 | 
				
			||||||
disablePlayer: "Malfermi la filmeton"
 | 
					disablePlayer: "Malfermi la filmeton"
 | 
				
			||||||
| 
						 | 
					@ -537,7 +539,7 @@ overview: "Resumo"
 | 
				
			||||||
logs: "Protokoloj"
 | 
					logs: "Protokoloj"
 | 
				
			||||||
delayed: "Prokrasto "
 | 
					delayed: "Prokrasto "
 | 
				
			||||||
database: "Datumbazo"
 | 
					database: "Datumbazo"
 | 
				
			||||||
channel: "Kanalo"
 | 
					channel: "Kanaloj"
 | 
				
			||||||
create: "Krei"
 | 
					create: "Krei"
 | 
				
			||||||
notificationSetting: "Agordoj de sciigoj"
 | 
					notificationSetting: "Agordoj de sciigoj"
 | 
				
			||||||
useGlobalSetting: "Oni uzas malloka agordo"
 | 
					useGlobalSetting: "Oni uzas malloka agordo"
 | 
				
			||||||
| 
						 | 
					@ -560,6 +562,7 @@ createNew: "Krei novan"
 | 
				
			||||||
optional: "Opciaj"
 | 
					optional: "Opciaj"
 | 
				
			||||||
public: "Publika"
 | 
					public: "Publika"
 | 
				
			||||||
i18nInfo: "Misskey estas tradukata en diversaj lingvoj de volontuloj. Oni povas kontribui ĉe {link}."
 | 
					i18nInfo: "Misskey estas tradukata en diversaj lingvoj de volontuloj. Oni povas kontribui ĉe {link}."
 | 
				
			||||||
 | 
					manageAccessTokens: "Bonteni la aŭtentikigajn pecojn"
 | 
				
			||||||
accountInfo: "Kontaj Informoj"
 | 
					accountInfo: "Kontaj Informoj"
 | 
				
			||||||
notesCount: "La nombro de notoj"
 | 
					notesCount: "La nombro de notoj"
 | 
				
			||||||
repliesCount: "La nombro de respondoj senditaj"
 | 
					repliesCount: "La nombro de respondoj senditaj"
 | 
				
			||||||
| 
						 | 
					@ -571,6 +574,7 @@ followersCount: "La nombro de sekvantoj"
 | 
				
			||||||
sentReactionsCount: "La nombro de la reagoj senditaj"
 | 
					sentReactionsCount: "La nombro de la reagoj senditaj"
 | 
				
			||||||
receivedReactionsCount: "La nombro de la reagoj ricevitaj"
 | 
					receivedReactionsCount: "La nombro de la reagoj ricevitaj"
 | 
				
			||||||
pollVotesCount: "Nombro de voĉdonado"
 | 
					pollVotesCount: "Nombro de voĉdonado"
 | 
				
			||||||
 | 
					pollVotedCount: "La nombro de la voĉoj ricevitaj en siaj enketoj"
 | 
				
			||||||
yes: "Jes"
 | 
					yes: "Jes"
 | 
				
			||||||
no: "Ne"
 | 
					no: "Ne"
 | 
				
			||||||
driveFilesCount: "La nombro de la dosieroj sur la disko"
 | 
					driveFilesCount: "La nombro de la dosieroj sur la disko"
 | 
				
			||||||
| 
						 | 
					@ -615,7 +619,7 @@ inUse: "Uzata"
 | 
				
			||||||
editCode: "Redakti kodon"
 | 
					editCode: "Redakti kodon"
 | 
				
			||||||
receiveAnnouncementFromInstance: "Ricevi informojn sciigintajn de la nodo"
 | 
					receiveAnnouncementFromInstance: "Ricevi informojn sciigintajn de la nodo"
 | 
				
			||||||
emailNotification: "Sciigoj per retpoŝto"
 | 
					emailNotification: "Sciigoj per retpoŝto"
 | 
				
			||||||
inChannelSearch: "Serĉi en kanalo"
 | 
					inChannelSearch: "Serĉi en la kanalo"
 | 
				
			||||||
useReactionPickerForContextMenu: "Dekstre-klaki por malfermi la elektilon de reagoj"
 | 
					useReactionPickerForContextMenu: "Dekstre-klaki por malfermi la elektilon de reagoj"
 | 
				
			||||||
typingUsers: "{users} nun skribas…"
 | 
					typingUsers: "{users} nun skribas…"
 | 
				
			||||||
clear: "Vakigi"
 | 
					clear: "Vakigi"
 | 
				
			||||||
| 
						 | 
					@ -635,7 +639,7 @@ administration: "Bontenado"
 | 
				
			||||||
accounts: "Kontoj"
 | 
					accounts: "Kontoj"
 | 
				
			||||||
configure: "Agordi"
 | 
					configure: "Agordi"
 | 
				
			||||||
recentPosts: "Novaj afiŝoj"
 | 
					recentPosts: "Novaj afiŝoj"
 | 
				
			||||||
popularPosts: "Populara noto"
 | 
					popularPosts: "Plej viditaj"
 | 
				
			||||||
shareWithNote: "Kundividi en noto"
 | 
					shareWithNote: "Kundividi en noto"
 | 
				
			||||||
ads: "Reklamaĵo"
 | 
					ads: "Reklamaĵo"
 | 
				
			||||||
expiration: "Limtempo"
 | 
					expiration: "Limtempo"
 | 
				
			||||||
| 
						 | 
					@ -646,6 +650,7 @@ low: "Malalta"
 | 
				
			||||||
emailNotConfiguredWarning: "Vi ne agordis retpoŝtadreso."
 | 
					emailNotConfiguredWarning: "Vi ne agordis retpoŝtadreso."
 | 
				
			||||||
customCss: "Personecigita CSS"
 | 
					customCss: "Personecigita CSS"
 | 
				
			||||||
global: "Malloka"
 | 
					global: "Malloka"
 | 
				
			||||||
 | 
					squareAvatars: "Montri bildsimbolon kiel kvadrata"
 | 
				
			||||||
sent: "Sendi"
 | 
					sent: "Sendi"
 | 
				
			||||||
received: "Ricevita"
 | 
					received: "Ricevita"
 | 
				
			||||||
searchResult: "Serĉorezultoj"
 | 
					searchResult: "Serĉorezultoj"
 | 
				
			||||||
| 
						 | 
					@ -674,10 +679,14 @@ ffVisibilityDescription: "Oni permesas agordi tiuln kiuj povas vidi la homojn ki
 | 
				
			||||||
continueThread: "Pli vidi la mesaĝaron"
 | 
					continueThread: "Pli vidi la mesaĝaron"
 | 
				
			||||||
incorrectPassword: "Nevalida pasvorto"
 | 
					incorrectPassword: "Nevalida pasvorto"
 | 
				
			||||||
voteConfirm: "Ĉu vi voĉdonas {choice}n?"
 | 
					voteConfirm: "Ĉu vi voĉdonas {choice}n?"
 | 
				
			||||||
 | 
					hide: "Kaŝi"
 | 
				
			||||||
leaveGroup: "Eliĝi el la grupo"
 | 
					leaveGroup: "Eliĝi el la grupo"
 | 
				
			||||||
leaveGroupConfirm: "Ĉu vi certas ke vi volas eliĝi el la grupo {name}?"
 | 
					leaveGroupConfirm: "Ĉu vi certas ke vi volas eliĝi el la grupo {name}?"
 | 
				
			||||||
welcomeBackWithName: "Bonrevenon, {name}!"
 | 
					welcomeBackWithName: "Bonrevenon, {name}!"
 | 
				
			||||||
clickToFinishEmailVerification: "Volu klaki [{ok}] por fini la konfirmon de vian retadreson"
 | 
					clickToFinishEmailVerification: "Volu klaki [{ok}] por fini la konfirmon de vian retadreson"
 | 
				
			||||||
 | 
					smartphone: "Saĝtelefono"
 | 
				
			||||||
 | 
					tablet: "Platkomputilo"
 | 
				
			||||||
 | 
					auto: "Aŭtomate"
 | 
				
			||||||
_emailUnavailable:
 | 
					_emailUnavailable:
 | 
				
			||||||
  used: "La retpoŝto jam estas uzita."
 | 
					  used: "La retpoŝto jam estas uzita."
 | 
				
			||||||
  format: "Nevalida formato."
 | 
					  format: "Nevalida formato."
 | 
				
			||||||
| 
						 | 
					@ -688,6 +697,7 @@ _ffVisibility:
 | 
				
			||||||
  followers: "Nur al sekvantoj"
 | 
					  followers: "Nur al sekvantoj"
 | 
				
			||||||
  private: "Malpublikigita"
 | 
					  private: "Malpublikigita"
 | 
				
			||||||
_signup:
 | 
					_signup:
 | 
				
			||||||
 | 
					  almostThere: "Preskaŭ plenumita"
 | 
				
			||||||
  emailAddressInfo: "Entajpu vian retpoŝton"
 | 
					  emailAddressInfo: "Entajpu vian retpoŝton"
 | 
				
			||||||
_accountDelete:
 | 
					_accountDelete:
 | 
				
			||||||
  accountDelete: "Forigi konton"
 | 
					  accountDelete: "Forigi konton"
 | 
				
			||||||
| 
						 | 
					@ -698,7 +708,7 @@ _ad:
 | 
				
			||||||
_forgotPassword:
 | 
					_forgotPassword:
 | 
				
			||||||
  enterEmail: "Entajpu la retpoŝton kiun vi registrigis al via konto. Ligilo por restarigi pasvorton estos sendita al la retadreso."
 | 
					  enterEmail: "Entajpu la retpoŝton kiun vi registrigis al via konto. Ligilo por restarigi pasvorton estos sendita al la retadreso."
 | 
				
			||||||
_gallery:
 | 
					_gallery:
 | 
				
			||||||
  my: "Mia afiŝo"
 | 
					  my: "Miaj afiŝoj"
 | 
				
			||||||
  liked: "Ŝatitaj notoj"
 | 
					  liked: "Ŝatitaj notoj"
 | 
				
			||||||
  like: "Ŝati"
 | 
					  like: "Ŝati"
 | 
				
			||||||
_email:
 | 
					_email:
 | 
				
			||||||
| 
						 | 
					@ -877,14 +887,18 @@ _cw:
 | 
				
			||||||
  chars: "{count} literoj"
 | 
					  chars: "{count} literoj"
 | 
				
			||||||
  files: "{count} dosiero(j)"
 | 
					  files: "{count} dosiero(j)"
 | 
				
			||||||
_poll:
 | 
					_poll:
 | 
				
			||||||
  choiceN: "Balotilo {n}"
 | 
					  choiceN: "Ebla voĉdono {n}"
 | 
				
			||||||
  noMore: "Oni ne povas aldoni pli."
 | 
					  noMore: "Oni ne povas aldoni pli"
 | 
				
			||||||
 | 
					  canMultipleVote: "Permesi plurelekton"
 | 
				
			||||||
  expiration: "Limtempo"
 | 
					  expiration: "Limtempo"
 | 
				
			||||||
  infinite: "Neniam"
 | 
					 | 
				
			||||||
  deadlineTime: "hor"
 | 
					  deadlineTime: "hor"
 | 
				
			||||||
  votesCount: "{n} balotiloj"
 | 
					  duration: "Daŭro"
 | 
				
			||||||
  vote: "Baloti"
 | 
					  votesCount: "{n} voĉoj"
 | 
				
			||||||
  closed: "Oni jam balotis ĝin"
 | 
					  totalVotes: "Sume {n} voĉoj"
 | 
				
			||||||
 | 
					  vote: "Voĉdoni"
 | 
				
			||||||
 | 
					  showResult: "Vidi la rezultojn"
 | 
				
			||||||
 | 
					  voted: "Voĉdonita"
 | 
				
			||||||
 | 
					  closed: "Finita"
 | 
				
			||||||
_visibility:
 | 
					_visibility:
 | 
				
			||||||
  public: "Publika"
 | 
					  public: "Publika"
 | 
				
			||||||
  publicDescription: "Publikigi al ĉiuj en la Fediverso"
 | 
					  publicDescription: "Publikigi al ĉiuj en la Fediverso"
 | 
				
			||||||
| 
						 | 
					@ -1089,7 +1103,7 @@ _notification:
 | 
				
			||||||
  youGotReply: "{name} respondis"
 | 
					  youGotReply: "{name} respondis"
 | 
				
			||||||
  youGotQuote: "{name} citis"
 | 
					  youGotQuote: "{name} citis"
 | 
				
			||||||
  youRenoted: "{name} plusendis"
 | 
					  youRenoted: "{name} plusendis"
 | 
				
			||||||
  youGotPoll: "{name} balotis"
 | 
					  youGotPoll: "{name} voĉdonis"
 | 
				
			||||||
  youGotMessagingMessageFromUser: "{name} sendis al vi mesaĝon"
 | 
					  youGotMessagingMessageFromUser: "{name} sendis al vi mesaĝon"
 | 
				
			||||||
  youGotMessagingMessageFromGroup: "Oni sendis al la grupo {name} mesaĝon"
 | 
					  youGotMessagingMessageFromGroup: "Oni sendis al la grupo {name} mesaĝon"
 | 
				
			||||||
  youWereFollowed: "Eksekvis vin"
 | 
					  youWereFollowed: "Eksekvis vin"
 | 
				
			||||||
| 
						 | 
					@ -1104,6 +1118,7 @@ _notification:
 | 
				
			||||||
    renote: "Plusendoj"
 | 
					    renote: "Plusendoj"
 | 
				
			||||||
    quote: "Citi"
 | 
					    quote: "Citi"
 | 
				
			||||||
    reaction: "Reagoj"
 | 
					    reaction: "Reagoj"
 | 
				
			||||||
 | 
					    pollVote: "Voĉdonoj en balotoj"
 | 
				
			||||||
    receiveFollowRequest: "Ricevi peton de sekvado"
 | 
					    receiveFollowRequest: "Ricevi peton de sekvado"
 | 
				
			||||||
    followRequestAccepted: "Akceptita peto de sekvado"
 | 
					    followRequestAccepted: "Akceptita peto de sekvado"
 | 
				
			||||||
    groupInvited: "Invitita al grupo"
 | 
					    groupInvited: "Invitita al grupo"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -595,6 +595,8 @@ smtpSecure: "SMTP 接続に暗黙的なSSL/TLSを使用する"
 | 
				
			||||||
smtpSecureInfo: "STARTTLS使用時はオフにします。"
 | 
					smtpSecureInfo: "STARTTLS使用時はオフにします。"
 | 
				
			||||||
testEmail: "配信テスト"
 | 
					testEmail: "配信テスト"
 | 
				
			||||||
wordMute: "ワードミュート"
 | 
					wordMute: "ワードミュート"
 | 
				
			||||||
 | 
					regexpError: "正規表現エラー"
 | 
				
			||||||
 | 
					regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが発生しました:"
 | 
				
			||||||
instanceMute: "インスタンスミュート"
 | 
					instanceMute: "インスタンスミュート"
 | 
				
			||||||
userSaysSomething: "{name}が何かを言いました"
 | 
					userSaysSomething: "{name}が何かを言いました"
 | 
				
			||||||
makeActive: "アクティブにする"
 | 
					makeActive: "アクティブにする"
 | 
				
			||||||
| 
						 | 
					@ -829,6 +831,8 @@ smartphone: "スマートフォン"
 | 
				
			||||||
tablet: "タブレット"
 | 
					tablet: "タブレット"
 | 
				
			||||||
auto: "自動"
 | 
					auto: "自動"
 | 
				
			||||||
themeColor: "テーマカラー"
 | 
					themeColor: "テーマカラー"
 | 
				
			||||||
 | 
					size: "サイズ"
 | 
				
			||||||
 | 
					numberOfColumn: "列の数"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_emailUnavailable:
 | 
					_emailUnavailable:
 | 
				
			||||||
  used: "既に使用されています"
 | 
					  used: "既に使用されています"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -594,6 +594,8 @@ smtpSecure: "Použiť implicitné SSL/TLS pre SMTP spojenia"
 | 
				
			||||||
smtpSecureInfo: "Toto vypnite keď používate STARTTLS"
 | 
					smtpSecureInfo: "Toto vypnite keď používate STARTTLS"
 | 
				
			||||||
testEmail: "Doručenie testovacieho emailu"
 | 
					testEmail: "Doručenie testovacieho emailu"
 | 
				
			||||||
wordMute: "Stíšenie slova"
 | 
					wordMute: "Stíšenie slova"
 | 
				
			||||||
 | 
					regexpError: "Chyba v regulárnom výraze"
 | 
				
			||||||
 | 
					regexpErrorDescription: "Na riadku {line} sa vyskytla chyba v stíšenom slove {tab}."
 | 
				
			||||||
instanceMute: "Stíšené servery"
 | 
					instanceMute: "Stíšené servery"
 | 
				
			||||||
userSaysSomething: "{name} niečo povedal/a"
 | 
					userSaysSomething: "{name} niečo povedal/a"
 | 
				
			||||||
makeActive: "Aktivovať"
 | 
					makeActive: "Aktivovať"
 | 
				
			||||||
| 
						 | 
					@ -827,6 +829,9 @@ overridedDeviceKind: "Typ zariadenia"
 | 
				
			||||||
smartphone: "Smartfón"
 | 
					smartphone: "Smartfón"
 | 
				
			||||||
tablet: "Tablet"
 | 
					tablet: "Tablet"
 | 
				
			||||||
auto: "Automaticky"
 | 
					auto: "Automaticky"
 | 
				
			||||||
 | 
					themeColor: "Farba témy"
 | 
				
			||||||
 | 
					size: "Veľkosť"
 | 
				
			||||||
 | 
					numberOfColumn: "Počet stĺpcov"
 | 
				
			||||||
_emailUnavailable:
 | 
					_emailUnavailable:
 | 
				
			||||||
  used: "Táto emailová adresa sa už používa"
 | 
					  used: "Táto emailová adresa sa už používa"
 | 
				
			||||||
  format: "Formát emailovej adresy je nesprávny"
 | 
					  format: "Formát emailovej adresy je nesprávny"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -595,6 +595,8 @@ smtpSecure: "在 SMTP 连接中使用隐式 SSL / TLS"
 | 
				
			||||||
smtpSecureInfo: "使用STARTTLS时关闭。"
 | 
					smtpSecureInfo: "使用STARTTLS时关闭。"
 | 
				
			||||||
testEmail: "邮件发送测试"
 | 
					testEmail: "邮件发送测试"
 | 
				
			||||||
wordMute: "文字屏蔽"
 | 
					wordMute: "文字屏蔽"
 | 
				
			||||||
 | 
					regexpError: "正则表达式错误"
 | 
				
			||||||
 | 
					regexpErrorDescription: "{tab} 屏蔽文字的第 {line} 行的正则表达式有错误:"
 | 
				
			||||||
instanceMute: "实例的屏蔽"
 | 
					instanceMute: "实例的屏蔽"
 | 
				
			||||||
userSaysSomething: "{name}说了什么"
 | 
					userSaysSomething: "{name}说了什么"
 | 
				
			||||||
makeActive: "启用"
 | 
					makeActive: "启用"
 | 
				
			||||||
| 
						 | 
					@ -828,6 +830,9 @@ overridedDeviceKind: "设备类型"
 | 
				
			||||||
smartphone: "智能手机"
 | 
					smartphone: "智能手机"
 | 
				
			||||||
tablet: "平板"
 | 
					tablet: "平板"
 | 
				
			||||||
auto: "自动"
 | 
					auto: "自动"
 | 
				
			||||||
 | 
					themeColor: "主题颜色"
 | 
				
			||||||
 | 
					size: "大小"
 | 
				
			||||||
 | 
					numberOfColumn: "列数"
 | 
				
			||||||
_emailUnavailable:
 | 
					_emailUnavailable:
 | 
				
			||||||
  used: "已经被使用过"
 | 
					  used: "已经被使用过"
 | 
				
			||||||
  format: "无效的格式"
 | 
					  format: "无效的格式"
 | 
				
			||||||
| 
						 | 
					@ -955,7 +960,7 @@ _mfm:
 | 
				
			||||||
  rotateDescription: "旋转指定的角度。"
 | 
					  rotateDescription: "旋转指定的角度。"
 | 
				
			||||||
_instanceTicker:
 | 
					_instanceTicker:
 | 
				
			||||||
  none: "不显示"
 | 
					  none: "不显示"
 | 
				
			||||||
  remote: "显示给远程用户"
 | 
					  remote: "仅显示远程用户的"
 | 
				
			||||||
  always: "始终显示"
 | 
					  always: "始终显示"
 | 
				
			||||||
_serverDisconnectedBehavior:
 | 
					_serverDisconnectedBehavior:
 | 
				
			||||||
  reload: "自动重载"
 | 
					  reload: "自动重载"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	"name": "misskey",
 | 
						"name": "misskey",
 | 
				
			||||||
	"version": "12.105.0",
 | 
						"version": "12.107.0",
 | 
				
			||||||
	"codename": "indigo",
 | 
						"codename": "indigo",
 | 
				
			||||||
	"repository": {
 | 
						"repository": {
 | 
				
			||||||
		"type": "git",
 | 
							"type": "git",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,65 @@
 | 
				
			||||||
 | 
					const RE2 = require('re2');
 | 
				
			||||||
 | 
					const { MigrationInterface, QueryRunner } = require("typeorm");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = class convertHardMutes1644010796173 {
 | 
				
			||||||
 | 
					    name = 'convertHardMutes1644010796173'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async up(queryRunner) {
 | 
				
			||||||
 | 
					        let entries = await queryRunner.query(`SELECT "userId", "mutedWords" FROM "user_profile" WHERE "userHost" IS NULL`);
 | 
				
			||||||
 | 
					        for(let i = 0; i < entries.length; i++) {
 | 
				
			||||||
 | 
					            let words = entries[i].mutedWords
 | 
				
			||||||
 | 
					                .map(line => {
 | 
				
			||||||
 | 
															if (typeof line === 'string') return [];
 | 
				
			||||||
 | 
					                    const regexp = line.join(" ").match(/^\/(.+)\/(.*)$/);
 | 
				
			||||||
 | 
					                    if (regexp) {
 | 
				
			||||||
 | 
					                        // convert regexp's
 | 
				
			||||||
 | 
					                        try {
 | 
				
			||||||
 | 
					                            new RE2(regexp[1], regexp[2]);
 | 
				
			||||||
 | 
					                            return `/${regexp[1]}/${regexp[2]}`;
 | 
				
			||||||
 | 
					                        } catch (err) {
 | 
				
			||||||
 | 
					                            // invalid regex, ignore it
 | 
				
			||||||
 | 
					                            return [];
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        // remove empty segments
 | 
				
			||||||
 | 
					                        return line.filter(x => x !== '');
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                // remove empty lines
 | 
				
			||||||
 | 
					                .filter(x => !(Array.isArray(x) && x.length === 0));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await queryRunner.connection.createQueryBuilder()
 | 
				
			||||||
 | 
					                .update('user_profile')
 | 
				
			||||||
 | 
					                .set({
 | 
				
			||||||
 | 
					                    mutedWords: words
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .where('userId = :id', { id: entries[i].userId })
 | 
				
			||||||
 | 
					                .execute();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async down(queryRunner) {
 | 
				
			||||||
 | 
					        let entries = await queryRunner.query(`SELECT "userId", "mutedWords" FROM "user_profile"`);
 | 
				
			||||||
 | 
					        for(let i = 0; i < entries.length; i++) {
 | 
				
			||||||
 | 
					            let words = entries[i].mutedWords
 | 
				
			||||||
 | 
					                .map(line => {
 | 
				
			||||||
 | 
					                    if (Array.isArray(line)) {
 | 
				
			||||||
 | 
					                        return line;
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                    	// do not split regex at spaces again
 | 
				
			||||||
 | 
					                        return [line];
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                // remove empty lines
 | 
				
			||||||
 | 
					                .filter(x => !(Array.isArray(x) && x.length === 0));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await queryRunner.connection.createQueryBuilder()
 | 
				
			||||||
 | 
					                .update('user_profile')
 | 
				
			||||||
 | 
					                .set({
 | 
				
			||||||
 | 
					                    mutedWords: words
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .where('userId = :id', { id: entries[i].userId })
 | 
				
			||||||
 | 
					                .execute();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										31
									
								
								packages/backend/migration/1644481657998-chart-v15.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								packages/backend/migration/1644481657998-chart-v15.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,31 @@
 | 
				
			||||||
 | 
					const { MigrationInterface, QueryRunner } = require("typeorm");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = class chartV151644481657998 {
 | 
				
			||||||
 | 
					    name = 'chartV151644481657998'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async up(queryRunner) {
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___instance_total"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___instance_inc"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___instance_dec"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___instance_total"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___instance_inc"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___instance_dec"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___sub" smallint NOT NULL DEFAULT '0'`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___pub" smallint NOT NULL DEFAULT '0'`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___sub" smallint NOT NULL DEFAULT '0'`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___pub" smallint NOT NULL DEFAULT '0'`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async down(queryRunner) {
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___pub"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___sub"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___pub"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___sub"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___instance_dec" smallint NOT NULL DEFAULT '0'`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___instance_inc" smallint NOT NULL DEFAULT '0'`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___instance_total" integer NOT NULL DEFAULT '0'`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___instance_dec" smallint NOT NULL DEFAULT '0'`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___instance_inc" smallint NOT NULL DEFAULT '0'`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___instance_total" integer NOT NULL DEFAULT '0'`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,15 @@
 | 
				
			||||||
 | 
					const { MigrationInterface, QueryRunner } = require("typeorm");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = class followingIndexes1644551208096 {
 | 
				
			||||||
 | 
					    name = 'followingIndexes1644551208096'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async up(queryRunner) {
 | 
				
			||||||
 | 
					        await queryRunner.query(`CREATE INDEX "IDX_4ccd2239268ebbd1b35e318754" ON "following" ("followerHost") `);
 | 
				
			||||||
 | 
					        await queryRunner.query(`CREATE INDEX "IDX_fcdafee716dfe9c3b5fde90f30" ON "following" ("followeeHost") `);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async down(queryRunner) {
 | 
				
			||||||
 | 
					        await queryRunner.query(`DROP INDEX "public"."IDX_fcdafee716dfe9c3b5fde90f30"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`DROP INDEX "public"."IDX_4ccd2239268ebbd1b35e318754"`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -123,7 +123,7 @@
 | 
				
			||||||
		"ms": "3.0.0-canary.1",
 | 
							"ms": "3.0.0-canary.1",
 | 
				
			||||||
		"multer": "1.4.4",
 | 
							"multer": "1.4.4",
 | 
				
			||||||
		"nested-property": "4.0.0",
 | 
							"nested-property": "4.0.0",
 | 
				
			||||||
		"node-fetch": "2.6.1",
 | 
							"node-fetch": "2.6.7",
 | 
				
			||||||
		"nodemailer": "6.7.2",
 | 
							"nodemailer": "6.7.2",
 | 
				
			||||||
		"os-utils": "0.0.14",
 | 
							"os-utils": "0.0.14",
 | 
				
			||||||
		"parse5": "6.0.1",
 | 
							"parse5": "6.0.1",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,26 +11,31 @@ type UserLike = {
 | 
				
			||||||
	id: User['id'];
 | 
						id: User['id'];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function checkWordMute(note: NoteLike, me: UserLike | null | undefined, mutedWords: string[][]): Promise<boolean> {
 | 
					export async function checkWordMute(note: NoteLike, me: UserLike | null | undefined, mutedWords: Array<string | string[]>): Promise<boolean> {
 | 
				
			||||||
	// 自分自身
 | 
						// 自分自身
 | 
				
			||||||
	if (me && (note.userId === me.id)) return false;
 | 
						if (me && (note.userId === me.id)) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const words = mutedWords
 | 
						if (mutedWords.length > 0) {
 | 
				
			||||||
		// Clean up
 | 
					 | 
				
			||||||
		.map(xs => xs.filter(x => x !== ''))
 | 
					 | 
				
			||||||
		.filter(xs => xs.length > 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (words.length > 0) {
 | 
					 | 
				
			||||||
		if (note.text == null) return false;
 | 
							if (note.text == null) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const matched = words.some(and =>
 | 
							const matched = mutedWords.some(filter => {
 | 
				
			||||||
			and.every(keyword => {
 | 
								if (Array.isArray(filter)) {
 | 
				
			||||||
				const regexp = keyword.match(/^\/(.+)\/(.*)$/);
 | 
									return filter.every(keyword => note.text!.includes(keyword));
 | 
				
			||||||
				if (regexp) {
 | 
								} else {
 | 
				
			||||||
 | 
									// represents RegExp
 | 
				
			||||||
 | 
									const regexp = filter.match(/^\/(.+)\/(.*)$/);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// This should never happen due to input sanitisation.
 | 
				
			||||||
 | 
									if (!regexp) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									try {
 | 
				
			||||||
					return new RE2(regexp[1], regexp[2]).test(note.text!);
 | 
										return new RE2(regexp[1], regexp[2]).test(note.text!);
 | 
				
			||||||
 | 
									} catch (err) {
 | 
				
			||||||
 | 
										// This should never happen due to input sanitisation.
 | 
				
			||||||
 | 
										return false;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				return note.text!.includes(keyword);
 | 
								}
 | 
				
			||||||
			}));
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (matched) return true;
 | 
							if (matched) return true;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,6 +41,7 @@ export class Following {
 | 
				
			||||||
	public follower: User | null;
 | 
						public follower: User | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	//#region Denormalized fields
 | 
						//#region Denormalized fields
 | 
				
			||||||
 | 
						@Index()
 | 
				
			||||||
	@Column('varchar', {
 | 
						@Column('varchar', {
 | 
				
			||||||
		length: 128, nullable: true,
 | 
							length: 128, nullable: true,
 | 
				
			||||||
		comment: '[Denormalized]',
 | 
							comment: '[Denormalized]',
 | 
				
			||||||
| 
						 | 
					@ -59,6 +60,7 @@ export class Following {
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	public followerSharedInbox: string | null;
 | 
						public followerSharedInbox: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Index()
 | 
				
			||||||
	@Column('varchar', {
 | 
						@Column('varchar', {
 | 
				
			||||||
		length: 128, nullable: true,
 | 
							length: 128, nullable: true,
 | 
				
			||||||
		comment: '[Denormalized]',
 | 
							comment: '[Denormalized]',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -258,6 +258,11 @@ export default function() {
 | 
				
			||||||
	processDb(dbQueue);
 | 
						processDb(dbQueue);
 | 
				
			||||||
	processObjectStorage(objectStorageQueue);
 | 
						processObjectStorage(objectStorageQueue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						systemQueue.add('tickCharts', {
 | 
				
			||||||
 | 
						}, {
 | 
				
			||||||
 | 
							repeat: { cron: '55 * * * *' },
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	systemQueue.add('resyncCharts', {
 | 
						systemQueue.add('resyncCharts', {
 | 
				
			||||||
	}, {
 | 
						}, {
 | 
				
			||||||
		repeat: { cron: '0 0 * * *' },
 | 
							repeat: { cron: '0 0 * * *' },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,10 @@
 | 
				
			||||||
import * as Bull from 'bull';
 | 
					import * as Bull from 'bull';
 | 
				
			||||||
 | 
					import { tickCharts } from './tick-charts';
 | 
				
			||||||
import { resyncCharts } from './resync-charts';
 | 
					import { resyncCharts } from './resync-charts';
 | 
				
			||||||
import { cleanCharts } from './clean-charts';
 | 
					import { cleanCharts } from './clean-charts';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const jobs = {
 | 
					const jobs = {
 | 
				
			||||||
 | 
						tickCharts,
 | 
				
			||||||
	resyncCharts,
 | 
						resyncCharts,
 | 
				
			||||||
	cleanCharts,
 | 
						cleanCharts,
 | 
				
			||||||
} as Record<string, Bull.ProcessCallbackFunction<Record<string, unknown>> | Bull.ProcessPromiseFunction<Record<string, unknown>>>;
 | 
					} as Record<string, Bull.ProcessCallbackFunction<Record<string, unknown>> | Bull.ProcessPromiseFunction<Record<string, unknown>>>;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										28
									
								
								packages/backend/src/queue/processors/system/tick-charts.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								packages/backend/src/queue/processors/system/tick-charts.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,28 @@
 | 
				
			||||||
 | 
					import * as Bull from 'bull';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { queueLogger } from '../../logger';
 | 
				
			||||||
 | 
					import { activeUsersChart, driveChart, federationChart, hashtagChart, instanceChart, notesChart, perUserDriveChart, perUserFollowingChart, perUserNotesChart, perUserReactionsChart, usersChart, apRequestChart } from '@/services/chart/index';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const logger = queueLogger.createSubLogger('tick-charts');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function tickCharts(job: Bull.Job<Record<string, unknown>>, done: any): Promise<void> {
 | 
				
			||||||
 | 
						logger.info(`Tick charts...`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						await Promise.all([
 | 
				
			||||||
 | 
							federationChart.tick(false),
 | 
				
			||||||
 | 
							notesChart.tick(false),
 | 
				
			||||||
 | 
							usersChart.tick(false),
 | 
				
			||||||
 | 
							activeUsersChart.tick(false),
 | 
				
			||||||
 | 
							instanceChart.tick(false),
 | 
				
			||||||
 | 
							perUserNotesChart.tick(false),
 | 
				
			||||||
 | 
							driveChart.tick(false),
 | 
				
			||||||
 | 
							perUserReactionsChart.tick(false),
 | 
				
			||||||
 | 
							hashtagChart.tick(false),
 | 
				
			||||||
 | 
							perUserFollowingChart.tick(false),
 | 
				
			||||||
 | 
							perUserDriveChart.tick(false),
 | 
				
			||||||
 | 
							apRequestChart.tick(false),
 | 
				
			||||||
 | 
						]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						logger.succ(`All charts successfully ticked.`);
 | 
				
			||||||
 | 
						done();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,22 +0,0 @@
 | 
				
			||||||
import define from '../../define';
 | 
					 | 
				
			||||||
import { driveChart, notesChart, usersChart } from '@/services/chart/index';
 | 
					 | 
				
			||||||
import { insertModerationLog } from '@/services/insert-moderation-log';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const meta = {
 | 
					 | 
				
			||||||
	tags: ['admin'],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	requireCredential: true,
 | 
					 | 
				
			||||||
	requireModerator: true,
 | 
					 | 
				
			||||||
} as const;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// eslint-disable-next-line import/no-default-export
 | 
					 | 
				
			||||||
export default define(meta, async (ps, me) => {
 | 
					 | 
				
			||||||
	insertModerationLog(me, 'chartResync');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	driveChart.resync();
 | 
					 | 
				
			||||||
	notesChart.resync();
 | 
					 | 
				
			||||||
	usersChart.resync();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// TODO: ユーザーごとのチャートもキューに入れて更新する
 | 
					 | 
				
			||||||
	// TODO: インスタンスごとのチャートもキューに入れて更新する
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,4 @@
 | 
				
			||||||
 | 
					const RE2 = require('re2');
 | 
				
			||||||
import $ from 'cafy';
 | 
					import $ from 'cafy';
 | 
				
			||||||
import * as mfm from 'mfm-js';
 | 
					import * as mfm from 'mfm-js';
 | 
				
			||||||
import { ID } from '@/misc/cafy-id';
 | 
					import { ID } from '@/misc/cafy-id';
 | 
				
			||||||
| 
						 | 
					@ -117,7 +118,7 @@ export const meta = {
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		mutedWords: {
 | 
							mutedWords: {
 | 
				
			||||||
			validator: $.optional.arr($.arr($.str)),
 | 
								validator: $.optional.arr($.either($.arr($.str.min(1)).min(1), $.str)),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		mutedInstances: {
 | 
							mutedInstances: {
 | 
				
			||||||
| 
						 | 
					@ -163,6 +164,12 @@ export const meta = {
 | 
				
			||||||
			code: 'NO_SUCH_PAGE',
 | 
								code: 'NO_SUCH_PAGE',
 | 
				
			||||||
			id: '8e01b590-7eb9-431b-a239-860e086c408e',
 | 
								id: '8e01b590-7eb9-431b-a239-860e086c408e',
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							invalidRegexp: {
 | 
				
			||||||
 | 
								message: 'Invalid Regular Expression.',
 | 
				
			||||||
 | 
								code: 'INVALID_REGEXP',
 | 
				
			||||||
 | 
								id: '0d786918-10df-41cd-8f33-8dec7d9a89a5',
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	res: {
 | 
						res: {
 | 
				
			||||||
| 
						 | 
					@ -191,6 +198,18 @@ export default define(meta, async (ps, _user, token) => {
 | 
				
			||||||
	if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId;
 | 
						if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId;
 | 
				
			||||||
	if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId;
 | 
						if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId;
 | 
				
			||||||
	if (ps.mutedWords !== undefined) {
 | 
						if (ps.mutedWords !== undefined) {
 | 
				
			||||||
 | 
							// validate regular expression syntax
 | 
				
			||||||
 | 
							ps.mutedWords.filter(x => !Array.isArray(x)).forEach(x => {
 | 
				
			||||||
 | 
								const regexp = x.match(/^\/(.+)\/(.*)$/);
 | 
				
			||||||
 | 
								if (!regexp) throw new ApiError(meta.errors.invalidRegexp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									new RE2(regexp[1], regexp[2]);
 | 
				
			||||||
 | 
								} catch (err) {
 | 
				
			||||||
 | 
									throw new ApiError(meta.errors.invalidRegexp);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		profileUpdates.mutedWords = ps.mutedWords;
 | 
							profileUpdates.mutedWords = ps.mutedWords;
 | 
				
			||||||
		profileUpdates.enableWordMute = ps.mutedWords.length > 0;
 | 
							profileUpdates.enableWordMute = ps.mutedWords.length > 0;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import define from '../define';
 | 
					import define from '../define';
 | 
				
			||||||
import { NoteReactions, Notes, Users } from '@/models/index';
 | 
					import { Instances, NoteReactions, Notes, Users } from '@/models/index';
 | 
				
			||||||
import { federationChart, driveChart } from '@/services/chart/index';
 | 
					import { } from '@/services/chart/index';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const meta = {
 | 
					export const meta = {
 | 
				
			||||||
	requireCredential: false,
 | 
						requireCredential: false,
 | 
				
			||||||
| 
						 | 
					@ -63,7 +63,7 @@ export default define(meta, async () => {
 | 
				
			||||||
		Users.count({ where: { host: null }, cache: 3600000 }),
 | 
							Users.count({ where: { host: null }, cache: 3600000 }),
 | 
				
			||||||
		NoteReactions.count({ cache: 3600000 }), // 1 hour
 | 
							NoteReactions.count({ cache: 3600000 }), // 1 hour
 | 
				
			||||||
		//NoteReactions.count({ where: { userHost: null }, cache: 3600000 }),
 | 
							//NoteReactions.count({ where: { userHost: null }, cache: 3600000 }),
 | 
				
			||||||
		federationChart.getChart('hour', 1, null).then(chart => chart.instance.total[0]),
 | 
							Instances.count({ cache: 3600000 }),
 | 
				
			||||||
	]);
 | 
						]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return {
 | 
						return {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,12 @@ export default class ActiveUsersChart extends Chart<typeof schema> {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	protected async queryCurrentState(): Promise<Partial<KVs<typeof schema>>> {
 | 
						protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
		return {};
 | 
							return {};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,12 @@ export default class ApRequestChart extends Chart<typeof schema> {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	protected async queryCurrentState(): Promise<Partial<KVs<typeof schema>>> {
 | 
						protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
		return {};
 | 
							return {};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,12 @@ export default class DriveChart extends Chart<typeof schema> {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	protected async queryCurrentState(): Promise<Partial<KVs<typeof schema>>> {
 | 
						protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
		return {};
 | 
							return {};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,12 +3,11 @@ import Chart from '../../core';
 | 
				
			||||||
export const name = 'federation';
 | 
					export const name = 'federation';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const schema = {
 | 
					export const schema = {
 | 
				
			||||||
	'instance.total': { accumulate: true },
 | 
					 | 
				
			||||||
	'instance.inc': { range: 'small' },
 | 
					 | 
				
			||||||
	'instance.dec': { range: 'small' },
 | 
					 | 
				
			||||||
	'deliveredInstances': { uniqueIncrement: true, range: 'small' },
 | 
						'deliveredInstances': { uniqueIncrement: true, range: 'small' },
 | 
				
			||||||
	'inboxInstances': { uniqueIncrement: true, range: 'small' },
 | 
						'inboxInstances': { uniqueIncrement: true, range: 'small' },
 | 
				
			||||||
	'stalled': { uniqueIncrement: true, range: 'small' },
 | 
						'stalled': { uniqueIncrement: true, range: 'small' },
 | 
				
			||||||
 | 
						'sub': { accumulate: true, range: 'small' },
 | 
				
			||||||
 | 
						'pub': { accumulate: true, range: 'small' },
 | 
				
			||||||
} as const;
 | 
					} as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const entity = Chart.schemaToEntity(name, schema);
 | 
					export const entity = Chart.schemaToEntity(name, schema);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import autobind from 'autobind-decorator';
 | 
					import autobind from 'autobind-decorator';
 | 
				
			||||||
import Chart, { KVs } from '../core';
 | 
					import Chart, { KVs } from '../core';
 | 
				
			||||||
import { Instances } from '@/models/index';
 | 
					import { Followings } from '@/models/index';
 | 
				
			||||||
import { name, schema } from './entities/federation';
 | 
					import { name, schema } from './entities/federation';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					@ -13,23 +13,30 @@ export default class FederationChart extends Chart<typeof schema> {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	protected async queryCurrentState(): Promise<Partial<KVs<typeof schema>>> {
 | 
						protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
		const [total] = await Promise.all([
 | 
					 | 
				
			||||||
			Instances.count({}),
 | 
					 | 
				
			||||||
		]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			'instance.total': total,
 | 
					 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	public async update(isAdditional: boolean): Promise<void> {
 | 
						protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
		await this.commit({
 | 
							const [sub, pub] = await Promise.all([
 | 
				
			||||||
			'instance.total': isAdditional ? 1 : -1,
 | 
								Followings.createQueryBuilder('following')
 | 
				
			||||||
			'instance.inc': isAdditional ? 1 : 0,
 | 
									.select('COUNT(DISTINCT following.followeeHost)')
 | 
				
			||||||
			'instance.dec': isAdditional ? 0 : 1,
 | 
									.where('following.followeeHost IS NOT NULL')
 | 
				
			||||||
		});
 | 
									.getRawOne()
 | 
				
			||||||
 | 
									.then(x => parseInt(x.count, 10)),
 | 
				
			||||||
 | 
								Followings.createQueryBuilder('following')
 | 
				
			||||||
 | 
									.select('COUNT(DISTINCT following.followerHost)')
 | 
				
			||||||
 | 
									.where('following.followerHost IS NOT NULL')
 | 
				
			||||||
 | 
									.getRawOne()
 | 
				
			||||||
 | 
									.then(x => parseInt(x.count, 10)),
 | 
				
			||||||
 | 
							]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								'sub': sub,
 | 
				
			||||||
 | 
								'pub': pub,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,12 @@ export default class HashtagChart extends Chart<typeof schema> {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	protected async queryCurrentState(): Promise<Partial<KVs<typeof schema>>> {
 | 
						protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
		return {};
 | 
							return {};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,7 +16,7 @@ export default class InstanceChart extends Chart<typeof schema> {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	protected async queryCurrentState(group: string): Promise<Partial<KVs<typeof schema>>> {
 | 
						protected async tickMajor(group: string): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
		const [
 | 
							const [
 | 
				
			||||||
			notesCount,
 | 
								notesCount,
 | 
				
			||||||
			usersCount,
 | 
								usersCount,
 | 
				
			||||||
| 
						 | 
					@ -42,6 +42,11 @@ export default class InstanceChart extends Chart<typeof schema> {
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	public async requestReceived(host: string): Promise<void> {
 | 
						public async requestReceived(host: string): Promise<void> {
 | 
				
			||||||
		await this.commit({
 | 
							await this.commit({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,7 @@ export default class NotesChart extends Chart<typeof schema> {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	protected async queryCurrentState(): Promise<Partial<KVs<typeof schema>>> {
 | 
						protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
		const [localCount, remoteCount] = await Promise.all([
 | 
							const [localCount, remoteCount] = await Promise.all([
 | 
				
			||||||
			Notes.count({ userHost: null }),
 | 
								Notes.count({ userHost: null }),
 | 
				
			||||||
			Notes.count({ userHost: Not(IsNull()) }),
 | 
								Notes.count({ userHost: Not(IsNull()) }),
 | 
				
			||||||
| 
						 | 
					@ -27,6 +27,11 @@ export default class NotesChart extends Chart<typeof schema> {
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	public async update(note: Note, isAdditional: boolean): Promise<void> {
 | 
						public async update(note: Note, isAdditional: boolean): Promise<void> {
 | 
				
			||||||
		const prefix = note.userHost === null ? 'local' : 'remote';
 | 
							const prefix = note.userHost === null ? 'local' : 'remote';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ export default class PerUserDriveChart extends Chart<typeof schema> {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	protected async queryCurrentState(group: string): Promise<Partial<KVs<typeof schema>>> {
 | 
						protected async tickMajor(group: string): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
		const [count, size] = await Promise.all([
 | 
							const [count, size] = await Promise.all([
 | 
				
			||||||
			DriveFiles.count({ userId: group }),
 | 
								DriveFiles.count({ userId: group }),
 | 
				
			||||||
			DriveFiles.calcDriveUsageOf(group),
 | 
								DriveFiles.calcDriveUsageOf(group),
 | 
				
			||||||
| 
						 | 
					@ -26,6 +26,11 @@ export default class PerUserDriveChart extends Chart<typeof schema> {
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	public async update(file: DriveFile, isAdditional: boolean): Promise<void> {
 | 
						public async update(file: DriveFile, isAdditional: boolean): Promise<void> {
 | 
				
			||||||
		const fileSizeKb = file.size / 1000;
 | 
							const fileSizeKb = file.size / 1000;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,7 @@ export default class PerUserFollowingChart extends Chart<typeof schema> {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	protected async queryCurrentState(group: string): Promise<Partial<KVs<typeof schema>>> {
 | 
						protected async tickMajor(group: string): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
		const [
 | 
							const [
 | 
				
			||||||
			localFollowingsCount,
 | 
								localFollowingsCount,
 | 
				
			||||||
			localFollowersCount,
 | 
								localFollowersCount,
 | 
				
			||||||
| 
						 | 
					@ -36,6 +36,11 @@ export default class PerUserFollowingChart extends Chart<typeof schema> {
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	public async update(follower: { id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }, isFollow: boolean): Promise<void> {
 | 
						public async update(follower: { id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }, isFollow: boolean): Promise<void> {
 | 
				
			||||||
		const prefixFollower = Users.isLocalUser(follower) ? 'local' : 'remote';
 | 
							const prefixFollower = Users.isLocalUser(follower) ? 'local' : 'remote';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,7 @@ export default class PerUserNotesChart extends Chart<typeof schema> {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	protected async queryCurrentState(group: string): Promise<Partial<KVs<typeof schema>>> {
 | 
						protected async tickMajor(group: string): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
		const [count] = await Promise.all([
 | 
							const [count] = await Promise.all([
 | 
				
			||||||
			Notes.count({ userId: group }),
 | 
								Notes.count({ userId: group }),
 | 
				
			||||||
		]);
 | 
							]);
 | 
				
			||||||
| 
						 | 
					@ -25,6 +25,11 @@ export default class PerUserNotesChart extends Chart<typeof schema> {
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	public async update(user: { id: User['id'] }, note: Note, isAdditional: boolean): Promise<void> {
 | 
						public async update(user: { id: User['id'] }, note: Note, isAdditional: boolean): Promise<void> {
 | 
				
			||||||
		await this.commit({
 | 
							await this.commit({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,12 @@ export default class PerUserReactionsChart extends Chart<typeof schema> {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	protected async queryCurrentState(group: string): Promise<Partial<KVs<typeof schema>>> {
 | 
						protected async tickMajor(group: string): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
		return {};
 | 
							return {};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,12 +14,17 @@ export default class TestGroupedChart extends Chart<typeof schema> {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	protected async queryCurrentState(group: string): Promise<Partial<KVs<typeof schema>>> {
 | 
						protected async tickMajor(group: string): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			'foo.total': this.total[group],
 | 
								'foo.total': this.total[group],
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	public async increment(group: string): Promise<void> {
 | 
						public async increment(group: string): Promise<void> {
 | 
				
			||||||
		if (this.total[group] == null) this.total[group] = 0;
 | 
							if (this.total[group] == null) this.total[group] = 0;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,12 @@ export default class TestIntersectionChart extends Chart<typeof schema> {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	protected async queryCurrentState(): Promise<Partial<KVs<typeof schema>>> {
 | 
						protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
		return {};
 | 
							return {};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,12 @@ export default class TestUniqueChart extends Chart<typeof schema> {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	protected async queryCurrentState(): Promise<Partial<KVs<typeof schema>>> {
 | 
						protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
		return {};
 | 
							return {};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,12 +14,17 @@ export default class TestChart extends Chart<typeof schema> {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	protected async queryCurrentState(): Promise<Partial<KVs<typeof schema>>> {
 | 
						protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			'foo.total': this.total,
 | 
								'foo.total': this.total,
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	public async increment(): Promise<void> {
 | 
						public async increment(): Promise<void> {
 | 
				
			||||||
		this.total++;
 | 
							this.total++;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,7 @@ export default class UsersChart extends Chart<typeof schema> {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	protected async queryCurrentState(): Promise<Partial<KVs<typeof schema>>> {
 | 
						protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
		const [localCount, remoteCount] = await Promise.all([
 | 
							const [localCount, remoteCount] = await Promise.all([
 | 
				
			||||||
			Users.count({ host: null }),
 | 
								Users.count({ host: null }),
 | 
				
			||||||
			Users.count({ host: Not(IsNull()) }),
 | 
								Users.count({ host: Not(IsNull()) }),
 | 
				
			||||||
| 
						 | 
					@ -27,6 +27,11 @@ export default class UsersChart extends Chart<typeof schema> {
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
 | 
				
			||||||
 | 
							return {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	public async update(user: { id: User['id'], host: User['host'] }, isAdditional: boolean): Promise<void> {
 | 
						public async update(user: { id: User['id'], host: User['host'] }, isAdditional: boolean): Promise<void> {
 | 
				
			||||||
		const prefix = Users.isLocalUser(user) ? 'local' : 'remote';
 | 
							const prefix = Users.isLocalUser(user) ? 'local' : 'remote';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,8 +17,27 @@ const columnPrefix = '___' as const;
 | 
				
			||||||
const uniqueTempColumnPrefix = 'unique_temp___' as const;
 | 
					const uniqueTempColumnPrefix = 'unique_temp___' as const;
 | 
				
			||||||
const columnDot = '_' as const;
 | 
					const columnDot = '_' as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Schema = Record<string, {
 | 
				
			||||||
 | 
						uniqueIncrement?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						intersection?: string[] | ReadonlyArray<string>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						range?: 'big' | 'small' | 'medium';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// previousな値を引き継ぐかどうか
 | 
				
			||||||
 | 
						accumulate?: boolean;
 | 
				
			||||||
 | 
					}>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type KeyToColumnName<T extends string> = T extends `${infer R1}.${infer R2}` ? `${R1}${typeof columnDot}${KeyToColumnName<R2>}` : T;
 | 
					type KeyToColumnName<T extends string> = T extends `${infer R1}.${infer R2}` ? `${R1}${typeof columnDot}${KeyToColumnName<R2>}` : T;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Columns<S extends Schema> = {
 | 
				
			||||||
 | 
						[K in keyof S as `${typeof columnPrefix}${KeyToColumnName<string & K>}`]: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type TempColumnsForUnique<S extends Schema> = {
 | 
				
			||||||
 | 
						[K in keyof S as `${typeof uniqueTempColumnPrefix}${KeyToColumnName<string & K>}`]: S[K]['uniqueIncrement'] extends true ? string[] : never;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type RawRecord<S extends Schema> = {
 | 
					type RawRecord<S extends Schema> = {
 | 
				
			||||||
	id: number;
 | 
						id: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,11 +50,7 @@ type RawRecord<S extends Schema> = {
 | 
				
			||||||
	 * 集計日時のUnixタイムスタンプ(秒)
 | 
						 * 集計日時のUnixタイムスタンプ(秒)
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	date: number;
 | 
						date: number;
 | 
				
			||||||
} & {
 | 
					} & TempColumnsForUnique<S> & Columns<S>;
 | 
				
			||||||
	[K in keyof S as `${typeof uniqueTempColumnPrefix}${KeyToColumnName<string & K>}`]: S[K]['uniqueIncrement'] extends true ? string[] : never;
 | 
					 | 
				
			||||||
} & {
 | 
					 | 
				
			||||||
	[K in keyof S as `${typeof columnPrefix}${KeyToColumnName<string & K>}`]: number;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const camelToSnake = (str: string): string => {
 | 
					const camelToSnake = (str: string): string => {
 | 
				
			||||||
	return str.replace(/([A-Z])/g, s => '_' + s.charAt(0).toLowerCase());
 | 
						return str.replace(/([A-Z])/g, s => '_' + s.charAt(0).toLowerCase());
 | 
				
			||||||
| 
						 | 
					@ -43,17 +58,6 @@ const camelToSnake = (str: string): string => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const removeDuplicates = (array: any[]) => Array.from(new Set(array));
 | 
					const removeDuplicates = (array: any[]) => Array.from(new Set(array));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Schema = Record<string, {
 | 
					 | 
				
			||||||
	uniqueIncrement?: boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	intersection?: string[] | ReadonlyArray<string>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	range?: 'big' | 'small' | 'medium';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// previousな値を引き継ぐかどうか
 | 
					 | 
				
			||||||
	accumulate?: boolean;
 | 
					 | 
				
			||||||
}>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Commit<S extends Schema> = {
 | 
					type Commit<S extends Schema> = {
 | 
				
			||||||
	[K in keyof S]?: S[K]['uniqueIncrement'] extends true ? string[] : number;
 | 
						[K in keyof S]?: S[K]['uniqueIncrement'] extends true ? string[] : number;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -78,10 +82,21 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
		diff: Commit<T>;
 | 
							diff: Commit<T>;
 | 
				
			||||||
		group: string | null;
 | 
							group: string | null;
 | 
				
			||||||
	}[] = [];
 | 
						}[] = [];
 | 
				
			||||||
	protected repositoryForHour: Repository<RawRecord<T>>;
 | 
						// ↓にしたいけどfindOneとかで型エラーになる
 | 
				
			||||||
	protected repositoryForDay: Repository<RawRecord<T>>;
 | 
						//private repositoryForHour: Repository<RawRecord<T>>;
 | 
				
			||||||
 | 
						//private repositoryForDay: Repository<RawRecord<T>>;
 | 
				
			||||||
 | 
						private repositoryForHour: Repository<{ id: number; group?: string | null; date: number; }>;
 | 
				
			||||||
 | 
						private repositoryForDay: Repository<{ id: number; group?: string | null; date: number; }>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	protected abstract queryCurrentState(group: string | null): Promise<Partial<KVs<T>>>;
 | 
						/**
 | 
				
			||||||
 | 
						 * 1日に一回程度実行されれば良いような計算処理を入れる(主にCASCADE削除などアプリケーション側で感知できない変動によるズレの修正用)
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						protected abstract tickMajor(group: string | null): Promise<Partial<KVs<T>>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * 少なくとも最小スパン内に1回は実行されて欲しい計算処理を入れる
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						protected abstract tickMinor(group: string | null): Promise<Partial<KVs<T>>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	private static convertSchemaToColumnDefinitions(schema: Schema): Record<string, { type: string; array?: boolean; default?: any; }> {
 | 
						private static convertSchemaToColumnDefinitions(schema: Schema): Record<string, { type: string; array?: boolean; default?: any; }> {
 | 
				
			||||||
| 
						 | 
					@ -188,23 +203,23 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
		this.schema = schema;
 | 
							this.schema = schema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const { hour, day } = Chart.schemaToEntity(name, schema, grouped);
 | 
							const { hour, day } = Chart.schemaToEntity(name, schema, grouped);
 | 
				
			||||||
		this.repositoryForHour = getRepository<RawRecord<T>>(hour);
 | 
							this.repositoryForHour = getRepository<{ id: number; group?: string | null; date: number; }>(hour);
 | 
				
			||||||
		this.repositoryForDay = getRepository<RawRecord<T>>(day);
 | 
							this.repositoryForDay = getRepository<{ id: number; group?: string | null; date: number; }>(day);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	private convertRawRecord(x: RawRecord<T>): KVs<T> {
 | 
						private convertRawRecord(x: RawRecord<T>): KVs<T> {
 | 
				
			||||||
		const kvs = {} as KVs<T>;
 | 
							const kvs = {} as Record<string, number>;
 | 
				
			||||||
		for (const k of Object.keys(x).filter(k => k.startsWith(columnPrefix))) {
 | 
							for (const k of Object.keys(x).filter((k) => k.startsWith(columnPrefix)) as (keyof Columns<T>)[]) {
 | 
				
			||||||
			kvs[k.substr(columnPrefix.length).split(columnDot).join('.')] = x[k];
 | 
								kvs[(k as string).substr(columnPrefix.length).split(columnDot).join('.')] = x[k];
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return kvs;
 | 
							return kvs as KVs<T>;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	private getNewLog(latest: KVs<T> | null): KVs<T> {
 | 
						private getNewLog(latest: KVs<T> | null): KVs<T> {
 | 
				
			||||||
		const log = {} as Record<keyof T, number>;
 | 
							const log = {} as Record<keyof T, number>;
 | 
				
			||||||
		for (const [k, v] of Object.entries(this.schema)) {
 | 
							for (const [k, v] of Object.entries(this.schema) as ([keyof typeof this['schema'], this['schema'][string]])[]) {
 | 
				
			||||||
			if (v.accumulate && latest) {
 | 
								if (v.accumulate && latest) {
 | 
				
			||||||
				log[k] = latest[k];
 | 
									log[k] = latest[k];
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
| 
						 | 
					@ -227,7 +242,7 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
			order: {
 | 
								order: {
 | 
				
			||||||
				date: -1,
 | 
									date: -1,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		}).then(x => x || null);
 | 
							}).then(x => x ?? null) as Promise<RawRecord<T> | null>;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
| 
						 | 
					@ -251,7 +266,7 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
		const currentLog = await repository.findOne({
 | 
							const currentLog = await repository.findOne({
 | 
				
			||||||
			date: Chart.dateToTimestamp(current),
 | 
								date: Chart.dateToTimestamp(current),
 | 
				
			||||||
			...(group ? { group: group } : {}),
 | 
								...(group ? { group: group } : {}),
 | 
				
			||||||
		});
 | 
							}) as RawRecord<T>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// ログがあればそれを返して終了
 | 
							// ログがあればそれを返して終了
 | 
				
			||||||
		if (currentLog != null) {
 | 
							if (currentLog != null) {
 | 
				
			||||||
| 
						 | 
					@ -291,7 +306,7 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
			const currentLog = await repository.findOne({
 | 
								const currentLog = await repository.findOne({
 | 
				
			||||||
				date: date,
 | 
									date: date,
 | 
				
			||||||
				...(group ? { group: group } : {}),
 | 
									...(group ? { group: group } : {}),
 | 
				
			||||||
			});
 | 
								}) as RawRecord<T>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// ログがあればそれを返して終了
 | 
								// ログがあればそれを返して終了
 | 
				
			||||||
			if (currentLog != null) return currentLog;
 | 
								if (currentLog != null) return currentLog;
 | 
				
			||||||
| 
						 | 
					@ -307,7 +322,7 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
				date: date,
 | 
									date: date,
 | 
				
			||||||
				...(group ? { group: group } : {}),
 | 
									...(group ? { group: group } : {}),
 | 
				
			||||||
				...columns,
 | 
									...columns,
 | 
				
			||||||
			}).then(x => repository.findOneOrFail(x.identifiers[0]));
 | 
								}).then(x => repository.findOneOrFail(x.identifiers[0])) as RawRecord<T>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): New commit created`);
 | 
								logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): New commit created`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -341,7 +356,7 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
		// これを回避するための実装は複雑になりそうなため、一旦保留。
 | 
							// これを回避するための実装は複雑になりそうなため、一旦保留。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const update = async (logHour: RawRecord<T>, logDay: RawRecord<T>): Promise<void> => {
 | 
							const update = async (logHour: RawRecord<T>, logDay: RawRecord<T>): Promise<void> => {
 | 
				
			||||||
			const finalDiffs = {} as Record<string, number | unknown[]>;
 | 
								const finalDiffs = {} as Record<string, number | string[]>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			for (const diff of this.buffer.filter(q => q.group == null || (q.group === logHour.group)).map(q => q.diff)) {
 | 
								for (const diff of this.buffer.filter(q => q.group == null || (q.group === logHour.group)).map(q => q.diff)) {
 | 
				
			||||||
				for (const [k, v] of Object.entries(diff)) {
 | 
									for (const [k, v] of Object.entries(diff)) {
 | 
				
			||||||
| 
						 | 
					@ -351,23 +366,23 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
						if (typeof finalDiffs[k] === 'number') {
 | 
											if (typeof finalDiffs[k] === 'number') {
 | 
				
			||||||
							(finalDiffs[k] as number) += v as number;
 | 
												(finalDiffs[k] as number) += v as number;
 | 
				
			||||||
						} else {
 | 
											} else {
 | 
				
			||||||
							(finalDiffs[k] as unknown[]) = (finalDiffs[k] as unknown[]).concat(v);
 | 
												(finalDiffs[k] as string[]) = (finalDiffs[k] as string[]).concat(v);
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const queryForHour: Record<string, number | (() => string)> = {};
 | 
								const queryForHour: Record<keyof RawRecord<T>, number | (() => string)> = {} as any;
 | 
				
			||||||
			const queryForDay: Record<string, number | (() => string)> = {};
 | 
								const queryForDay: Record<keyof RawRecord<T>, number | (() => string)> = {} as any;
 | 
				
			||||||
			for (const [k, v] of Object.entries(finalDiffs)) {
 | 
								for (const [k, v] of Object.entries(finalDiffs)) {
 | 
				
			||||||
				if (typeof v === 'number') {
 | 
									if (typeof v === 'number') {
 | 
				
			||||||
					const name = columnPrefix + k.replaceAll('.', columnDot);
 | 
										const name = columnPrefix + k.replaceAll('.', columnDot) as keyof Columns<T>;
 | 
				
			||||||
					if (v > 0) queryForHour[name] = () => `"${name}" + ${v}`;
 | 
										if (v > 0) queryForHour[name] = () => `"${name}" + ${v}`;
 | 
				
			||||||
					if (v < 0) queryForHour[name] = () => `"${name}" - ${Math.abs(v)}`;
 | 
										if (v < 0) queryForHour[name] = () => `"${name}" - ${Math.abs(v)}`;
 | 
				
			||||||
					if (v > 0) queryForDay[name] = () => `"${name}" + ${v}`;
 | 
										if (v > 0) queryForDay[name] = () => `"${name}" + ${v}`;
 | 
				
			||||||
					if (v < 0) queryForDay[name] = () => `"${name}" - ${Math.abs(v)}`;
 | 
										if (v < 0) queryForDay[name] = () => `"${name}" - ${Math.abs(v)}`;
 | 
				
			||||||
				} else if (Array.isArray(v) && v.length > 0) { // ユニークインクリメント
 | 
									} else if (Array.isArray(v) && v.length > 0) { // ユニークインクリメント
 | 
				
			||||||
					const tempColumnName = uniqueTempColumnPrefix + k.replaceAll('.', columnDot);
 | 
										const tempColumnName = uniqueTempColumnPrefix + k.replaceAll('.', columnDot) as keyof TempColumnsForUnique<T>;
 | 
				
			||||||
					// TODO: item をSQLエスケープ
 | 
										// TODO: item をSQLエスケープ
 | 
				
			||||||
					const itemsForHour = v.filter(item => !logHour[tempColumnName].includes(item)).map(item => `"${item}"`);
 | 
										const itemsForHour = v.filter(item => !logHour[tempColumnName].includes(item)).map(item => `"${item}"`);
 | 
				
			||||||
					const itemsForDay = v.filter(item => !logDay[tempColumnName].includes(item)).map(item => `"${item}"`);
 | 
										const itemsForDay = v.filter(item => !logDay[tempColumnName].includes(item)).map(item => `"${item}"`);
 | 
				
			||||||
| 
						 | 
					@ -379,10 +394,10 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
			// bake unique count
 | 
								// bake unique count
 | 
				
			||||||
			for (const [k, v] of Object.entries(finalDiffs)) {
 | 
								for (const [k, v] of Object.entries(finalDiffs)) {
 | 
				
			||||||
				if (this.schema[k].uniqueIncrement) {
 | 
									if (this.schema[k].uniqueIncrement) {
 | 
				
			||||||
					const name = columnPrefix + k.replaceAll('.', columnDot);
 | 
										const name = columnPrefix + k.replaceAll('.', columnDot) as keyof Columns<T>;
 | 
				
			||||||
					const tempColumnName = uniqueTempColumnPrefix + k.replaceAll('.', columnDot);
 | 
										const tempColumnName = uniqueTempColumnPrefix + k.replaceAll('.', columnDot) as keyof TempColumnsForUnique<T>;
 | 
				
			||||||
					queryForHour[name] = new Set([...v, ...logHour[tempColumnName]]).size;
 | 
										queryForHour[name] = new Set([...(v as string[]), ...logHour[tempColumnName]]).size;
 | 
				
			||||||
					queryForDay[name] = new Set([...v, ...logDay[tempColumnName]]).size;
 | 
										queryForDay[name] = new Set([...(v as string[]), ...logDay[tempColumnName]]).size;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -391,16 +406,18 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
			for (const [k, v] of Object.entries(this.schema)) {
 | 
								for (const [k, v] of Object.entries(this.schema)) {
 | 
				
			||||||
				const intersection = v.intersection;
 | 
									const intersection = v.intersection;
 | 
				
			||||||
				if (intersection) {
 | 
									if (intersection) {
 | 
				
			||||||
					const name = columnPrefix + k.replaceAll('.', columnDot);
 | 
										const name = columnPrefix + k.replaceAll('.', columnDot) as keyof Columns<T>;
 | 
				
			||||||
					const firstKey = intersection[0];
 | 
										const firstKey = intersection[0];
 | 
				
			||||||
					const firstTempColumnName = uniqueTempColumnPrefix + firstKey.replaceAll('.', columnDot);
 | 
										const firstTempColumnName = uniqueTempColumnPrefix + firstKey.replaceAll('.', columnDot) as keyof TempColumnsForUnique<T>;
 | 
				
			||||||
					const currentValuesForHour = new Set([...(finalDiffs[firstKey] ?? []), ...logHour[firstTempColumnName]]);
 | 
										const firstValues = finalDiffs[firstKey] as string[] | undefined;
 | 
				
			||||||
					const currentValuesForDay = new Set([...(finalDiffs[firstKey] ?? []), ...logDay[firstTempColumnName]]);
 | 
										const currentValuesForHour = new Set([...(firstValues ?? []), ...logHour[firstTempColumnName]]);
 | 
				
			||||||
 | 
										const currentValuesForDay = new Set([...(firstValues ?? []), ...logDay[firstTempColumnName]]);
 | 
				
			||||||
					for (let i = 1; i < intersection.length; i++) {
 | 
										for (let i = 1; i < intersection.length; i++) {
 | 
				
			||||||
						const targetKey = intersection[i];
 | 
											const targetKey = intersection[i];
 | 
				
			||||||
						const targetTempColumnName = uniqueTempColumnPrefix + targetKey.replaceAll('.', columnDot);
 | 
											const targetTempColumnName = uniqueTempColumnPrefix + targetKey.replaceAll('.', columnDot) as keyof TempColumnsForUnique<T>;
 | 
				
			||||||
						const targetValuesForHour = new Set([...(finalDiffs[targetKey] ?? []), ...logHour[targetTempColumnName]]);
 | 
											const targetValues = finalDiffs[targetKey] as string[] | undefined;
 | 
				
			||||||
						const targetValuesForDay = new Set([...(finalDiffs[targetKey] ?? []), ...logDay[targetTempColumnName]]);
 | 
											const targetValuesForHour = new Set([...(targetValues ?? []), ...logHour[targetTempColumnName]]);
 | 
				
			||||||
 | 
											const targetValuesForDay = new Set([...(targetValues ?? []), ...logDay[targetTempColumnName]]);
 | 
				
			||||||
						currentValuesForHour.forEach(v => {
 | 
											currentValuesForHour.forEach(v => {
 | 
				
			||||||
							if (!targetValuesForHour.has(v)) currentValuesForHour.delete(v);
 | 
												if (!targetValuesForHour.has(v)) currentValuesForHour.delete(v);
 | 
				
			||||||
						});
 | 
											});
 | 
				
			||||||
| 
						 | 
					@ -417,12 +434,12 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
			await Promise.all([
 | 
								await Promise.all([
 | 
				
			||||||
				this.repositoryForHour.createQueryBuilder()
 | 
									this.repositoryForHour.createQueryBuilder()
 | 
				
			||||||
					.update()
 | 
										.update()
 | 
				
			||||||
					.set(queryForHour)
 | 
										.set(queryForHour as any)
 | 
				
			||||||
					.where('id = :id', { id: logHour.id })
 | 
										.where('id = :id', { id: logHour.id })
 | 
				
			||||||
					.execute(),
 | 
										.execute(),
 | 
				
			||||||
				this.repositoryForDay.createQueryBuilder()
 | 
									this.repositoryForDay.createQueryBuilder()
 | 
				
			||||||
					.update()
 | 
										.update()
 | 
				
			||||||
					.set(queryForDay)
 | 
										.set(queryForDay as any)
 | 
				
			||||||
					.where('id = :id', { id: logDay.id })
 | 
										.where('id = :id', { id: logDay.id })
 | 
				
			||||||
					.execute(),
 | 
										.execute(),
 | 
				
			||||||
			]);
 | 
								]);
 | 
				
			||||||
| 
						 | 
					@ -445,13 +462,13 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	public async resync(group: string | null = null): Promise<void> {
 | 
						public async tick(major: boolean, group: string | null = null): Promise<void> {
 | 
				
			||||||
		const data = await this.queryCurrentState(group);
 | 
							const data = major ? await this.tickMajor(group) : await this.tickMinor(group);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const columns = {} as Record<string, number>;
 | 
							const columns = {} as Record<keyof Columns<T>, number>;
 | 
				
			||||||
		for (const [k, v] of Object.entries(data)) {
 | 
							for (const [k, v] of Object.entries(data) as ([keyof typeof data, number])[]) {
 | 
				
			||||||
			const name = k.replaceAll('.', columnDot);
 | 
								const name = columnPrefix + (k as string).replaceAll('.', columnDot) as keyof Columns<T>;
 | 
				
			||||||
			columns[columnPrefix + name] = v;
 | 
								columns[name] = v;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (Object.keys(columns).length === 0) {
 | 
							if (Object.keys(columns).length === 0) {
 | 
				
			||||||
| 
						 | 
					@ -462,12 +479,12 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
			await Promise.all([
 | 
								await Promise.all([
 | 
				
			||||||
				this.repositoryForHour.createQueryBuilder()
 | 
									this.repositoryForHour.createQueryBuilder()
 | 
				
			||||||
					.update()
 | 
										.update()
 | 
				
			||||||
					.set(columns as any)
 | 
										.set(columns)
 | 
				
			||||||
					.where('id = :id', { id: logHour.id })
 | 
										.where('id = :id', { id: logHour.id })
 | 
				
			||||||
					.execute(),
 | 
										.execute(),
 | 
				
			||||||
				this.repositoryForDay.createQueryBuilder()
 | 
									this.repositoryForDay.createQueryBuilder()
 | 
				
			||||||
					.update()
 | 
										.update()
 | 
				
			||||||
					.set(columns as any)
 | 
										.set(columns)
 | 
				
			||||||
					.where('id = :id', { id: logDay.id })
 | 
										.where('id = :id', { id: logDay.id })
 | 
				
			||||||
					.execute(),
 | 
										.execute(),
 | 
				
			||||||
			]);
 | 
								]);
 | 
				
			||||||
| 
						 | 
					@ -480,6 +497,11 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
			update(logHour, logDay));
 | 
								update(logHour, logDay));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						public resync(group: string | null = null): Promise<void> {
 | 
				
			||||||
 | 
							return this.tick(true, group);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	public async clean(): Promise<void> {
 | 
						public async clean(): Promise<void> {
 | 
				
			||||||
		const current = dateUTC(Chart.getCurrentDate());
 | 
							const current = dateUTC(Chart.getCurrentDate());
 | 
				
			||||||
| 
						 | 
					@ -488,11 +510,11 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
		const gt = Chart.dateToTimestamp(current) - (60 * 60 * 24 * 3);
 | 
							const gt = Chart.dateToTimestamp(current) - (60 * 60 * 24 * 3);
 | 
				
			||||||
		const lt = Chart.dateToTimestamp(current) - (60 * 60 * 24);
 | 
							const lt = Chart.dateToTimestamp(current) - (60 * 60 * 24);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const columns = {} as Record<string, number>;
 | 
							const columns = {} as Record<keyof TempColumnsForUnique<T>, []>;
 | 
				
			||||||
		for (const [k, v] of Object.entries(this.schema)) {
 | 
							for (const [k, v] of Object.entries(this.schema)) {
 | 
				
			||||||
			if (v.uniqueIncrement) {
 | 
								if (v.uniqueIncrement) {
 | 
				
			||||||
				const name = k.replaceAll('.', columnDot);
 | 
									const name = uniqueTempColumnPrefix + k.replaceAll('.', columnDot) as keyof TempColumnsForUnique<T>;
 | 
				
			||||||
				columns[uniqueTempColumnPrefix + name] = [];
 | 
									columns[name] = [];
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -503,13 +525,13 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
		await Promise.all([
 | 
							await Promise.all([
 | 
				
			||||||
			this.repositoryForHour.createQueryBuilder()
 | 
								this.repositoryForHour.createQueryBuilder()
 | 
				
			||||||
				.update()
 | 
									.update()
 | 
				
			||||||
				.set(columns as any)
 | 
									.set(columns)
 | 
				
			||||||
				.where('date > :gt', { gt })
 | 
									.where('date > :gt', { gt })
 | 
				
			||||||
				.andWhere('date < :lt', { lt })
 | 
									.andWhere('date < :lt', { lt })
 | 
				
			||||||
				.execute(),
 | 
									.execute(),
 | 
				
			||||||
			this.repositoryForDay.createQueryBuilder()
 | 
								this.repositoryForDay.createQueryBuilder()
 | 
				
			||||||
				.update()
 | 
									.update()
 | 
				
			||||||
				.set(columns as any)
 | 
									.set(columns)
 | 
				
			||||||
				.where('date > :gt', { gt })
 | 
									.where('date > :gt', { gt })
 | 
				
			||||||
				.andWhere('date < :lt', { lt })
 | 
									.andWhere('date < :lt', { lt })
 | 
				
			||||||
				.execute(),
 | 
									.execute(),
 | 
				
			||||||
| 
						 | 
					@ -542,7 +564,7 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
			order: {
 | 
								order: {
 | 
				
			||||||
				date: -1,
 | 
									date: -1,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		});
 | 
							}) as RawRecord<T>[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 要求された範囲にログがひとつもなかったら
 | 
							// 要求された範囲にログがひとつもなかったら
 | 
				
			||||||
		if (logs.length === 0) {
 | 
							if (logs.length === 0) {
 | 
				
			||||||
| 
						 | 
					@ -554,7 +576,7 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
				order: {
 | 
									order: {
 | 
				
			||||||
					date: -1,
 | 
										date: -1,
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			});
 | 
								}) as RawRecord<T>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (recentLog) {
 | 
								if (recentLog) {
 | 
				
			||||||
				logs = [recentLog];
 | 
									logs = [recentLog];
 | 
				
			||||||
| 
						 | 
					@ -571,7 +593,7 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
				order: {
 | 
									order: {
 | 
				
			||||||
					date: -1,
 | 
										date: -1,
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			});
 | 
								}) as RawRecord<T>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (outdatedLog) {
 | 
								if (outdatedLog) {
 | 
				
			||||||
				logs.push(outdatedLog);
 | 
									logs.push(outdatedLog);
 | 
				
			||||||
| 
						 | 
					@ -607,7 +629,7 @@ export default abstract class Chart<T extends Schema> {
 | 
				
			||||||
		 * にする
 | 
							 * にする
 | 
				
			||||||
		 */
 | 
							 */
 | 
				
			||||||
		for (const record of chart) {
 | 
							for (const record of chart) {
 | 
				
			||||||
			for (const [k, v] of Object.entries(record)) {
 | 
								for (const [k, v] of Object.entries(record) as ([keyof typeof record, number])[]) {
 | 
				
			||||||
				if (res[k]) {
 | 
									if (res[k]) {
 | 
				
			||||||
					res[k].push(v);
 | 
										res[k].push(v);
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,5 @@
 | 
				
			||||||
import { Instance } from '@/models/entities/instance';
 | 
					import { Instance } from '@/models/entities/instance';
 | 
				
			||||||
import { Instances } from '@/models/index';
 | 
					import { Instances } from '@/models/index';
 | 
				
			||||||
import { federationChart } from '@/services/chart/index';
 | 
					 | 
				
			||||||
import { genId } from '@/misc/gen-id';
 | 
					import { genId } from '@/misc/gen-id';
 | 
				
			||||||
import { toPuny } from '@/misc/convert-host';
 | 
					import { toPuny } from '@/misc/convert-host';
 | 
				
			||||||
import { Cache } from '@/misc/cache';
 | 
					import { Cache } from '@/misc/cache';
 | 
				
			||||||
| 
						 | 
					@ -23,8 +22,6 @@ export async function registerOrFetchInstanceDoc(host: string): Promise<Instance
 | 
				
			||||||
			lastCommunicatedAt: new Date(),
 | 
								lastCommunicatedAt: new Date(),
 | 
				
			||||||
		}).then(x => Instances.findOneOrFail(x.identifiers[0]));
 | 
							}).then(x => Instances.findOneOrFail(x.identifiers[0]));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		federationChart.update(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		cache.set(host, i);
 | 
							cache.set(host, i);
 | 
				
			||||||
		return i;
 | 
							return i;
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3134,9 +3134,9 @@ github-from-package@0.0.0:
 | 
				
			||||||
  integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=
 | 
					  integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
glob-parent@^5.1.0, glob-parent@~5.1.0:
 | 
					glob-parent@^5.1.0, glob-parent@~5.1.0:
 | 
				
			||||||
  version "5.1.1"
 | 
					  version "5.1.2"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
 | 
					  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
 | 
				
			||||||
  integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
 | 
					  integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    is-glob "^4.0.1"
 | 
					    is-glob "^4.0.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3741,14 +3741,7 @@ is-generator-function@^1.0.7:
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.7.tgz#d2132e529bb0000a7f80794d4bdf5cd5e5813522"
 | 
					  resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.7.tgz#d2132e529bb0000a7f80794d4bdf5cd5e5813522"
 | 
				
			||||||
  integrity sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==
 | 
					  integrity sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
 | 
					is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
 | 
				
			||||||
  version "4.0.1"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
 | 
					 | 
				
			||||||
  integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
 | 
					 | 
				
			||||||
  dependencies:
 | 
					 | 
				
			||||||
    is-extglob "^2.1.1"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
is-glob@^4.0.3:
 | 
					 | 
				
			||||||
  version "4.0.3"
 | 
					  version "4.0.3"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
 | 
					  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
 | 
				
			||||||
  integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
 | 
					  integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
 | 
				
			||||||
| 
						 | 
					@ -4643,17 +4636,10 @@ minipass-sized@^1.0.3:
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    minipass "^3.0.0"
 | 
					    minipass "^3.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
minipass@^3.0.0:
 | 
					minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3:
 | 
				
			||||||
  version "3.1.1"
 | 
					  version "3.1.6"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.1.tgz#7607ce778472a185ad6d89082aa2070f79cedcd5"
 | 
					  resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee"
 | 
				
			||||||
  integrity sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==
 | 
					  integrity sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==
 | 
				
			||||||
  dependencies:
 | 
					 | 
				
			||||||
    yallist "^4.0.0"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3:
 | 
					 | 
				
			||||||
  version "3.1.3"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd"
 | 
					 | 
				
			||||||
  integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==
 | 
					 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    yallist "^4.0.0"
 | 
					    yallist "^4.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4882,10 +4868,12 @@ node-fetch@*:
 | 
				
			||||||
    fetch-blob "^3.1.4"
 | 
					    fetch-blob "^3.1.4"
 | 
				
			||||||
    formdata-polyfill "^4.0.10"
 | 
					    formdata-polyfill "^4.0.10"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
node-fetch@2.6.1, node-fetch@^2.6.1:
 | 
					node-fetch@2.6.7, node-fetch@^2.6.1:
 | 
				
			||||||
  version "2.6.1"
 | 
					  version "2.6.7"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
 | 
					  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
 | 
				
			||||||
  integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
 | 
					  integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    whatwg-url "^5.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
node-fetch@3.0.0-beta.9:
 | 
					node-fetch@3.0.0-beta.9:
 | 
				
			||||||
  version "3.0.0-beta.9"
 | 
					  version "3.0.0-beta.9"
 | 
				
			||||||
| 
						 | 
					@ -4959,9 +4947,9 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
 | 
				
			||||||
  integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
 | 
					  integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
normalize-url@^4.1.0:
 | 
					normalize-url@^4.1.0:
 | 
				
			||||||
  version "4.5.0"
 | 
					  version "4.5.1"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129"
 | 
					  resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
 | 
				
			||||||
  integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==
 | 
					  integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
npm-run-path@^5.0.1:
 | 
					npm-run-path@^5.0.1:
 | 
				
			||||||
  version "5.0.1"
 | 
					  version "5.0.1"
 | 
				
			||||||
| 
						 | 
					@ -5293,9 +5281,9 @@ path-key@^4.0.0:
 | 
				
			||||||
  integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==
 | 
					  integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
path-parse@^1.0.6:
 | 
					path-parse@^1.0.6:
 | 
				
			||||||
  version "1.0.6"
 | 
					  version "1.0.7"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
 | 
					  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
 | 
				
			||||||
  integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
 | 
					  integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
path-to-regexp@^6.1.0:
 | 
					path-to-regexp@^6.1.0:
 | 
				
			||||||
  version "6.1.0"
 | 
					  version "6.1.0"
 | 
				
			||||||
| 
						 | 
					@ -6169,20 +6157,11 @@ signal-exit@^3.0.5:
 | 
				
			||||||
  integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==
 | 
					  integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
simple-concat@^1.0.0:
 | 
					simple-concat@^1.0.0:
 | 
				
			||||||
  version "1.0.0"
 | 
					  version "1.0.1"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6"
 | 
					  resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
 | 
				
			||||||
  integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=
 | 
					  integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
simple-get@^4.0.0:
 | 
					simple-get@^4.0.0, simple-get@^4.0.1:
 | 
				
			||||||
  version "4.0.0"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.0.tgz#73fa628278d21de83dadd5512d2cc1f4872bd675"
 | 
					 | 
				
			||||||
  integrity sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ==
 | 
					 | 
				
			||||||
  dependencies:
 | 
					 | 
				
			||||||
    decompress-response "^6.0.0"
 | 
					 | 
				
			||||||
    once "^1.3.1"
 | 
					 | 
				
			||||||
    simple-concat "^1.0.0"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
simple-get@^4.0.1:
 | 
					 | 
				
			||||||
  version "4.0.1"
 | 
					  version "4.0.1"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543"
 | 
					  resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543"
 | 
				
			||||||
  integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==
 | 
					  integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==
 | 
				
			||||||
| 
						 | 
					@ -6542,19 +6521,7 @@ tar-stream@^2.1.4, tar-stream@^2.2.0:
 | 
				
			||||||
    inherits "^2.0.3"
 | 
					    inherits "^2.0.3"
 | 
				
			||||||
    readable-stream "^3.1.1"
 | 
					    readable-stream "^3.1.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
tar@^6.0.2:
 | 
					tar@^6.0.2, tar@^6.1.2:
 | 
				
			||||||
  version "6.0.5"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f"
 | 
					 | 
				
			||||||
  integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==
 | 
					 | 
				
			||||||
  dependencies:
 | 
					 | 
				
			||||||
    chownr "^2.0.0"
 | 
					 | 
				
			||||||
    fs-minipass "^2.0.0"
 | 
					 | 
				
			||||||
    minipass "^3.0.0"
 | 
					 | 
				
			||||||
    minizlib "^2.1.1"
 | 
					 | 
				
			||||||
    mkdirp "^1.0.3"
 | 
					 | 
				
			||||||
    yallist "^4.0.0"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
tar@^6.1.2:
 | 
					 | 
				
			||||||
  version "6.1.11"
 | 
					  version "6.1.11"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
 | 
					  resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
 | 
				
			||||||
  integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==
 | 
					  integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==
 | 
				
			||||||
| 
						 | 
					@ -6653,6 +6620,11 @@ tr46@^3.0.0:
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    punycode "^2.1.1"
 | 
					    punycode "^2.1.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tr46@~0.0.3:
 | 
				
			||||||
 | 
					  version "0.0.3"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
 | 
				
			||||||
 | 
					  integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
trace-redirect@1.0.6:
 | 
					trace-redirect@1.0.6:
 | 
				
			||||||
  version "1.0.6"
 | 
					  version "1.0.6"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/trace-redirect/-/trace-redirect-1.0.6.tgz#ac629b5bf8247d30dde5a35fe9811b811075b504"
 | 
					  resolved "https://registry.yarnpkg.com/trace-redirect/-/trace-redirect-1.0.6.tgz#ac629b5bf8247d30dde5a35fe9811b811075b504"
 | 
				
			||||||
| 
						 | 
					@ -6998,6 +6970,11 @@ web-streams-polyfill@^3.0.3:
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz#a6b74026b38e4885869fb5c589e90b95ccfc7965"
 | 
					  resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz#a6b74026b38e4885869fb5c589e90b95ccfc7965"
 | 
				
			||||||
  integrity sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==
 | 
					  integrity sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					webidl-conversions@^3.0.0:
 | 
				
			||||||
 | 
					  version "3.0.1"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
 | 
				
			||||||
 | 
					  integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
webidl-conversions@^7.0.0:
 | 
					webidl-conversions@^7.0.0:
 | 
				
			||||||
  version "7.0.0"
 | 
					  version "7.0.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
 | 
					  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
 | 
				
			||||||
| 
						 | 
					@ -7035,6 +7012,14 @@ whatwg-url@^10.0.0:
 | 
				
			||||||
    tr46 "^3.0.0"
 | 
					    tr46 "^3.0.0"
 | 
				
			||||||
    webidl-conversions "^7.0.0"
 | 
					    webidl-conversions "^7.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					whatwg-url@^5.0.0:
 | 
				
			||||||
 | 
					  version "5.0.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
 | 
				
			||||||
 | 
					  integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0=
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    tr46 "~0.0.3"
 | 
				
			||||||
 | 
					    webidl-conversions "^3.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
which-boxed-primitive@^1.0.2:
 | 
					which-boxed-primitive@^1.0.2:
 | 
				
			||||||
  version "1.0.2"
 | 
					  version "1.0.2"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
 | 
					  resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -213,17 +213,18 @@ export default defineComponent({
 | 
				
			||||||
						data: x.data.slice().reverse(),
 | 
											data: x.data.slice().reverse(),
 | 
				
			||||||
						tension: 0.3,
 | 
											tension: 0.3,
 | 
				
			||||||
						pointRadius: 0,
 | 
											pointRadius: 0,
 | 
				
			||||||
						borderWidth: 2,
 | 
											borderWidth: props.bar ? 0 : 2,
 | 
				
			||||||
						borderColor: x.color ? x.color : getColor(i),
 | 
											borderColor: x.color ? x.color : getColor(i),
 | 
				
			||||||
						borderDash: x.borderDash || [],
 | 
											borderDash: x.borderDash || [],
 | 
				
			||||||
						borderJoinStyle: 'round',
 | 
											borderJoinStyle: 'round',
 | 
				
			||||||
						backgroundColor: alpha(x.color ? x.color : getColor(i), 0.1),
 | 
											borderRadius: props.bar ? 3 : undefined,
 | 
				
			||||||
						gradient: {
 | 
											backgroundColor: props.bar ? (x.color ? x.color : getColor(i)) : alpha(x.color ? x.color : getColor(i), 0.1),
 | 
				
			||||||
 | 
											gradient: props.bar ? undefined : {
 | 
				
			||||||
							backgroundColor: {
 | 
												backgroundColor: {
 | 
				
			||||||
								axis: 'y',
 | 
													axis: 'y',
 | 
				
			||||||
								colors: {
 | 
													colors: {
 | 
				
			||||||
									0: alpha(x.color ? x.color : getColor(i), 0),
 | 
														0: alpha(x.color ? x.color : getColor(i), 0),
 | 
				
			||||||
									[maxes[i]]: alpha(x.color ? x.color : getColor(i), 0.1),
 | 
														[maxes[i]]: alpha(x.color ? x.color : getColor(i), 0.15),
 | 
				
			||||||
								},
 | 
													},
 | 
				
			||||||
							},
 | 
												},
 | 
				
			||||||
						},
 | 
											},
 | 
				
			||||||
| 
						 | 
					@ -248,6 +249,7 @@ export default defineComponent({
 | 
				
			||||||
						x: {
 | 
											x: {
 | 
				
			||||||
							type: 'time',
 | 
												type: 'time',
 | 
				
			||||||
							stacked: props.stacked,
 | 
												stacked: props.stacked,
 | 
				
			||||||
 | 
												offset: false,
 | 
				
			||||||
							time: {
 | 
												time: {
 | 
				
			||||||
								stepSize: 1,
 | 
													stepSize: 1,
 | 
				
			||||||
								unit: props.span === 'day' ? 'month' : 'day',
 | 
													unit: props.span === 'day' ? 'month' : 'day',
 | 
				
			||||||
| 
						 | 
					@ -271,6 +273,7 @@ export default defineComponent({
 | 
				
			||||||
						y: {
 | 
											y: {
 | 
				
			||||||
							position: 'left',
 | 
												position: 'left',
 | 
				
			||||||
							stacked: props.stacked,
 | 
												stacked: props.stacked,
 | 
				
			||||||
 | 
												suggestedMax: 100,
 | 
				
			||||||
							grid: {
 | 
												grid: {
 | 
				
			||||||
								color: gridColor,
 | 
													color: gridColor,
 | 
				
			||||||
								borderColor: 'rgb(0, 0, 0, 0)',
 | 
													borderColor: 'rgb(0, 0, 0, 0)',
 | 
				
			||||||
| 
						 | 
					@ -308,7 +311,7 @@ export default defineComponent({
 | 
				
			||||||
							},
 | 
												},
 | 
				
			||||||
							external: externalTooltipHandler,
 | 
												external: externalTooltipHandler,
 | 
				
			||||||
						},
 | 
											},
 | 
				
			||||||
						zoom: {
 | 
											zoom: props.detailed ? {
 | 
				
			||||||
							pan: {
 | 
												pan: {
 | 
				
			||||||
								enabled: true,
 | 
													enabled: true,
 | 
				
			||||||
							},
 | 
												},
 | 
				
			||||||
| 
						 | 
					@ -334,7 +337,7 @@ export default defineComponent({
 | 
				
			||||||
									max: 'original',
 | 
														max: 'original',
 | 
				
			||||||
								},
 | 
													},
 | 
				
			||||||
							}
 | 
												}
 | 
				
			||||||
						},
 | 
											} : undefined,
 | 
				
			||||||
						gradient,
 | 
											gradient,
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
| 
						 | 
					@ -370,14 +373,14 @@ export default defineComponent({
 | 
				
			||||||
			const raw = await os.api('charts/federation', { limit: props.limit, span: props.span });
 | 
								const raw = await os.api('charts/federation', { limit: props.limit, span: props.span });
 | 
				
			||||||
			return {
 | 
								return {
 | 
				
			||||||
				series: [{
 | 
									series: [{
 | 
				
			||||||
					name: 'Total',
 | 
										name: 'Sub',
 | 
				
			||||||
					type: 'area',
 | 
										type: 'area',
 | 
				
			||||||
					data: format(raw.instance.total),
 | 
										data: format(raw.sub),
 | 
				
			||||||
					color: '#888888',
 | 
										color: colors.orange,
 | 
				
			||||||
				}, {
 | 
									}, {
 | 
				
			||||||
					name: 'Inc/Dec',
 | 
										name: 'Pub',
 | 
				
			||||||
					type: 'area',
 | 
										type: 'area',
 | 
				
			||||||
					data: format(sum(raw.instance.inc, negate(raw.instance.dec))),
 | 
										data: format(raw.pub),
 | 
				
			||||||
					color: colors.purple,
 | 
										color: colors.purple,
 | 
				
			||||||
				}, {
 | 
									}, {
 | 
				
			||||||
					name: 'Received',
 | 
										name: 'Received',
 | 
				
			||||||
| 
						 | 
					@ -426,7 +429,6 @@ export default defineComponent({
 | 
				
			||||||
				series: [{
 | 
									series: [{
 | 
				
			||||||
					name: 'All',
 | 
										name: 'All',
 | 
				
			||||||
					type: 'line',
 | 
										type: 'line',
 | 
				
			||||||
					borderDash: [5, 5],
 | 
					 | 
				
			||||||
					data: format(type == 'combined'
 | 
										data: format(type == 'combined'
 | 
				
			||||||
						? sum(raw.local.inc, negate(raw.local.dec), raw.remote.inc, negate(raw.remote.dec))
 | 
											? sum(raw.local.inc, negate(raw.local.dec), raw.remote.inc, negate(raw.remote.dec))
 | 
				
			||||||
						: sum(raw[type].inc, negate(raw[type].dec))
 | 
											: sum(raw[type].inc, negate(raw[type].dec))
 | 
				
			||||||
| 
						 | 
					@ -750,20 +752,28 @@ export default defineComponent({
 | 
				
			||||||
				series: [...(props.args.withoutAll ? [] : [{
 | 
									series: [...(props.args.withoutAll ? [] : [{
 | 
				
			||||||
					name: 'All',
 | 
										name: 'All',
 | 
				
			||||||
					type: 'line',
 | 
										type: 'line',
 | 
				
			||||||
					borderDash: [5, 5],
 | 
					 | 
				
			||||||
					data: format(sum(raw.inc, negate(raw.dec))),
 | 
										data: format(sum(raw.inc, negate(raw.dec))),
 | 
				
			||||||
 | 
										color: '#888888',
 | 
				
			||||||
				}]), {
 | 
									}]), {
 | 
				
			||||||
 | 
										name: 'With file',
 | 
				
			||||||
 | 
										type: 'area',
 | 
				
			||||||
 | 
										data: format(raw.diffs.withFile),
 | 
				
			||||||
 | 
										color: colors.purple,
 | 
				
			||||||
 | 
									}, {
 | 
				
			||||||
					name: 'Renotes',
 | 
										name: 'Renotes',
 | 
				
			||||||
					type: 'area',
 | 
										type: 'area',
 | 
				
			||||||
					data: format(raw.diffs.renote),
 | 
										data: format(raw.diffs.renote),
 | 
				
			||||||
 | 
										color: colors.green,
 | 
				
			||||||
				}, {
 | 
									}, {
 | 
				
			||||||
					name: 'Replies',
 | 
										name: 'Replies',
 | 
				
			||||||
					type: 'area',
 | 
										type: 'area',
 | 
				
			||||||
					data: format(raw.diffs.reply),
 | 
										data: format(raw.diffs.reply),
 | 
				
			||||||
 | 
										color: colors.yellow,
 | 
				
			||||||
				}, {
 | 
									}, {
 | 
				
			||||||
					name: 'Normal',
 | 
										name: 'Normal',
 | 
				
			||||||
					type: 'area',
 | 
										type: 'area',
 | 
				
			||||||
					data: format(raw.diffs.normal),
 | 
										data: format(raw.diffs.normal),
 | 
				
			||||||
 | 
										color: colors.blue,
 | 
				
			||||||
				}],
 | 
									}],
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="omfetrab" :class="['w' + width, 'h' + height, { big, asDrawer }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }">
 | 
					<div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }">
 | 
				
			||||||
	<input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" @paste.stop="paste" @keyup.enter="done()">
 | 
						<input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" @paste.stop="paste" @keyup.enter="done()">
 | 
				
			||||||
	<div ref="emojis" class="emojis">
 | 
						<div ref="emojis" class="emojis">
 | 
				
			||||||
		<section class="result">
 | 
							<section class="result">
 | 
				
			||||||
| 
						 | 
					@ -105,15 +105,16 @@ const emojis = ref<HTMLDivElement>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {
 | 
					const {
 | 
				
			||||||
	reactions: pinned,
 | 
						reactions: pinned,
 | 
				
			||||||
 | 
						reactionPickerSize,
 | 
				
			||||||
	reactionPickerWidth,
 | 
						reactionPickerWidth,
 | 
				
			||||||
	reactionPickerHeight,
 | 
						reactionPickerHeight,
 | 
				
			||||||
	disableShowingAnimatedImages,
 | 
						disableShowingAnimatedImages,
 | 
				
			||||||
	recentlyUsedEmojis,
 | 
						recentlyUsedEmojis,
 | 
				
			||||||
} = defaultStore.reactiveState;
 | 
					} = defaultStore.reactiveState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const size = computed(() => props.asReactionPicker ? reactionPickerSize.value : 1);
 | 
				
			||||||
const width = computed(() => props.asReactionPicker ? reactionPickerWidth.value : 3);
 | 
					const width = computed(() => props.asReactionPicker ? reactionPickerWidth.value : 3);
 | 
				
			||||||
const height = computed(() => props.asReactionPicker ? reactionPickerHeight.value : 2);
 | 
					const height = computed(() => props.asReactionPicker ? reactionPickerHeight.value : 2);
 | 
				
			||||||
const big = props.asReactionPicker ? isTouchUsing : false;
 | 
					 | 
				
			||||||
const customEmojiCategories = emojiCategories;
 | 
					const customEmojiCategories = emojiCategories;
 | 
				
			||||||
const customEmojis = instance.emojis;
 | 
					const customEmojis = instance.emojis;
 | 
				
			||||||
const q = ref<string | null>(null);
 | 
					const q = ref<string | null>(null);
 | 
				
			||||||
| 
						 | 
					@ -345,13 +346,20 @@ defineExpose({
 | 
				
			||||||
<style lang="scss" scoped>
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
.omfetrab {
 | 
					.omfetrab {
 | 
				
			||||||
	$pad: 8px;
 | 
						$pad: 8px;
 | 
				
			||||||
	--eachSize: 40px;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	display: flex;
 | 
						display: flex;
 | 
				
			||||||
	flex-direction: column;
 | 
						flex-direction: column;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	&.big {
 | 
						&.s1 {
 | 
				
			||||||
		--eachSize: 44px;
 | 
							--eachSize: 40px;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						&.s2 {
 | 
				
			||||||
 | 
							--eachSize: 45px;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						&.s3 {
 | 
				
			||||||
 | 
							--eachSize: 50px;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	&.w1 {
 | 
						&.w1 {
 | 
				
			||||||
| 
						 | 
					@ -369,6 +377,16 @@ defineExpose({
 | 
				
			||||||
		--columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
 | 
							--columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						&.w4 {
 | 
				
			||||||
 | 
							width: calc((var(--eachSize) * 8) + (#{$pad} * 2));
 | 
				
			||||||
 | 
							--columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						&.w5 {
 | 
				
			||||||
 | 
							width: calc((var(--eachSize) * 9) + (#{$pad} * 2));
 | 
				
			||||||
 | 
							--columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	&.h1 {
 | 
						&.h1 {
 | 
				
			||||||
		height: calc((var(--eachSize) * 4) + (#{$pad} * 2));
 | 
							height: calc((var(--eachSize) * 4) + (#{$pad} * 2));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -381,6 +399,10 @@ defineExpose({
 | 
				
			||||||
		height: calc((var(--eachSize) * 8) + (#{$pad} * 2));
 | 
							height: calc((var(--eachSize) * 8) + (#{$pad} * 2));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						&.h4 {
 | 
				
			||||||
 | 
							height: calc((var(--eachSize) * 10) + (#{$pad} * 2));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	&.asDrawer {
 | 
						&.asDrawer {
 | 
				
			||||||
		width: 100% !important;
 | 
							width: 100% !important;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,7 @@
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import { deviceKind } from '@/scripts/device-kind';
 | 
				
			||||||
import { defineComponent, inject, onMounted, onUnmounted, ref } from 'vue';
 | 
					import { defineComponent, inject, onMounted, onUnmounted, ref } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
| 
						 | 
					@ -35,7 +36,7 @@ export default defineComponent({
 | 
				
			||||||
		const margin = ref(0);
 | 
							const margin = ref(0);
 | 
				
			||||||
		const shouldSpacerMin = inject('shouldSpacerMin', false);
 | 
							const shouldSpacerMin = inject('shouldSpacerMin', false);
 | 
				
			||||||
		const adjust = (rect: { width: number; height: number; }) => {
 | 
							const adjust = (rect: { width: number; height: number; }) => {
 | 
				
			||||||
			if (shouldSpacerMin) {
 | 
								if (shouldSpacerMin || deviceKind === 'smartphone') {
 | 
				
			||||||
				margin.value = props.marginMin;
 | 
									margin.value = props.marginMin;
 | 
				
			||||||
				return;
 | 
									return;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -59,7 +59,7 @@ export default defineComponent({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	setup() {
 | 
						setup() {
 | 
				
			||||||
		const chartSpan = ref<'hour' | 'day'>('hour');
 | 
							const chartSpan = ref<'hour' | 'day'>('hour');
 | 
				
			||||||
		const chartSrc = ref('notes');
 | 
							const chartSrc = ref('active-users');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			chartSrc,
 | 
								chartSrc,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -154,11 +154,13 @@ const props = defineProps<{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const inChannel = inject('inChannel', null);
 | 
					const inChannel = inject('inChannel', null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const note = $ref(JSON.parse(JSON.stringify(props.note)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const isRenote = (
 | 
					const isRenote = (
 | 
				
			||||||
	props.note.renote != null &&
 | 
						note.renote != null &&
 | 
				
			||||||
	props.note.text == null &&
 | 
						note.text == null &&
 | 
				
			||||||
	props.note.fileIds.length === 0 &&
 | 
						note.fileIds.length === 0 &&
 | 
				
			||||||
	props.note.poll == null
 | 
						note.poll == null
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const el = ref<HTMLElement>();
 | 
					const el = ref<HTMLElement>();
 | 
				
			||||||
| 
						 | 
					@ -166,8 +168,8 @@ const menuButton = ref<HTMLElement>();
 | 
				
			||||||
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
 | 
					const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
 | 
				
			||||||
const renoteTime = ref<HTMLElement>();
 | 
					const renoteTime = ref<HTMLElement>();
 | 
				
			||||||
const reactButton = ref<HTMLElement>();
 | 
					const reactButton = ref<HTMLElement>();
 | 
				
			||||||
let appearNote = $ref(isRenote ? props.note.renote as misskey.entities.Note : props.note);
 | 
					let appearNote = $ref(isRenote ? note.renote as misskey.entities.Note : note);
 | 
				
			||||||
const isMyRenote = $i && ($i.id === props.note.userId);
 | 
					const isMyRenote = $i && ($i.id === note.userId);
 | 
				
			||||||
const showContent = ref(false);
 | 
					const showContent = ref(false);
 | 
				
			||||||
const isDeleted = ref(false);
 | 
					const isDeleted = ref(false);
 | 
				
			||||||
const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords));
 | 
					const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords));
 | 
				
			||||||
| 
						 | 
					@ -188,8 +190,9 @@ const keymap = {
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
useNoteCapture({
 | 
					useNoteCapture({
 | 
				
			||||||
	appearNote: $$(appearNote),
 | 
					 | 
				
			||||||
	rootEl: el,
 | 
						rootEl: el,
 | 
				
			||||||
 | 
						note: $$(appearNote),
 | 
				
			||||||
 | 
						isDeletedRef: isDeleted,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function reply(viaKeyboard = false): void {
 | 
					function reply(viaKeyboard = false): void {
 | 
				
			||||||
| 
						 | 
					@ -237,12 +240,12 @@ function onContextmenu(ev: MouseEvent): void {
 | 
				
			||||||
		ev.preventDefault();
 | 
							ev.preventDefault();
 | 
				
			||||||
		react();
 | 
							react();
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		os.contextMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), ev).then(focus);
 | 
							os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton }), ev).then(focus);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function menu(viaKeyboard = false): void {
 | 
					function menu(viaKeyboard = false): void {
 | 
				
			||||||
	os.popupMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), menuButton.value, {
 | 
						os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton }), menuButton.value, {
 | 
				
			||||||
		viaKeyboard
 | 
							viaKeyboard
 | 
				
			||||||
	}).then(focus);
 | 
						}).then(focus);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -255,7 +258,7 @@ function showRenoteMenu(viaKeyboard = false): void {
 | 
				
			||||||
		danger: true,
 | 
							danger: true,
 | 
				
			||||||
		action: () => {
 | 
							action: () => {
 | 
				
			||||||
			os.api('notes/delete', {
 | 
								os.api('notes/delete', {
 | 
				
			||||||
				noteId: props.note.id
 | 
									noteId: note.id
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
			isDeleted.value = true;
 | 
								isDeleted.value = true;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -138,11 +138,13 @@ const props = defineProps<{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const inChannel = inject('inChannel', null);
 | 
					const inChannel = inject('inChannel', null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const note = $ref(JSON.parse(JSON.stringify(props.note)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const isRenote = (
 | 
					const isRenote = (
 | 
				
			||||||
	props.note.renote != null &&
 | 
						note.renote != null &&
 | 
				
			||||||
	props.note.text == null &&
 | 
						note.text == null &&
 | 
				
			||||||
	props.note.fileIds.length === 0 &&
 | 
						note.fileIds.length === 0 &&
 | 
				
			||||||
	props.note.poll == null
 | 
						note.poll == null
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const el = ref<HTMLElement>();
 | 
					const el = ref<HTMLElement>();
 | 
				
			||||||
| 
						 | 
					@ -150,8 +152,8 @@ const menuButton = ref<HTMLElement>();
 | 
				
			||||||
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
 | 
					const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
 | 
				
			||||||
const renoteTime = ref<HTMLElement>();
 | 
					const renoteTime = ref<HTMLElement>();
 | 
				
			||||||
const reactButton = ref<HTMLElement>();
 | 
					const reactButton = ref<HTMLElement>();
 | 
				
			||||||
let appearNote = $ref(isRenote ? props.note.renote as misskey.entities.Note : props.note);
 | 
					let appearNote = $ref(isRenote ? note.renote as misskey.entities.Note : note);
 | 
				
			||||||
const isMyRenote = $i && ($i.id === props.note.userId);
 | 
					const isMyRenote = $i && ($i.id === note.userId);
 | 
				
			||||||
const showContent = ref(false);
 | 
					const showContent = ref(false);
 | 
				
			||||||
const collapsed = ref(appearNote.cw == null && appearNote.text != null && (
 | 
					const collapsed = ref(appearNote.cw == null && appearNote.text != null && (
 | 
				
			||||||
	(appearNote.text.split('\n').length > 9) ||
 | 
						(appearNote.text.split('\n').length > 9) ||
 | 
				
			||||||
| 
						 | 
					@ -176,8 +178,9 @@ const keymap = {
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
useNoteCapture({
 | 
					useNoteCapture({
 | 
				
			||||||
	appearNote: $$(appearNote),
 | 
					 | 
				
			||||||
	rootEl: el,
 | 
						rootEl: el,
 | 
				
			||||||
 | 
						note: $$(appearNote),
 | 
				
			||||||
 | 
						isDeletedRef: isDeleted,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function reply(viaKeyboard = false): void {
 | 
					function reply(viaKeyboard = false): void {
 | 
				
			||||||
| 
						 | 
					@ -225,12 +228,12 @@ function onContextmenu(ev: MouseEvent): void {
 | 
				
			||||||
		ev.preventDefault();
 | 
							ev.preventDefault();
 | 
				
			||||||
		react();
 | 
							react();
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		os.contextMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), ev).then(focus);
 | 
							os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton }), ev).then(focus);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function menu(viaKeyboard = false): void {
 | 
					function menu(viaKeyboard = false): void {
 | 
				
			||||||
	os.popupMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), menuButton.value, {
 | 
						os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton }), menuButton.value, {
 | 
				
			||||||
		viaKeyboard
 | 
							viaKeyboard
 | 
				
			||||||
	}).then(focus);
 | 
						}).then(focus);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -243,7 +246,7 @@ function showRenoteMenu(viaKeyboard = false): void {
 | 
				
			||||||
		danger: true,
 | 
							danger: true,
 | 
				
			||||||
		action: () => {
 | 
							action: () => {
 | 
				
			||||||
			os.api('notes/delete', {
 | 
								os.api('notes/delete', {
 | 
				
			||||||
				noteId: props.note.id
 | 
									noteId: note.id
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
			isDeleted.value = true;
 | 
								isDeleted.value = true;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -180,12 +180,12 @@ const setPosition = () => {
 | 
				
			||||||
	el.value.style.top = top + 'px';
 | 
						el.value.style.top = top + 'px';
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let loopHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(() => {
 | 
					onMounted(() => {
 | 
				
			||||||
	nextTick(() => {
 | 
						nextTick(() => {
 | 
				
			||||||
		setPosition();
 | 
							setPosition();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		let loopHandler;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const loop = () => {
 | 
							const loop = () => {
 | 
				
			||||||
			loopHandler = window.requestAnimationFrame(() => {
 | 
								loopHandler = window.requestAnimationFrame(() => {
 | 
				
			||||||
				setPosition();
 | 
									setPosition();
 | 
				
			||||||
| 
						 | 
					@ -194,11 +194,11 @@ onMounted(() => {
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		loop();
 | 
							loop();
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		onUnmounted(() => {
 | 
					onUnmounted(() => {
 | 
				
			||||||
	window.cancelAnimationFrame(loopHandler);
 | 
						window.cancelAnimationFrame(loopHandler);
 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -95,8 +95,7 @@ window.addEventListener('resize', () => {
 | 
				
			||||||
if (['smartphone', 'tablet'].includes(deviceKind)) {
 | 
					if (['smartphone', 'tablet'].includes(deviceKind)) {
 | 
				
			||||||
	const viewport = document.getElementsByName('viewport').item(0);
 | 
						const viewport = document.getElementsByName('viewport').item(0);
 | 
				
			||||||
	viewport.setAttribute('content',
 | 
						viewport.setAttribute('content',
 | 
				
			||||||
		`${viewport.getAttribute('content')},minimum-scale=1,maximum-scale=1,user-scalable=no`);
 | 
							`${viewport.getAttribute('content')}, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover`);
 | 
				
			||||||
	document.head.appendChild(viewport);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//#region Set lang attr
 | 
					//#region Set lang attr
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,17 +17,26 @@
 | 
				
			||||||
		<template #caption>{{ $ts.reactionSettingDescription2 }} <button class="_textButton" @click="preview">{{ $ts.preview }}</button></template>
 | 
							<template #caption>{{ $ts.reactionSettingDescription2 }} <button class="_textButton" @click="preview">{{ $ts.preview }}</button></template>
 | 
				
			||||||
	</FromSlot>
 | 
						</FromSlot>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<FormRadios v-model="reactionPickerWidth" class="_formBlock">
 | 
						<FormRadios v-model="reactionPickerSize" class="_formBlock">
 | 
				
			||||||
		<template #label>{{ $ts.width }}</template>
 | 
							<template #label>{{ $ts.size }}</template>
 | 
				
			||||||
		<option :value="1">{{ $ts.small }}</option>
 | 
							<option :value="1">{{ $ts.small }}</option>
 | 
				
			||||||
		<option :value="2">{{ $ts.medium }}</option>
 | 
							<option :value="2">{{ $ts.medium }}</option>
 | 
				
			||||||
		<option :value="3">{{ $ts.large }}</option>
 | 
							<option :value="3">{{ $ts.large }}</option>
 | 
				
			||||||
	</FormRadios>
 | 
						</FormRadios>
 | 
				
			||||||
 | 
						<FormRadios v-model="reactionPickerWidth" class="_formBlock">
 | 
				
			||||||
 | 
							<template #label>{{ $ts.numberOfColumn }}</template>
 | 
				
			||||||
 | 
							<option :value="1">5</option>
 | 
				
			||||||
 | 
							<option :value="2">6</option>
 | 
				
			||||||
 | 
							<option :value="3">7</option>
 | 
				
			||||||
 | 
							<option :value="4">8</option>
 | 
				
			||||||
 | 
							<option :value="5">9</option>
 | 
				
			||||||
 | 
						</FormRadios>
 | 
				
			||||||
	<FormRadios v-model="reactionPickerHeight" class="_formBlock">
 | 
						<FormRadios v-model="reactionPickerHeight" class="_formBlock">
 | 
				
			||||||
		<template #label>{{ $ts.height }}</template>
 | 
							<template #label>{{ $ts.height }}</template>
 | 
				
			||||||
		<option :value="1">{{ $ts.small }}</option>
 | 
							<option :value="1">{{ $ts.small }}</option>
 | 
				
			||||||
		<option :value="2">{{ $ts.medium }}</option>
 | 
							<option :value="2">{{ $ts.medium }}</option>
 | 
				
			||||||
		<option :value="3">{{ $ts.large }}</option>
 | 
							<option :value="3">{{ $ts.large }}</option>
 | 
				
			||||||
 | 
							<option :value="4">{{ $ts.large }}+</option>
 | 
				
			||||||
	</FormRadios>
 | 
						</FormRadios>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<FormSwitch v-model="reactionPickerUseDrawerForMobile" class="_formBlock">
 | 
						<FormSwitch v-model="reactionPickerUseDrawerForMobile" class="_formBlock">
 | 
				
			||||||
| 
						 | 
					@ -60,6 +69,7 @@ import { i18n } from '@/i18n';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let reactions = $ref(JSON.parse(JSON.stringify(defaultStore.state.reactions)));
 | 
					let reactions = $ref(JSON.parse(JSON.stringify(defaultStore.state.reactions)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const reactionPickerSize = $computed(defaultStore.makeGetterSetter('reactionPickerSize'));
 | 
				
			||||||
const reactionPickerWidth = $computed(defaultStore.makeGetterSetter('reactionPickerWidth'));
 | 
					const reactionPickerWidth = $computed(defaultStore.makeGetterSetter('reactionPickerWidth'));
 | 
				
			||||||
const reactionPickerHeight = $computed(defaultStore.makeGetterSetter('reactionPickerHeight'));
 | 
					const reactionPickerHeight = $computed(defaultStore.makeGetterSetter('reactionPickerHeight'));
 | 
				
			||||||
const reactionPickerUseDrawerForMobile = $computed(defaultStore.makeGetterSetter('reactionPickerUseDrawerForMobile'));
 | 
					const reactionPickerUseDrawerForMobile = $computed(defaultStore.makeGetterSetter('reactionPickerUseDrawerForMobile'));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -81,18 +81,67 @@ export default defineComponent({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	async created() {
 | 
						async created() {
 | 
				
			||||||
		this.softMutedWords = this.$store.state.mutedWords.map(x => x.join(' ')).join('\n');
 | 
							const render = (mutedWords) => mutedWords.map(x => {
 | 
				
			||||||
		this.hardMutedWords = this.$i.mutedWords.map(x => x.join(' ')).join('\n');
 | 
								if (Array.isArray(x)) {
 | 
				
			||||||
 | 
									return x.join(' ');
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									return x;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}).join('\n');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.softMutedWords = render(this.$store.state.mutedWords);
 | 
				
			||||||
 | 
							this.hardMutedWords = render(this.$i.mutedWords);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.hardWordMutedNotesCount = (await os.api('i/get-word-muted-notes-count', {})).count;
 | 
							this.hardWordMutedNotesCount = (await os.api('i/get-word-muted-notes-count', {})).count;
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		async save() {
 | 
							async save() {
 | 
				
			||||||
			this.$store.set('mutedWords', this.softMutedWords.trim().split('\n').map(x => x.trim().split(' ')));
 | 
								const parseMutes = (mutes, tab) => {
 | 
				
			||||||
			await os.api('i/update', {
 | 
									// split into lines, remove empty lines and unnecessary whitespace
 | 
				
			||||||
				mutedWords: this.hardMutedWords.trim().split('\n').map(x => x.trim().split(' ')),
 | 
									let lines = mutes.trim().split('\n').map(line => line.trim()).filter(line => line != '');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// check each line if it is a RegExp or not
 | 
				
			||||||
 | 
									for (let i = 0; i < lines.length; i++) {
 | 
				
			||||||
 | 
										const line = lines[i]
 | 
				
			||||||
 | 
										const regexp = line.match(/^\/(.+)\/(.*)$/);
 | 
				
			||||||
 | 
										if (regexp) {
 | 
				
			||||||
 | 
											// check that the RegExp is valid
 | 
				
			||||||
 | 
											try {
 | 
				
			||||||
 | 
												new RegExp(regexp[1], regexp[2]);
 | 
				
			||||||
 | 
												// note that regex lines will not be split by spaces!
 | 
				
			||||||
 | 
											} catch (err) {
 | 
				
			||||||
 | 
												// invalid syntax: do not save, do not reset changed flag
 | 
				
			||||||
 | 
												os.alert({
 | 
				
			||||||
 | 
													type: 'error',
 | 
				
			||||||
 | 
													title: this.$ts.regexpError,
 | 
				
			||||||
 | 
													text: this.$t('regexpErrorDescription', { tab, line: i + 1 }) + "\n" + err.toString()
 | 
				
			||||||
							});
 | 
												});
 | 
				
			||||||
 | 
												// re-throw error so these invalid settings are not saved
 | 
				
			||||||
 | 
												throw err;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											lines[i] = line.split(' ');
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return lines;
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								let softMutes, hardMutes;
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									softMutes = parseMutes(this.softMutedWords, this.$ts._wordMute.soft);
 | 
				
			||||||
 | 
									hardMutes = parseMutes(this.hardMutedWords, this.$ts._wordMute.hard);
 | 
				
			||||||
 | 
								} catch (err) {
 | 
				
			||||||
 | 
									// already displayed error message in parseMutes
 | 
				
			||||||
 | 
									return;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								this.$store.set('mutedWords', softMutes);
 | 
				
			||||||
 | 
								await os.api('i/update', {
 | 
				
			||||||
 | 
									mutedWords: hardMutes,
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			this.changed = false;
 | 
								this.changed = false;
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,6 +18,6 @@ const props = withDefaults(defineProps<{
 | 
				
			||||||
	user: misskey.entities.User;
 | 
						user: misskey.entities.User;
 | 
				
			||||||
	limit?: number;
 | 
						limit?: number;
 | 
				
			||||||
}>(), {
 | 
					}>(), {
 | 
				
			||||||
	limit: 40,
 | 
						limit: 50,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,23 +1,32 @@
 | 
				
			||||||
export function checkWordMute(note: Record<string, any>, me: Record<string, any> | null | undefined, mutedWords: string[][]): boolean {
 | 
					export function checkWordMute(note: Record<string, any>, me: Record<string, any> | null | undefined, mutedWords: Array<string | string[]>): boolean {
 | 
				
			||||||
	// 自分自身
 | 
						// 自分自身
 | 
				
			||||||
	if (me && (note.userId === me.id)) return false;
 | 
						if (me && (note.userId === me.id)) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const words = mutedWords
 | 
						if (mutedWords.length > 0) {
 | 
				
			||||||
		// Clean up
 | 
					 | 
				
			||||||
		.map(xs => xs.filter(x => x !== ''))
 | 
					 | 
				
			||||||
		.filter(xs => xs.length > 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (words.length > 0) {
 | 
					 | 
				
			||||||
		if (note.text == null) return false;
 | 
							if (note.text == null) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const matched = words.some(and =>
 | 
							const matched = mutedWords.some(filter => {
 | 
				
			||||||
			and.every(keyword => {
 | 
								if (Array.isArray(filter)) {
 | 
				
			||||||
				const regexp = keyword.match(/^\/(.+)\/(.*)$/);
 | 
									// Clean up
 | 
				
			||||||
				if (regexp) {
 | 
									const filteredFilter = filter.filter(keyword => keyword !== '');
 | 
				
			||||||
 | 
									if (filteredFilter.length === 0) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return filteredFilter.every(keyword => note.text!.includes(keyword));
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									// represents RegExp
 | 
				
			||||||
 | 
									const regexp = filter.match(/^\/(.+)\/(.*)$/);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// This should never happen due to input sanitisation.
 | 
				
			||||||
 | 
									if (!regexp) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									try {
 | 
				
			||||||
					return new RegExp(regexp[1], regexp[2]).test(note.text!);
 | 
										return new RegExp(regexp[1], regexp[2]).test(note.text!);
 | 
				
			||||||
 | 
									} catch (err) {
 | 
				
			||||||
 | 
										// This should never happen due to input sanitisation.
 | 
				
			||||||
 | 
										return false;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				return note.text!.includes(keyword);
 | 
								}
 | 
				
			||||||
			}));
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (matched) return true;
 | 
							if (matched) return true;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +20,7 @@ export const builtinThemes = [
 | 
				
			||||||
	require('@/themes/l-apricot.json5'),
 | 
						require('@/themes/l-apricot.json5'),
 | 
				
			||||||
	require('@/themes/l-rainy.json5'),
 | 
						require('@/themes/l-rainy.json5'),
 | 
				
			||||||
	require('@/themes/l-vivid.json5'),
 | 
						require('@/themes/l-vivid.json5'),
 | 
				
			||||||
 | 
						require('@/themes/l-cherry.json5'),
 | 
				
			||||||
	require('@/themes/l-sushi.json5'),
 | 
						require('@/themes/l-sushi.json5'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	require('@/themes/d-dark.json5'),
 | 
						require('@/themes/d-dark.json5'),
 | 
				
			||||||
| 
						 | 
					@ -27,6 +28,7 @@ export const builtinThemes = [
 | 
				
			||||||
	require('@/themes/d-astro.json5'),
 | 
						require('@/themes/d-astro.json5'),
 | 
				
			||||||
	require('@/themes/d-future.json5'),
 | 
						require('@/themes/d-future.json5'),
 | 
				
			||||||
	require('@/themes/d-botanical.json5'),
 | 
						require('@/themes/d-botanical.json5'),
 | 
				
			||||||
 | 
						require('@/themes/d-cherry.json5'),
 | 
				
			||||||
	require('@/themes/d-pumpkin.json5'),
 | 
						require('@/themes/d-pumpkin.json5'),
 | 
				
			||||||
	require('@/themes/d-black.json5'),
 | 
						require('@/themes/d-black.json5'),
 | 
				
			||||||
] as Theme[];
 | 
					] as Theme[];
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,34 +5,35 @@ import { $i } from '@/account';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function useNoteCapture(props: {
 | 
					export function useNoteCapture(props: {
 | 
				
			||||||
	rootEl: Ref<HTMLElement>;
 | 
						rootEl: Ref<HTMLElement>;
 | 
				
			||||||
	appearNote: Ref<misskey.entities.Note>;
 | 
						note: Ref<misskey.entities.Note>;
 | 
				
			||||||
 | 
						isDeletedRef: Ref<boolean>;
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
	const appearNote = props.appearNote;
 | 
						const note = props.note;
 | 
				
			||||||
	const connection = $i ? stream : null;
 | 
						const connection = $i ? stream : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	function onStreamNoteUpdated(data): void {
 | 
						function onStreamNoteUpdated(data): void {
 | 
				
			||||||
		const { type, id, body } = data;
 | 
							const { type, id, body } = data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (id !== appearNote.value.id) return;
 | 
							if (id !== note.value.id) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		switch (type) {
 | 
							switch (type) {
 | 
				
			||||||
			case 'reacted': {
 | 
								case 'reacted': {
 | 
				
			||||||
				const reaction = body.reaction;
 | 
									const reaction = body.reaction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if (body.emoji) {
 | 
									if (body.emoji) {
 | 
				
			||||||
					const emojis = appearNote.value.emojis || [];
 | 
										const emojis = note.value.emojis || [];
 | 
				
			||||||
					if (!emojis.includes(body.emoji)) {
 | 
										if (!emojis.includes(body.emoji)) {
 | 
				
			||||||
						appearNote.value.emojis = [...emojis, body.emoji];
 | 
											note.value.emojis = [...emojis, body.emoji];
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる
 | 
									// TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる
 | 
				
			||||||
				const currentCount = (appearNote.value.reactions || {})[reaction] || 0;
 | 
									const currentCount = (note.value.reactions || {})[reaction] || 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				appearNote.value.reactions[reaction] = currentCount + 1;
 | 
									note.value.reactions[reaction] = currentCount + 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if ($i && (body.userId === $i.id)) {
 | 
									if ($i && (body.userId === $i.id)) {
 | 
				
			||||||
					appearNote.value.myReaction = reaction;
 | 
										note.value.myReaction = reaction;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
| 
						 | 
					@ -41,12 +42,12 @@ export function useNoteCapture(props: {
 | 
				
			||||||
				const reaction = body.reaction;
 | 
									const reaction = body.reaction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる
 | 
									// TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる
 | 
				
			||||||
				const currentCount = (appearNote.value.reactions || {})[reaction] || 0;
 | 
									const currentCount = (note.value.reactions || {})[reaction] || 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				appearNote.value.reactions[reaction] = Math.max(0, currentCount - 1);
 | 
									note.value.reactions[reaction] = Math.max(0, currentCount - 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if ($i && (body.userId === $i.id)) {
 | 
									if ($i && (body.userId === $i.id)) {
 | 
				
			||||||
					appearNote.value.myReaction = null;
 | 
										note.value.myReaction = null;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
| 
						 | 
					@ -54,7 +55,7 @@ export function useNoteCapture(props: {
 | 
				
			||||||
			case 'pollVoted': {
 | 
								case 'pollVoted': {
 | 
				
			||||||
				const choice = body.choice;
 | 
									const choice = body.choice;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				const choices = [...appearNote.value.poll.choices];
 | 
									const choices = [...note.value.poll.choices];
 | 
				
			||||||
				choices[choice] = {
 | 
									choices[choice] = {
 | 
				
			||||||
					...choices[choice],
 | 
										...choices[choice],
 | 
				
			||||||
					votes: choices[choice].votes + 1,
 | 
										votes: choices[choice].votes + 1,
 | 
				
			||||||
| 
						 | 
					@ -63,12 +64,12 @@ export function useNoteCapture(props: {
 | 
				
			||||||
					} : {})
 | 
										} : {})
 | 
				
			||||||
				};
 | 
									};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				appearNote.value.poll.choices = choices;
 | 
									note.value.poll.choices = choices;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			case 'deleted': {
 | 
								case 'deleted': {
 | 
				
			||||||
				appearNote.value.deletedAt = new Date();
 | 
									props.isDeletedRef.value = true;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -77,7 +78,7 @@ export function useNoteCapture(props: {
 | 
				
			||||||
	function capture(withHandler = false): void {
 | 
						function capture(withHandler = false): void {
 | 
				
			||||||
		if (connection) {
 | 
							if (connection) {
 | 
				
			||||||
			// TODO: このノートがストリーミング経由で流れてきた場合のみ sr する
 | 
								// TODO: このノートがストリーミング経由で流れてきた場合のみ sr する
 | 
				
			||||||
			connection.send(document.body.contains(props.rootEl.value) ? 'sr' : 's', { id: appearNote.value.id });
 | 
								connection.send(document.body.contains(props.rootEl.value) ? 'sr' : 's', { id: note.value.id });
 | 
				
			||||||
			if (withHandler) connection.on('noteUpdated', onStreamNoteUpdated);
 | 
								if (withHandler) connection.on('noteUpdated', onStreamNoteUpdated);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -85,7 +86,7 @@ export function useNoteCapture(props: {
 | 
				
			||||||
	function decapture(withHandler = false): void {
 | 
						function decapture(withHandler = false): void {
 | 
				
			||||||
		if (connection) {
 | 
							if (connection) {
 | 
				
			||||||
			connection.send('un', {
 | 
								connection.send('un', {
 | 
				
			||||||
				id: appearNote.value.id,
 | 
									id: note.value.id,
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
			if (withHandler) connection.off('noteUpdated', onStreamNoteUpdated);
 | 
								if (withHandler) connection.off('noteUpdated', onStreamNoteUpdated);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -182,6 +182,10 @@ export const defaultStore = markRaw(new Storage('base', {
 | 
				
			||||||
		where: 'device',
 | 
							where: 'device',
 | 
				
			||||||
		default: 'remote' as 'none' | 'remote' | 'always'
 | 
							default: 'remote' as 'none' | 'remote' | 'always'
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						reactionPickerSize: {
 | 
				
			||||||
 | 
							where: 'device',
 | 
				
			||||||
 | 
							default: 1
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	reactionPickerWidth: {
 | 
						reactionPickerWidth: {
 | 
				
			||||||
		where: 'device',
 | 
							where: 'device',
 | 
				
			||||||
		default: 1
 | 
							default: 1
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										20
									
								
								packages/client/src/themes/d-cherry.json5
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								packages/client/src/themes/d-cherry.json5
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,20 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						id: '679b3b87-a4e9-4789-8696-b56c15cc33b0',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						name: 'Mi Cherry Dark',
 | 
				
			||||||
 | 
						author: 'syuilo',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						base: 'dark',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						props: {
 | 
				
			||||||
 | 
							accent: 'rgb(255, 89, 117)',
 | 
				
			||||||
 | 
							bg: 'rgb(28, 28, 37)',
 | 
				
			||||||
 | 
							fg: 'rgb(236, 239, 244)',
 | 
				
			||||||
 | 
							panel: 'rgb(35, 35, 47)',
 | 
				
			||||||
 | 
							renote: '@accent',
 | 
				
			||||||
 | 
							link: '@accent',
 | 
				
			||||||
 | 
							mention: '@accent',
 | 
				
			||||||
 | 
							hashtag: '@accent',
 | 
				
			||||||
 | 
							divider: 'rgb(63, 63, 80)',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								packages/client/src/themes/l-cherry.json5
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								packages/client/src/themes/l-cherry.json5
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						id: 'ac168876-f737-4074-a3fc-a370c732ef48',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						name: 'Mi Cherry Light',
 | 
				
			||||||
 | 
						author: 'syuilo',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						base: 'light',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						props: {
 | 
				
			||||||
 | 
							accent: 'rgb(219, 96, 114)',
 | 
				
			||||||
 | 
							bg: 'rgb(254, 248, 249)',
 | 
				
			||||||
 | 
							fg: 'rgb(152, 13, 26)',
 | 
				
			||||||
 | 
							panel: 'rgb(255, 255, 255)',
 | 
				
			||||||
 | 
							renote: '@accent',
 | 
				
			||||||
 | 
							link: 'rgb(156, 187, 5)',
 | 
				
			||||||
 | 
							mention: '@accent',
 | 
				
			||||||
 | 
							hashtag: '@accent',
 | 
				
			||||||
 | 
							divider: 'rgba(134, 51, 51, 0.1)',
 | 
				
			||||||
 | 
							inputBorderHover: 'rgb(238, 221, 222)',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -305,7 +305,7 @@ export default defineComponent({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				&.post:before {
 | 
									&.post:before {
 | 
				
			||||||
					width: calc(100% - 28px);
 | 
										width: calc(100% - 28px);
 | 
				
			||||||
					height: min-content;
 | 
										height: auto;
 | 
				
			||||||
					aspect-ratio: 1/1;
 | 
										aspect-ratio: 1/1;
 | 
				
			||||||
					border-radius: 100%;
 | 
										border-radius: 100%;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -276,7 +276,7 @@ export default defineComponent({
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			> * {
 | 
								> * {
 | 
				
			||||||
				font-size: 22px;
 | 
									font-size: 20px;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			&:disabled {
 | 
								&:disabled {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -340,13 +340,14 @@ const wallpaper = localStorage.getItem('wallpaper') != null;
 | 
				
			||||||
		z-index: 1000;
 | 
							z-index: 1000;
 | 
				
			||||||
		bottom: 0;
 | 
							bottom: 0;
 | 
				
			||||||
		left: 0;
 | 
							left: 0;
 | 
				
			||||||
		padding: 16px;
 | 
							padding: 16px 16px calc(env(safe-area-inset-bottom, 0px) + 16px) 16px;
 | 
				
			||||||
		display: flex;
 | 
							display: flex;
 | 
				
			||||||
		width: 100%;
 | 
							width: 100%;
 | 
				
			||||||
		box-sizing: border-box;
 | 
							box-sizing: border-box;
 | 
				
			||||||
		-webkit-backdrop-filter: var(--blur, blur(32px));
 | 
							-webkit-backdrop-filter: var(--blur, blur(32px));
 | 
				
			||||||
		backdrop-filter: var(--blur, blur(32px));
 | 
							backdrop-filter: var(--blur, blur(32px));
 | 
				
			||||||
		background-color: var(--header);
 | 
							background-color: var(--header);
 | 
				
			||||||
 | 
							border-top: solid 0.5px var(--divider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		> .button {
 | 
							> .button {
 | 
				
			||||||
			position: relative;
 | 
								position: relative;
 | 
				
			||||||
| 
						 | 
					@ -392,7 +393,7 @@ const wallpaper = localStorage.getItem('wallpaper') != null;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			> * {
 | 
								> * {
 | 
				
			||||||
				font-size: 22px;
 | 
									font-size: 20px;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			&:disabled {
 | 
								&:disabled {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1322,11 +1322,11 @@ aws4@^1.8.0:
 | 
				
			||||||
  integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==
 | 
					  integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
axios@^0.21.1:
 | 
					axios@^0.21.1:
 | 
				
			||||||
  version "0.21.1"
 | 
					  version "0.21.4"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
 | 
					  resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
 | 
				
			||||||
  integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
 | 
					  integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    follow-redirects "^1.10.0"
 | 
					    follow-redirects "^1.14.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
babel-walk@3.0.0-canary-5:
 | 
					babel-walk@3.0.0-canary-5:
 | 
				
			||||||
  version "3.0.0-canary-5"
 | 
					  version "3.0.0-canary-5"
 | 
				
			||||||
| 
						 | 
					@ -1426,38 +1426,16 @@ browser-stdout@1.3.1:
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
 | 
					  resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
 | 
				
			||||||
  integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
 | 
					  integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
browserslist@^4.0.0, browserslist@^4.14.5:
 | 
					browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.0, browserslist@^4.16.6:
 | 
				
			||||||
  version "4.16.3"
 | 
					  version "4.19.1"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717"
 | 
					  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3"
 | 
				
			||||||
  integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==
 | 
					  integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    caniuse-lite "^1.0.30001181"
 | 
					    caniuse-lite "^1.0.30001286"
 | 
				
			||||||
    colorette "^1.2.1"
 | 
					    electron-to-chromium "^1.4.17"
 | 
				
			||||||
    electron-to-chromium "^1.3.649"
 | 
					 | 
				
			||||||
    escalade "^3.1.1"
 | 
					    escalade "^3.1.1"
 | 
				
			||||||
    node-releases "^1.1.70"
 | 
					    node-releases "^2.0.1"
 | 
				
			||||||
 | 
					    picocolors "^1.0.0"
 | 
				
			||||||
browserslist@^4.16.0:
 | 
					 | 
				
			||||||
  version "4.16.4"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.4.tgz#7ebf913487f40caf4637b892b268069951c35d58"
 | 
					 | 
				
			||||||
  integrity sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ==
 | 
					 | 
				
			||||||
  dependencies:
 | 
					 | 
				
			||||||
    caniuse-lite "^1.0.30001208"
 | 
					 | 
				
			||||||
    colorette "^1.2.2"
 | 
					 | 
				
			||||||
    electron-to-chromium "^1.3.712"
 | 
					 | 
				
			||||||
    escalade "^3.1.1"
 | 
					 | 
				
			||||||
    node-releases "^1.1.71"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
browserslist@^4.16.6:
 | 
					 | 
				
			||||||
  version "4.16.6"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2"
 | 
					 | 
				
			||||||
  integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==
 | 
					 | 
				
			||||||
  dependencies:
 | 
					 | 
				
			||||||
    caniuse-lite "^1.0.30001219"
 | 
					 | 
				
			||||||
    colorette "^1.2.2"
 | 
					 | 
				
			||||||
    electron-to-chromium "^1.3.723"
 | 
					 | 
				
			||||||
    escalade "^3.1.1"
 | 
					 | 
				
			||||||
    node-releases "^1.1.71"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
buffer-crc32@~0.2.3:
 | 
					buffer-crc32@~0.2.3:
 | 
				
			||||||
  version "0.2.13"
 | 
					  version "0.2.13"
 | 
				
			||||||
| 
						 | 
					@ -1527,10 +1505,10 @@ caniuse-api@^3.0.0:
 | 
				
			||||||
    lodash.memoize "^4.1.2"
 | 
					    lodash.memoize "^4.1.2"
 | 
				
			||||||
    lodash.uniq "^4.5.0"
 | 
					    lodash.uniq "^4.5.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001181, caniuse-lite@^1.0.30001208, caniuse-lite@^1.0.30001219:
 | 
					caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001286:
 | 
				
			||||||
  version "1.0.30001279"
 | 
					  version "1.0.30001311"
 | 
				
			||||||
  resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001279.tgz"
 | 
					  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001311.tgz#682ef3f4e617f1a177ad943de59775ed3032e511"
 | 
				
			||||||
  integrity sha512-VfEHpzHEXj6/CxggTwSFoZBBYGQfQv9Cf42KPlO79sWXCD1QNKWKsKzFeWL7QpZHJQYAvocqV6Rty1yJMkqWLQ==
 | 
					  integrity sha512-mleTFtFKfykEeW34EyfhGIFjGCqzhh38Y0LhdQ9aWF+HorZTtdgKV/1hEE0NlFkG2ubvisPV6l400tlbPys98A==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
caseless@~0.12.0:
 | 
					caseless@~0.12.0:
 | 
				
			||||||
  version "0.12.0"
 | 
					  version "0.12.0"
 | 
				
			||||||
| 
						 | 
					@ -1722,7 +1700,7 @@ colord@^2.9.1:
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.1.tgz#c961ea0efeb57c9f0f4834458f26cb9cc4a3f90e"
 | 
					  resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.1.tgz#c961ea0efeb57c9f0f4834458f26cb9cc4a3f90e"
 | 
				
			||||||
  integrity sha512-4LBMSt09vR0uLnPVkOUBnmxgoaeN4ewRbx801wY/bXcltXfpR/G46OdWn96XpYmCWuYvO46aBZP4NgX8HpNAcw==
 | 
					  integrity sha512-4LBMSt09vR0uLnPVkOUBnmxgoaeN4ewRbx801wY/bXcltXfpR/G46OdWn96XpYmCWuYvO46aBZP4NgX8HpNAcw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
colorette@^1.2.0, colorette@^1.2.1, colorette@^1.2.2:
 | 
					colorette@^1.2.0, colorette@^1.2.2:
 | 
				
			||||||
  version "1.2.2"
 | 
					  version "1.2.2"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
 | 
					  resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
 | 
				
			||||||
  integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
 | 
					  integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
 | 
				
			||||||
| 
						 | 
					@ -2260,20 +2238,10 @@ ecdsa-sig-formatter@1.0.11:
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    safe-buffer "^5.0.1"
 | 
					    safe-buffer "^5.0.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
electron-to-chromium@^1.3.649:
 | 
					electron-to-chromium@^1.4.17:
 | 
				
			||||||
  version "1.3.672"
 | 
					  version "1.4.68"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.672.tgz#3a6e335016dab4bc584d5292adc4f98f54541f6a"
 | 
					  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.68.tgz#d79447b6bd1bec9183f166bb33d4bef0d5e4e568"
 | 
				
			||||||
  integrity sha512-gFQe7HBb0lbOMqK2GAS5/1F+B0IMdYiAgB9OT/w1F4M7lgJK2aNOMNOM622aEax+nS1cTMytkiT0uMOkbtFmHw==
 | 
					  integrity sha512-cId+QwWrV8R1UawO6b9BR1hnkJ4EJPCPAr4h315vliHUtVUJDk39Sg1PMNnaWKfj5x+93ssjeJ9LKL6r8LaMiA==
 | 
				
			||||||
 | 
					 | 
				
			||||||
electron-to-chromium@^1.3.712:
 | 
					 | 
				
			||||||
  version "1.3.717"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.717.tgz#78d4c857070755fb58ab64bcc173db1d51cbc25f"
 | 
					 | 
				
			||||||
  integrity sha512-OfzVPIqD1MkJ7fX+yTl2nKyOE4FReeVfMCzzxQS+Kp43hZYwHwThlGP+EGIZRXJsxCM7dqo8Y65NOX/HP12iXQ==
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
electron-to-chromium@^1.3.723:
 | 
					 | 
				
			||||||
  version "1.3.742"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.742.tgz#7223215acbbd3a5284962ebcb6df85d88b95f200"
 | 
					 | 
				
			||||||
  integrity sha512-ihL14knI9FikJmH2XUIDdZFWJxvr14rPSdOhJ7PpS27xbz8qmaRwCwyg/bmFwjWKmWK9QyamiCZVCvXm5CH//Q==
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
emoji-regex@^8.0.0:
 | 
					emoji-regex@^8.0.0:
 | 
				
			||||||
  version "8.0.0"
 | 
					  version "8.0.0"
 | 
				
			||||||
| 
						 | 
					@ -2929,10 +2897,10 @@ flatted@^3.1.0:
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.0.tgz#a5d06b4a8b01e3a63771daa5cb7a1903e2e57067"
 | 
					  resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.0.tgz#a5d06b4a8b01e3a63771daa5cb7a1903e2e57067"
 | 
				
			||||||
  integrity sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==
 | 
					  integrity sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
follow-redirects@^1.10.0:
 | 
					follow-redirects@^1.14.0:
 | 
				
			||||||
  version "1.14.1"
 | 
					  version "1.14.8"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
 | 
					  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc"
 | 
				
			||||||
  integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==
 | 
					  integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
forever-agent@~0.6.1:
 | 
					forever-agent@~0.6.1:
 | 
				
			||||||
  version "0.6.1"
 | 
					  version "0.6.1"
 | 
				
			||||||
| 
						 | 
					@ -3041,9 +3009,9 @@ getpass@^0.1.1:
 | 
				
			||||||
    assert-plus "^1.0.0"
 | 
					    assert-plus "^1.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
glob-parent@^5.1.0, glob-parent@~5.1.0:
 | 
					glob-parent@^5.1.0, glob-parent@~5.1.0:
 | 
				
			||||||
  version "5.1.1"
 | 
					  version "5.1.2"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
 | 
					  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
 | 
				
			||||||
  integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
 | 
					  integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    is-glob "^4.0.1"
 | 
					    is-glob "^4.0.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3490,14 +3458,7 @@ is-fullwidth-code-point@^3.0.0:
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
 | 
					  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
 | 
				
			||||||
  integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
 | 
					  integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
 | 
					is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
 | 
				
			||||||
  version "4.0.1"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
 | 
					 | 
				
			||||||
  integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
 | 
					 | 
				
			||||||
  dependencies:
 | 
					 | 
				
			||||||
    is-extglob "^2.1.1"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
is-glob@^4.0.3:
 | 
					 | 
				
			||||||
  version "4.0.3"
 | 
					  version "4.0.3"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
 | 
					  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
 | 
				
			||||||
  integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
 | 
					  integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
 | 
				
			||||||
| 
						 | 
					@ -4245,19 +4206,21 @@ next-tick@~1.0.0:
 | 
				
			||||||
  integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
 | 
					  integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
node-fetch@^2.6.1:
 | 
					node-fetch@^2.6.1:
 | 
				
			||||||
  version "2.6.1"
 | 
					  version "2.6.7"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
 | 
					  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
 | 
				
			||||||
  integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
 | 
					  integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    whatwg-url "^5.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
node-gyp-build@~3.7.0:
 | 
					node-gyp-build@~3.7.0:
 | 
				
			||||||
  version "3.7.0"
 | 
					  version "3.7.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.7.0.tgz#daa77a4f547b9aed3e2aac779eaf151afd60ec8d"
 | 
					  resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.7.0.tgz#daa77a4f547b9aed3e2aac779eaf151afd60ec8d"
 | 
				
			||||||
  integrity sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w==
 | 
					  integrity sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
node-releases@^1.1.70, node-releases@^1.1.71:
 | 
					node-releases@^2.0.1:
 | 
				
			||||||
  version "1.1.71"
 | 
					  version "2.0.2"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb"
 | 
					  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01"
 | 
				
			||||||
  integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==
 | 
					  integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
normalize-path@^3.0.0, normalize-path@~3.0.0:
 | 
					normalize-path@^3.0.0, normalize-path@~3.0.0:
 | 
				
			||||||
  version "3.0.0"
 | 
					  version "3.0.0"
 | 
				
			||||||
| 
						 | 
					@ -4532,9 +4495,9 @@ path-key@^3.0.0, path-key@^3.1.0:
 | 
				
			||||||
  integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
 | 
					  integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
path-parse@^1.0.6:
 | 
					path-parse@^1.0.6:
 | 
				
			||||||
  version "1.0.6"
 | 
					  version "1.0.7"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
 | 
					  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
 | 
				
			||||||
  integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
 | 
					  integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
path-type@^4.0.0:
 | 
					path-type@^4.0.0:
 | 
				
			||||||
  version "4.0.0"
 | 
					  version "4.0.0"
 | 
				
			||||||
| 
						 | 
					@ -5835,6 +5798,11 @@ tough-cookie@~2.5.0:
 | 
				
			||||||
    psl "^1.1.28"
 | 
					    psl "^1.1.28"
 | 
				
			||||||
    punycode "^2.1.1"
 | 
					    punycode "^2.1.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tr46@~0.0.3:
 | 
				
			||||||
 | 
					  version "0.0.3"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
 | 
				
			||||||
 | 
					  integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ts-loader@9.2.6:
 | 
					ts-loader@9.2.6:
 | 
				
			||||||
  version "9.2.6"
 | 
					  version "9.2.6"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.6.tgz#9937c4dd0a1e3dbbb5e433f8102a6601c6615d74"
 | 
					  resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.6.tgz#9937c4dd0a1e3dbbb5e433f8102a6601c6615d74"
 | 
				
			||||||
| 
						 | 
					@ -6188,6 +6156,11 @@ web-push@3.4.5:
 | 
				
			||||||
    minimist "^1.2.5"
 | 
					    minimist "^1.2.5"
 | 
				
			||||||
    urlsafe-base64 "^1.0.0"
 | 
					    urlsafe-base64 "^1.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					webidl-conversions@^3.0.0:
 | 
				
			||||||
 | 
					  version "3.0.1"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
 | 
				
			||||||
 | 
					  integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
webpack-cli@4.9.1:
 | 
					webpack-cli@4.9.1:
 | 
				
			||||||
  version "4.9.1"
 | 
					  version "4.9.1"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.9.1.tgz#b64be825e2d1b130f285c314caa3b1ba9a4632b3"
 | 
					  resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.9.1.tgz#b64be825e2d1b130f285c314caa3b1ba9a4632b3"
 | 
				
			||||||
| 
						 | 
					@ -6298,6 +6271,14 @@ websocket@1.0.34:
 | 
				
			||||||
    utf-8-validate "^5.0.2"
 | 
					    utf-8-validate "^5.0.2"
 | 
				
			||||||
    yaeti "^0.0.6"
 | 
					    yaeti "^0.0.6"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					whatwg-url@^5.0.0:
 | 
				
			||||||
 | 
					  version "5.0.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
 | 
				
			||||||
 | 
					  integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0=
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    tr46 "~0.0.3"
 | 
				
			||||||
 | 
					    webidl-conversions "^3.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
which-boxed-primitive@^1.0.2:
 | 
					which-boxed-primitive@^1.0.2:
 | 
				
			||||||
  version "1.0.2"
 | 
					  version "1.0.2"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
 | 
					  resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue