Compare commits
	
		
			108 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					11f25ea2e7 | ||
| 
						 | 
					ef62497777 | ||
| 
						 | 
					2824d8a5b6 | ||
| 
						 | 
					1c84c0828e | ||
| 
						 | 
					39e4494836 | ||
| 
						 | 
					e7180d529a | ||
| 
						 | 
					b8cd872738 | ||
| 
						 | 
					efaaa76185 | ||
| 
						 | 
					19e1f996a6 | ||
| 
						 | 
					40a69bf200 | ||
| 
						 | 
					9e3abb9989 | ||
| 
						 | 
					5ba48e06f7 | ||
| 
						 | 
					8b3a0a524b | ||
| 
						 | 
					d9fe9cc5df | ||
| 
						 | 
					b202c7906a | ||
| 
						 | 
					b9c868cac6 | ||
| 
						 | 
					33adf3c88d | ||
| 
						 | 
					8b84f40975 | ||
| 
						 | 
					fa131d2023 | ||
| 
						 | 
					a83b38b50a | ||
| 
						 | 
					dcd7b286ef | ||
| 
						 | 
					b85bf769cd | ||
| 
						 | 
					630a20d61e | ||
| 
						 | 
					88c3794cf1 | ||
| 
						 | 
					42eb457ad0 | ||
| 
						 | 
					d153a8de20 | ||
| 
						 | 
					7f7551f44c | ||
| 
						 | 
					23082b55a4 | ||
| 
						 | 
					dac7387a7f | ||
| 
						 | 
					8c6856d894 | ||
| 
						 | 
					2c0e514fb2 | ||
| 
						 | 
					1917b0339e | ||
| 
						 | 
					c05419f223 | ||
| 
						 | 
					e0deaec695 | ||
| 
						 | 
					d70e2a788e | ||
| 
						 | 
					7343e6e2e8 | ||
| 
						 | 
					106d4cc0d6 | ||
| 
						 | 
					c98879cb7a | ||
| 
						 | 
					9ca755c313 | ||
| 
						 | 
					25e0b98840 | ||
| 
						 | 
					5599c43c71 | ||
| 
						 | 
					eb7597d7e4 | ||
| 
						 | 
					d5767e92c4 | ||
| 
						 | 
					ba3749d373 | ||
| 
						 | 
					d8088acdf2 | ||
| 
						 | 
					ad93e0aa3d | ||
| 
						 | 
					691e58f03d | ||
| 
						 | 
					95d5bccfca | ||
| 
						 | 
					2aa8e0a4bf | ||
| 
						 | 
					6e96d6677d | ||
| 
						 | 
					8816c20f51 | ||
| 
						 | 
					e955fe1ffd | ||
| 
						 | 
					5cbcac713a | ||
| 
						 | 
					2b50364ab4 | ||
| 
						 | 
					fa04ac789e | ||
| 
						 | 
					95ce8dce3d | ||
| 
						 | 
					0b5eec4ca8 | ||
| 
						 | 
					6d9716f90e | ||
| 
						 | 
					aa31061d90 | ||
| 
						 | 
					acc7797dff | ||
| 
						 | 
					7959196dc7 | ||
| 
						 | 
					c6ff6939a5 | ||
| 
						 | 
					769960f29e | ||
| 
						 | 
					d92e9759f3 | ||
| 
						 | 
					bf7e19b288 | ||
| 
						 | 
					98954cd6d4 | ||
| 
						 | 
					538bb978ed | ||
| 
						 | 
					10232c5866 | ||
| 
						 | 
					5cd6a0db16 | ||
| 
						 | 
					ff0a05a2d6 | ||
| 
						 | 
					e34b264af2 | ||
| 
						 | 
					00d79487cd | ||
| 
						 | 
					3cace734c7 | ||
| 
						 | 
					f428372869 | ||
| 
						 | 
					5dd2feba9b | ||
| 
						 | 
					a1b026239e | ||
| 
						 | 
					40735ce76b | ||
| 
						 | 
					4a00c13b33 | ||
| 
						 | 
					8e359d54bd | ||
| 
						 | 
					fb76dff836 | ||
| 
						 | 
					7167c8c593 | ||
| 
						 | 
					51b79d4250 | ||
| 
						 | 
					925fcc1c64 | ||
| 
						 | 
					fcdc14862c | ||
| 
						 | 
					dac2844cae | ||
| 
						 | 
					706b0cea16 | ||
| 
						 | 
					842e7844c7 | ||
| 
						 | 
					1dceda50d8 | ||
| 
						 | 
					af8b9abba4 | ||
| 
						 | 
					07b07685fa | ||
| 
						 | 
					cde43fe3c8 | ||
| 
						 | 
					2fe84aa75b | ||
| 
						 | 
					7d79a4840d | ||
| 
						 | 
					3ee9479572 | ||
| 
						 | 
					16da91d8d1 | ||
| 
						 | 
					8ffd62b462 | ||
| 
						 | 
					935367e167 | ||
| 
						 | 
					00618260f2 | ||
| 
						 | 
					77d66fac7b | ||
| 
						 | 
					17d7f59b06 | ||
| 
						 | 
					2561547db1 | ||
| 
						 | 
					7738438616 | ||
| 
						 | 
					3d8fc4a794 | ||
| 
						 | 
					87b4e7905e | ||
| 
						 | 
					13c5d4985a | ||
| 
						 | 
					f0df4096fd | ||
| 
						 | 
					5044424549 | ||
| 
						 | 
					8ce67cdcd6 | 
@@ -651,6 +651,7 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  delete-wallpaper: "壁紙を削除"
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
 | 
			
		||||
  post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
 | 
			
		||||
  suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
 | 
			
		||||
@@ -865,7 +866,10 @@ desktop/views/pages/welcome.vue:
 | 
			
		||||
  signin-button: "やってる"
 | 
			
		||||
  signup-button: "やる"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  photos: "最近の画像"
 | 
			
		||||
  powered-by-misskey: "Powered by <b>Misskey</b>."
 | 
			
		||||
  info: "情報"
 | 
			
		||||
desktop/views/pages/drive.vue:
 | 
			
		||||
  title: "Misskey Drive"
 | 
			
		||||
desktop/views/pages/favorites.vue:
 | 
			
		||||
@@ -1153,6 +1157,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  i-am-under-limited-internet: "私は通信を制限されている"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  show-reply-target: "リプライ先を表示する"
 | 
			
		||||
  show-my-renotes: "自分の行ったRenoteを表示する"
 | 
			
		||||
@@ -1161,6 +1166,9 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  post-style: "投稿の表示スタイル"
 | 
			
		||||
  post-style-standard: "標準"
 | 
			
		||||
  post-style-smart: "スマート"
 | 
			
		||||
  notification-position: "通知の表示"
 | 
			
		||||
  notification-position-bottom: "下"
 | 
			
		||||
  notification-position-top: "上"
 | 
			
		||||
  behavior: "動作"
 | 
			
		||||
  fetch-on-scroll: "スクロールで自動読み込み"
 | 
			
		||||
  disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
 | 
			
		||||
@@ -1182,7 +1190,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  settings: "設定"
 | 
			
		||||
  signout: "サインアウト"
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enableSounds: "サウンドを有効にする"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
 
 | 
			
		||||
@@ -651,6 +651,7 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  delete-wallpaper: "壁紙を削除"
 | 
			
		||||
  dark-mode: "Nacht Modus"
 | 
			
		||||
  circle-icons: "Kreisförmige Icons"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  gradient-window-header: "Übergang in Fensterköpfen"
 | 
			
		||||
  post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
 | 
			
		||||
  suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
 | 
			
		||||
@@ -865,7 +866,10 @@ desktop/views/pages/welcome.vue:
 | 
			
		||||
  signin-button: "やってる"
 | 
			
		||||
  signup-button: "やる"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  photos: "最近の画像"
 | 
			
		||||
  powered-by-misskey: "Powered by <b>Misskey</b>."
 | 
			
		||||
  info: "情報"
 | 
			
		||||
desktop/views/pages/drive.vue:
 | 
			
		||||
  title: "Misskey Drive"
 | 
			
		||||
desktop/views/pages/favorites.vue:
 | 
			
		||||
@@ -1153,6 +1157,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  i-am-under-limited-internet: "私は通信を制限されている"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  show-reply-target: "リプライ先を表示する"
 | 
			
		||||
  show-my-renotes: "自分の行ったRenoteを表示する"
 | 
			
		||||
@@ -1161,6 +1166,9 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  post-style: "投稿の表示スタイル"
 | 
			
		||||
  post-style-standard: "標準"
 | 
			
		||||
  post-style-smart: "スマート"
 | 
			
		||||
  notification-position: "通知の表示"
 | 
			
		||||
  notification-position-bottom: "下"
 | 
			
		||||
  notification-position-top: "上"
 | 
			
		||||
  behavior: "動作"
 | 
			
		||||
  fetch-on-scroll: "スクロールで自動読み込み"
 | 
			
		||||
  disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
 | 
			
		||||
@@ -1182,7 +1190,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  settings: "設定"
 | 
			
		||||
  signout: "サインアウト"
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enableSounds: "サウンドを有効にする"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
 
 | 
			
		||||
@@ -651,6 +651,7 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  delete-wallpaper: "Remove background"
 | 
			
		||||
  dark-mode: "Dark Mode"
 | 
			
		||||
  circle-icons: "Use circle icons"
 | 
			
		||||
  contrasted-acct: "Add contrast to username"
 | 
			
		||||
  gradient-window-header: "Use gradients on window headers"
 | 
			
		||||
  post-form-on-timeline: "Display post form at the top of the timeline"
 | 
			
		||||
  suggest-recent-hashtags: "Show recent popular hashtags on the post form"
 | 
			
		||||
@@ -865,7 +866,10 @@ desktop/views/pages/welcome.vue:
 | 
			
		||||
  signin-button: "Logging in..."
 | 
			
		||||
  signup-button: "Sign up"
 | 
			
		||||
  timeline: "Timeline"
 | 
			
		||||
  announcements: "Announcements"
 | 
			
		||||
  photos: "Recent uploaded"
 | 
			
		||||
  powered-by-misskey: "Powered by <b>Misskey</b>."
 | 
			
		||||
  info: "Information"
 | 
			
		||||
desktop/views/pages/drive.vue:
 | 
			
		||||
  title: "Misskey storage"
 | 
			
		||||
desktop/views/pages/favorites.vue:
 | 
			
		||||
@@ -1153,6 +1157,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  dark-mode: "Dark Mode"
 | 
			
		||||
  i-am-under-limited-internet: "I'm in limited bandwidth"
 | 
			
		||||
  circle-icons: "Use circle icons"
 | 
			
		||||
  contrasted-acct: "Add contrast to username"
 | 
			
		||||
  timeline: "Timeline"
 | 
			
		||||
  show-reply-target: "Show reply target"
 | 
			
		||||
  show-my-renotes: "Show my reposts"
 | 
			
		||||
@@ -1161,6 +1166,9 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  post-style: "Post design"
 | 
			
		||||
  post-style-standard: "Standard"
 | 
			
		||||
  post-style-smart: "Smart"
 | 
			
		||||
  notification-position: "Notification style"
 | 
			
		||||
  notification-position-bottom: "Bottom"
 | 
			
		||||
  notification-position-top: "Top"
 | 
			
		||||
  behavior: "Behavior"
 | 
			
		||||
  fetch-on-scroll: "Endless loading on scroll"
 | 
			
		||||
  disable-via-mobile: "Don't mark the post as 'from mobile'"
 | 
			
		||||
@@ -1182,7 +1190,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  settings: "Settings"
 | 
			
		||||
  signout: "Sign out"
 | 
			
		||||
  sound: "Sounds"
 | 
			
		||||
  enableSounds: "Enable sounds"
 | 
			
		||||
  enable-sounds: "Enable sounds"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "Follows you"
 | 
			
		||||
  following: "Following"
 | 
			
		||||
 
 | 
			
		||||
@@ -651,6 +651,7 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  delete-wallpaper: "Suprimir fondo"
 | 
			
		||||
  dark-mode: "Modo Nocturno"
 | 
			
		||||
  circle-icons: "Usar iconos circulares"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  gradient-window-header: "Usar degradados en las cabeceras de las páginas"
 | 
			
		||||
  post-form-on-timeline: "Mostrar el formulario de las entradas encima de la línea de tiempo"
 | 
			
		||||
  suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
 | 
			
		||||
@@ -865,7 +866,10 @@ desktop/views/pages/welcome.vue:
 | 
			
		||||
  signin-button: "やってる"
 | 
			
		||||
  signup-button: "やる"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  photos: "最近の画像"
 | 
			
		||||
  powered-by-misskey: "Powered by <b>Misskey</b>."
 | 
			
		||||
  info: "情報"
 | 
			
		||||
desktop/views/pages/drive.vue:
 | 
			
		||||
  title: "Misskey Drive"
 | 
			
		||||
desktop/views/pages/favorites.vue:
 | 
			
		||||
@@ -1153,6 +1157,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  i-am-under-limited-internet: "私は通信を制限されている"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  show-reply-target: "リプライ先を表示する"
 | 
			
		||||
  show-my-renotes: "自分の行ったRenoteを表示する"
 | 
			
		||||
@@ -1161,6 +1166,9 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  post-style: "投稿の表示スタイル"
 | 
			
		||||
  post-style-standard: "標準"
 | 
			
		||||
  post-style-smart: "スマート"
 | 
			
		||||
  notification-position: "通知の表示"
 | 
			
		||||
  notification-position-bottom: "下"
 | 
			
		||||
  notification-position-top: "上"
 | 
			
		||||
  behavior: "動作"
 | 
			
		||||
  fetch-on-scroll: "スクロールで自動読み込み"
 | 
			
		||||
  disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
 | 
			
		||||
@@ -1182,7 +1190,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  settings: "設定"
 | 
			
		||||
  signout: "サインアウト"
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enableSounds: "サウンドを有効にする"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
 
 | 
			
		||||
@@ -651,6 +651,7 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  delete-wallpaper: "Supprimer le fond d'écran"
 | 
			
		||||
  dark-mode: "Mode nuit"
 | 
			
		||||
  circle-icons: "Utiliser des icônes circulaires"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  gradient-window-header: "Utiliser les dégradés sur la barre de titre de la fenêtre"
 | 
			
		||||
  post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
 | 
			
		||||
  suggest-recent-hashtags: "Afficher les hashtags populaires dans le champs de saisie"
 | 
			
		||||
@@ -865,7 +866,10 @@ desktop/views/pages/welcome.vue:
 | 
			
		||||
  signin-button: "Se connecter"
 | 
			
		||||
  signup-button: "S'inscrire"
 | 
			
		||||
  timeline: "Fil d'actualité"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  photos: "最近の画像"
 | 
			
		||||
  powered-by-misskey: "Propulsé par <b>Misskey</b>."
 | 
			
		||||
  info: "情報"
 | 
			
		||||
desktop/views/pages/drive.vue:
 | 
			
		||||
  title: "Lecteur de Misskey"
 | 
			
		||||
desktop/views/pages/favorites.vue:
 | 
			
		||||
@@ -1153,6 +1157,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  dark-mode: "Mode nuit"
 | 
			
		||||
  i-am-under-limited-internet: "J'ai un accès Internet limité"
 | 
			
		||||
  circle-icons: "Utiliser des icônes circulaires"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  timeline: "Fil d'actualité"
 | 
			
		||||
  show-reply-target: "Afficher les réponses"
 | 
			
		||||
  show-my-renotes: "Afficher mes republications"
 | 
			
		||||
@@ -1161,6 +1166,9 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  post-style: "Style de la publication"
 | 
			
		||||
  post-style-standard: "Standard"
 | 
			
		||||
  post-style-smart: "Intelligent"
 | 
			
		||||
  notification-position: "通知の表示"
 | 
			
		||||
  notification-position-bottom: "下"
 | 
			
		||||
  notification-position-top: "上"
 | 
			
		||||
  behavior: "Comportement"
 | 
			
		||||
  fetch-on-scroll: "Chargement lors du défilement"
 | 
			
		||||
  disable-via-mobile: "Ne pas mentionner que ma publication provient d'un 'périphérique mobile'"
 | 
			
		||||
@@ -1182,7 +1190,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  settings: "Réglages"
 | 
			
		||||
  signout: "Déconnexion"
 | 
			
		||||
  sound: "Sons"
 | 
			
		||||
  enableSounds: "Activer le son"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "vous suit"
 | 
			
		||||
  following: "Abonnements"
 | 
			
		||||
 
 | 
			
		||||
@@ -651,6 +651,7 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  delete-wallpaper: "壁紙を削除"
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
 | 
			
		||||
  post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
 | 
			
		||||
  suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
 | 
			
		||||
@@ -865,7 +866,10 @@ desktop/views/pages/welcome.vue:
 | 
			
		||||
  signin-button: "やってる"
 | 
			
		||||
  signup-button: "やる"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  photos: "最近の画像"
 | 
			
		||||
  powered-by-misskey: "Powered by <b>Misskey</b>."
 | 
			
		||||
  info: "情報"
 | 
			
		||||
desktop/views/pages/drive.vue:
 | 
			
		||||
  title: "Misskey Drive"
 | 
			
		||||
desktop/views/pages/favorites.vue:
 | 
			
		||||
@@ -1153,6 +1157,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  i-am-under-limited-internet: "私は通信を制限されている"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  show-reply-target: "リプライ先を表示する"
 | 
			
		||||
  show-my-renotes: "自分の行ったRenoteを表示する"
 | 
			
		||||
@@ -1161,6 +1166,9 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  post-style: "投稿の表示スタイル"
 | 
			
		||||
  post-style-standard: "標準"
 | 
			
		||||
  post-style-smart: "スマート"
 | 
			
		||||
  notification-position: "通知の表示"
 | 
			
		||||
  notification-position-bottom: "下"
 | 
			
		||||
  notification-position-top: "上"
 | 
			
		||||
  behavior: "動作"
 | 
			
		||||
  fetch-on-scroll: "スクロールで自動読み込み"
 | 
			
		||||
  disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
 | 
			
		||||
@@ -1182,7 +1190,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  settings: "設定"
 | 
			
		||||
  signout: "サインアウト"
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enableSounds: "サウンドを有効にする"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
 
 | 
			
		||||
@@ -73,6 +73,16 @@ common:
 | 
			
		||||
    rip: "RIP"
 | 
			
		||||
    pudding: "Pudding"
 | 
			
		||||
 | 
			
		||||
  note-visibility:
 | 
			
		||||
    public: "公開"
 | 
			
		||||
    home: "ホーム"
 | 
			
		||||
    home-desc: "ホームタイムラインにのみ公開"
 | 
			
		||||
    followers: "フォロワー"
 | 
			
		||||
    followers-desc: "自分のフォロワーにのみ公開"
 | 
			
		||||
    specified: "ダイレクト"
 | 
			
		||||
    specified-desc: "指定したユーザーにのみ公開"
 | 
			
		||||
    private: "非公開"
 | 
			
		||||
 | 
			
		||||
  note-placeholders:
 | 
			
		||||
    a: "今どうしてる?"
 | 
			
		||||
    b: "何かありましたか?"
 | 
			
		||||
@@ -724,6 +734,9 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  behaviour: "動作"
 | 
			
		||||
  fetch-on-scroll: "スクロールで自動読み込み"
 | 
			
		||||
  fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
 | 
			
		||||
  note-visibility: "投稿の公開範囲"
 | 
			
		||||
  default-note-visibility: "デフォルトの公開範囲"
 | 
			
		||||
  remember-note-visibility: "投稿の公開範囲を記憶する"
 | 
			
		||||
  auto-popout: "ウィンドウの自動ポップアウト"
 | 
			
		||||
  auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
 | 
			
		||||
  advanced: "詳細設定"
 | 
			
		||||
@@ -736,6 +749,7 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  delete-wallpaper: "壁紙を削除"
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
 | 
			
		||||
  post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
 | 
			
		||||
  suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
 | 
			
		||||
@@ -993,6 +1007,7 @@ desktop/views/pages/welcome.vue:
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  photos: "最近の画像"
 | 
			
		||||
  powered-by-misskey: "Powered by <b>Misskey</b>."
 | 
			
		||||
  info: "情報"
 | 
			
		||||
 | 
			
		||||
desktop/views/pages/drive.vue:
 | 
			
		||||
  title: "Misskey Drive"
 | 
			
		||||
@@ -1349,6 +1364,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  i-am-under-limited-internet: "私は通信を制限されている"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  show-reply-target: "リプライ先を表示する"
 | 
			
		||||
  show-my-renotes: "自分の行ったRenoteを表示する"
 | 
			
		||||
@@ -1362,6 +1378,9 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  notification-position-top: "上"
 | 
			
		||||
  behavior: "動作"
 | 
			
		||||
  fetch-on-scroll: "スクロールで自動読み込み"
 | 
			
		||||
  note-visibility: "投稿の公開範囲"
 | 
			
		||||
  default-note-visibility: "デフォルトの公開範囲"
 | 
			
		||||
  remember-note-visibility: "投稿の公開範囲を記憶する"
 | 
			
		||||
  disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
 | 
			
		||||
  load-raw-images: "添付された画像を高画質で表示する"
 | 
			
		||||
  load-remote-media: "リモートサーバーのメディアを表示する"
 | 
			
		||||
@@ -1381,7 +1400,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  settings: "設定"
 | 
			
		||||
  signout: "サインアウト"
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enableSounds: "サウンドを有効にする"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
 
 | 
			
		||||
@@ -651,6 +651,7 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  delete-wallpaper: "壁紙を削除"
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
 | 
			
		||||
  post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
 | 
			
		||||
  suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
 | 
			
		||||
@@ -865,7 +866,10 @@ desktop/views/pages/welcome.vue:
 | 
			
		||||
  signin-button: "サインイン中…"
 | 
			
		||||
  signup-button: "サインアップ"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  photos: "最近の画像"
 | 
			
		||||
  powered-by-misskey: "Powered by <b>Misskey</b>."
 | 
			
		||||
  info: "情報"
 | 
			
		||||
desktop/views/pages/drive.vue:
 | 
			
		||||
  title: "ドライブ"
 | 
			
		||||
desktop/views/pages/favorites.vue:
 | 
			
		||||
@@ -1153,6 +1157,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  i-am-under-limited-internet: "私は通信を制限されている"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  show-reply-target: "リプライ先を表示する"
 | 
			
		||||
  show-my-renotes: "自分の行ったRenoteを表示する"
 | 
			
		||||
@@ -1161,6 +1166,9 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  post-style: "投稿の表示スタイル"
 | 
			
		||||
  post-style-standard: "標準"
 | 
			
		||||
  post-style-smart: "べっぴんさん"
 | 
			
		||||
  notification-position: "通知の表示"
 | 
			
		||||
  notification-position-bottom: "下"
 | 
			
		||||
  notification-position-top: "上"
 | 
			
		||||
  behavior: "動作"
 | 
			
		||||
  fetch-on-scroll: "スクロールで自動読み込み"
 | 
			
		||||
  disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
 | 
			
		||||
@@ -1182,7 +1190,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  settings: "設定"
 | 
			
		||||
  signout: "サインアウト"
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enableSounds: "サウンドを有効にする"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
 
 | 
			
		||||
@@ -651,6 +651,7 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  delete-wallpaper: "壁紙を削除"
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
 | 
			
		||||
  post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
 | 
			
		||||
  suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
 | 
			
		||||
@@ -865,7 +866,10 @@ desktop/views/pages/welcome.vue:
 | 
			
		||||
  signin-button: "やってる"
 | 
			
		||||
  signup-button: "やる"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  photos: "最近の画像"
 | 
			
		||||
  powered-by-misskey: "Powered by <b>Misskey</b>."
 | 
			
		||||
  info: "情報"
 | 
			
		||||
desktop/views/pages/drive.vue:
 | 
			
		||||
  title: "Misskey Drive"
 | 
			
		||||
desktop/views/pages/favorites.vue:
 | 
			
		||||
@@ -1153,6 +1157,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  i-am-under-limited-internet: "私は通信を制限されている"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  show-reply-target: "リプライ先を表示する"
 | 
			
		||||
  show-my-renotes: "自分の行ったRenoteを表示する"
 | 
			
		||||
@@ -1161,6 +1166,9 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  post-style: "投稿の表示スタイル"
 | 
			
		||||
  post-style-standard: "標準"
 | 
			
		||||
  post-style-smart: "スマート"
 | 
			
		||||
  notification-position: "通知の表示"
 | 
			
		||||
  notification-position-bottom: "下"
 | 
			
		||||
  notification-position-top: "上"
 | 
			
		||||
  behavior: "動作"
 | 
			
		||||
  fetch-on-scroll: "スクロールで自動読み込み"
 | 
			
		||||
  disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
 | 
			
		||||
@@ -1182,7 +1190,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  settings: "設定"
 | 
			
		||||
  signout: "サインアウト"
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enableSounds: "サウンドを有効にする"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
 
 | 
			
		||||
@@ -651,6 +651,7 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  delete-wallpaper: "壁紙を削除"
 | 
			
		||||
  dark-mode: "Donkere modus"
 | 
			
		||||
  circle-icons: "Ronde pictogrammen gebruiken"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  gradient-window-header: "Kleurverloop gebruiken op vensterkoppen"
 | 
			
		||||
  post-form-on-timeline: "Berichtformulier boven de tijdlijn tonen"
 | 
			
		||||
  suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
 | 
			
		||||
@@ -865,7 +866,10 @@ desktop/views/pages/welcome.vue:
 | 
			
		||||
  signin-button: "Inloggen"
 | 
			
		||||
  signup-button: "Registreren"
 | 
			
		||||
  timeline: "Tijdlijn"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  photos: "最近の画像"
 | 
			
		||||
  powered-by-misskey: "Powered by <b>Misskey</b>."
 | 
			
		||||
  info: "情報"
 | 
			
		||||
desktop/views/pages/drive.vue:
 | 
			
		||||
  title: "Misskey Drive"
 | 
			
		||||
desktop/views/pages/favorites.vue:
 | 
			
		||||
@@ -1153,6 +1157,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  dark-mode: "Donkere modus"
 | 
			
		||||
  i-am-under-limited-internet: "Ik heb beperkt internet"
 | 
			
		||||
  circle-icons: "Ronde pictogrammen gebruiken"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  timeline: "Tijdlijn"
 | 
			
		||||
  show-reply-target: "Antwoordknop tonen"
 | 
			
		||||
  show-my-renotes: "Mijn renotes tonen"
 | 
			
		||||
@@ -1161,6 +1166,9 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  post-style: "Berichtontwerp"
 | 
			
		||||
  post-style-standard: "Standaard"
 | 
			
		||||
  post-style-smart: "Slim"
 | 
			
		||||
  notification-position: "通知の表示"
 | 
			
		||||
  notification-position-bottom: "下"
 | 
			
		||||
  notification-position-top: "上"
 | 
			
		||||
  behavior: "Gedrag"
 | 
			
		||||
  fetch-on-scroll: "Ophalen bij scrollen"
 | 
			
		||||
  disable-via-mobile: "Zonder 'mobiele berichten'"
 | 
			
		||||
@@ -1182,7 +1190,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  settings: "Instellingen"
 | 
			
		||||
  signout: "Uitloggen"
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enableSounds: "サウンドを有効にする"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "Volgt jou"
 | 
			
		||||
  following: "Volgend"
 | 
			
		||||
 
 | 
			
		||||
@@ -651,6 +651,7 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  delete-wallpaper: "壁紙を削除"
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
 | 
			
		||||
  post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
 | 
			
		||||
  suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
 | 
			
		||||
@@ -865,7 +866,10 @@ desktop/views/pages/welcome.vue:
 | 
			
		||||
  signin-button: "やってる"
 | 
			
		||||
  signup-button: "やる"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  photos: "最近の画像"
 | 
			
		||||
  powered-by-misskey: "Powered by <b>Misskey</b>."
 | 
			
		||||
  info: "情報"
 | 
			
		||||
desktop/views/pages/drive.vue:
 | 
			
		||||
  title: "Misskey Drive"
 | 
			
		||||
desktop/views/pages/favorites.vue:
 | 
			
		||||
@@ -1153,6 +1157,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  i-am-under-limited-internet: "私は通信を制限されている"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  show-reply-target: "リプライ先を表示する"
 | 
			
		||||
  show-my-renotes: "自分の行ったRenoteを表示する"
 | 
			
		||||
@@ -1161,6 +1166,9 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  post-style: "投稿の表示スタイル"
 | 
			
		||||
  post-style-standard: "標準"
 | 
			
		||||
  post-style-smart: "スマート"
 | 
			
		||||
  notification-position: "通知の表示"
 | 
			
		||||
  notification-position-bottom: "下"
 | 
			
		||||
  notification-position-top: "上"
 | 
			
		||||
  behavior: "動作"
 | 
			
		||||
  fetch-on-scroll: "スクロールで自動読み込み"
 | 
			
		||||
  disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
 | 
			
		||||
@@ -1182,7 +1190,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  settings: "設定"
 | 
			
		||||
  signout: "サインアウト"
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enableSounds: "サウンドを有効にする"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
 
 | 
			
		||||
@@ -651,6 +651,7 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  delete-wallpaper: "Usuń tło"
 | 
			
		||||
  dark-mode: "Tryb ciemny"
 | 
			
		||||
  circle-icons: "Używaj okrągłych ikon"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  gradient-window-header: "Używaj gradientów na pasku tytułu okna"
 | 
			
		||||
  post-form-on-timeline: "Wyświetlaj formularz tworzenia wpisu w górnej części osi czasu"
 | 
			
		||||
  suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
 | 
			
		||||
@@ -865,7 +866,10 @@ desktop/views/pages/welcome.vue:
 | 
			
		||||
  signin-button: "Zaloguj się"
 | 
			
		||||
  signup-button: "Zarejestruj się"
 | 
			
		||||
  timeline: "Oś czasu"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  photos: "最近の画像"
 | 
			
		||||
  powered-by-misskey: "Oparto o <b>Misskey</b>."
 | 
			
		||||
  info: "情報"
 | 
			
		||||
desktop/views/pages/drive.vue:
 | 
			
		||||
  title: "Dysk Misskey"
 | 
			
		||||
desktop/views/pages/favorites.vue:
 | 
			
		||||
@@ -1153,6 +1157,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  dark-mode: "Tryb ciemny"
 | 
			
		||||
  i-am-under-limited-internet: "Ograniczaj zużycie transferu"
 | 
			
		||||
  circle-icons: "Używaj okrągłych ikon"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  timeline: "Oś czasu"
 | 
			
		||||
  show-reply-target: "Pokazuj cel odpowiedzi"
 | 
			
		||||
  show-my-renotes: "Pokazuj moje udostępnienia"
 | 
			
		||||
@@ -1161,6 +1166,9 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  post-style: "Styl wpisów"
 | 
			
		||||
  post-style-standard: "Standardowy"
 | 
			
		||||
  post-style-smart: "Inteligentny"
 | 
			
		||||
  notification-position: "通知の表示"
 | 
			
		||||
  notification-position-bottom: "下"
 | 
			
		||||
  notification-position-top: "上"
 | 
			
		||||
  behavior: "Zachowanie"
 | 
			
		||||
  fetch-on-scroll: "Automatycznie ładuj po przeciągnięciu w dół"
 | 
			
		||||
  disable-via-mobile: "Nie oznaczaj wpisów jako „wysłane z telefonu”"
 | 
			
		||||
@@ -1182,7 +1190,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  settings: "Ustawienia"
 | 
			
		||||
  signout: "Wyloguj"
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enableSounds: "サウンドを有効にする"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "Śledzi Cię"
 | 
			
		||||
  following: "Śledzeni"
 | 
			
		||||
 
 | 
			
		||||
@@ -651,6 +651,7 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  delete-wallpaper: "壁紙を削除"
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
 | 
			
		||||
  post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
 | 
			
		||||
  suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
 | 
			
		||||
@@ -865,7 +866,10 @@ desktop/views/pages/welcome.vue:
 | 
			
		||||
  signin-button: "やってる"
 | 
			
		||||
  signup-button: "やる"
 | 
			
		||||
  timeline: "Timeline"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  photos: "最近の画像"
 | 
			
		||||
  powered-by-misskey: "Desenvolvido por <b>Misskey</b>."
 | 
			
		||||
  info: "情報"
 | 
			
		||||
desktop/views/pages/drive.vue:
 | 
			
		||||
  title: "Drive Misskey"
 | 
			
		||||
desktop/views/pages/favorites.vue:
 | 
			
		||||
@@ -1153,6 +1157,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  i-am-under-limited-internet: "私は通信を制限されている"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  show-reply-target: "リプライ先を表示する"
 | 
			
		||||
  show-my-renotes: "自分の行ったRenoteを表示する"
 | 
			
		||||
@@ -1161,6 +1166,9 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  post-style: "投稿の表示スタイル"
 | 
			
		||||
  post-style-standard: "標準"
 | 
			
		||||
  post-style-smart: "スマート"
 | 
			
		||||
  notification-position: "通知の表示"
 | 
			
		||||
  notification-position-bottom: "下"
 | 
			
		||||
  notification-position-top: "上"
 | 
			
		||||
  behavior: "動作"
 | 
			
		||||
  fetch-on-scroll: "スクロールで自動読み込み"
 | 
			
		||||
  disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
 | 
			
		||||
@@ -1182,7 +1190,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  settings: "設定"
 | 
			
		||||
  signout: "サインアウト"
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enableSounds: "サウンドを有効にする"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
 
 | 
			
		||||
@@ -651,6 +651,7 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  delete-wallpaper: "壁紙を削除"
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
 | 
			
		||||
  post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
 | 
			
		||||
  suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
 | 
			
		||||
@@ -865,7 +866,10 @@ desktop/views/pages/welcome.vue:
 | 
			
		||||
  signin-button: "やってる"
 | 
			
		||||
  signup-button: "やる"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  photos: "最近の画像"
 | 
			
		||||
  powered-by-misskey: "Powered by <b>Misskey</b>."
 | 
			
		||||
  info: "情報"
 | 
			
		||||
desktop/views/pages/drive.vue:
 | 
			
		||||
  title: "Misskey Drive"
 | 
			
		||||
desktop/views/pages/favorites.vue:
 | 
			
		||||
@@ -1153,6 +1157,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  i-am-under-limited-internet: "私は通信を制限されている"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  show-reply-target: "リプライ先を表示する"
 | 
			
		||||
  show-my-renotes: "自分の行ったRenoteを表示する"
 | 
			
		||||
@@ -1161,6 +1166,9 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  post-style: "投稿の表示スタイル"
 | 
			
		||||
  post-style-standard: "標準"
 | 
			
		||||
  post-style-smart: "スマート"
 | 
			
		||||
  notification-position: "通知の表示"
 | 
			
		||||
  notification-position-bottom: "下"
 | 
			
		||||
  notification-position-top: "上"
 | 
			
		||||
  behavior: "動作"
 | 
			
		||||
  fetch-on-scroll: "スクロールで自動読み込み"
 | 
			
		||||
  disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
 | 
			
		||||
@@ -1182,7 +1190,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  settings: "設定"
 | 
			
		||||
  signout: "サインアウト"
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enableSounds: "サウンドを有効にする"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
 
 | 
			
		||||
@@ -651,6 +651,7 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  delete-wallpaper: "壁紙を削除"
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
 | 
			
		||||
  post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
 | 
			
		||||
  suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
 | 
			
		||||
@@ -865,7 +866,10 @@ desktop/views/pages/welcome.vue:
 | 
			
		||||
  signin-button: "やってる"
 | 
			
		||||
  signup-button: "やる"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  announcements: "お知らせ"
 | 
			
		||||
  photos: "最近の画像"
 | 
			
		||||
  powered-by-misskey: "Powered by <b>Misskey</b>."
 | 
			
		||||
  info: "情報"
 | 
			
		||||
desktop/views/pages/drive.vue:
 | 
			
		||||
  title: "Misskey Drive"
 | 
			
		||||
desktop/views/pages/favorites.vue:
 | 
			
		||||
@@ -1153,6 +1157,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  dark-mode: "ダークモード"
 | 
			
		||||
  i-am-under-limited-internet: "私は通信を制限されている"
 | 
			
		||||
  circle-icons: "円形のアイコンを使用"
 | 
			
		||||
  contrasted-acct: "ユーザー名にコントラストを付ける"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  show-reply-target: "リプライ先を表示する"
 | 
			
		||||
  show-my-renotes: "自分の行ったRenoteを表示する"
 | 
			
		||||
@@ -1161,6 +1166,9 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  post-style: "投稿の表示スタイル"
 | 
			
		||||
  post-style-standard: "標準"
 | 
			
		||||
  post-style-smart: "スマート"
 | 
			
		||||
  notification-position: "通知の表示"
 | 
			
		||||
  notification-position-bottom: "下"
 | 
			
		||||
  notification-position-top: "上"
 | 
			
		||||
  behavior: "動作"
 | 
			
		||||
  fetch-on-scroll: "スクロールで自動読み込み"
 | 
			
		||||
  disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
 | 
			
		||||
@@ -1182,7 +1190,7 @@ mobile/views/pages/settings.vue:
 | 
			
		||||
  settings: "設定"
 | 
			
		||||
  signout: "サインアウト"
 | 
			
		||||
  sound: "サウンド"
 | 
			
		||||
  enableSounds: "サウンドを有効にする"
 | 
			
		||||
  enable-sounds: "サウンドを有効にする"
 | 
			
		||||
mobile/views/pages/user.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								package.json
									
									
									
									
									
								
							@@ -1,8 +1,8 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "misskey",
 | 
			
		||||
	"author": "syuilo <i@syuilo.com>",
 | 
			
		||||
	"version": "8.27.0",
 | 
			
		||||
	"clientVersion": "1.0.9378",
 | 
			
		||||
	"version": "8.31.0",
 | 
			
		||||
	"clientVersion": "1.0.9486",
 | 
			
		||||
	"codename": "nighthike",
 | 
			
		||||
	"main": "./built/index.js",
 | 
			
		||||
	"private": true,
 | 
			
		||||
@@ -55,7 +55,7 @@
 | 
			
		||||
		"@types/koa-send": "4.1.1",
 | 
			
		||||
		"@types/koa-views": "2.0.3",
 | 
			
		||||
		"@types/koa__cors": "2.2.3",
 | 
			
		||||
		"@types/minio": "6.0.2",
 | 
			
		||||
		"@types/minio": "7.0.0",
 | 
			
		||||
		"@types/mkdirp": "0.5.2",
 | 
			
		||||
		"@types/mocha": "5.2.3",
 | 
			
		||||
		"@types/mongodb": "3.1.4",
 | 
			
		||||
@@ -80,7 +80,7 @@
 | 
			
		||||
		"@types/webpack": "4.4.11",
 | 
			
		||||
		"@types/webpack-stream": "3.2.10",
 | 
			
		||||
		"@types/websocket": "0.0.40",
 | 
			
		||||
		"@types/ws": "6.0.0",
 | 
			
		||||
		"@types/ws": "6.0.1",
 | 
			
		||||
		"animejs": "2.2.0",
 | 
			
		||||
		"autosize": "4.0.2",
 | 
			
		||||
		"autwh": "0.1.0",
 | 
			
		||||
@@ -151,7 +151,7 @@
 | 
			
		||||
		"lodash.assign": "4.2.0",
 | 
			
		||||
		"mecab-async": "0.1.2",
 | 
			
		||||
		"merge-options": "1.0.1",
 | 
			
		||||
		"minio": "7.0.0",
 | 
			
		||||
		"minio": "7.0.1",
 | 
			
		||||
		"mkdirp": "0.5.1",
 | 
			
		||||
		"mocha": "5.2.0",
 | 
			
		||||
		"moji": "0.5.1",
 | 
			
		||||
@@ -161,7 +161,7 @@
 | 
			
		||||
		"nan": "2.11.0",
 | 
			
		||||
		"nested-property": "0.0.7",
 | 
			
		||||
		"node-sass": "4.9.3",
 | 
			
		||||
		"node-sass-json-importer": "4.0.0",
 | 
			
		||||
		"node-sass-json-importer": "4.0.1",
 | 
			
		||||
		"nprogress": "0.2.0",
 | 
			
		||||
		"object-assign-deep": "0.4.0",
 | 
			
		||||
		"on-build-webpack": "0.1.0",
 | 
			
		||||
@@ -217,6 +217,7 @@
 | 
			
		||||
		"vue-style-loader": "4.1.2",
 | 
			
		||||
		"vue-template-compiler": "2.5.17",
 | 
			
		||||
		"vuedraggable": "2.16.0",
 | 
			
		||||
		"vuewordcloud": "18.7.11",
 | 
			
		||||
		"vuex": "3.0.1",
 | 
			
		||||
		"vuex-persistedstate": "2.5.4",
 | 
			
		||||
		"web-push": "3.3.2",
 | 
			
		||||
 
 | 
			
		||||
@@ -140,7 +140,7 @@
 | 
			
		||||
		// Random
 | 
			
		||||
		localStorage.setItem('salt', Math.random().toString());
 | 
			
		||||
 | 
			
		||||
		// Clear cache (serive worker)
 | 
			
		||||
		// Clear cache (service worker)
 | 
			
		||||
		try {
 | 
			
		||||
			navigator.serviceWorker.controller.postMessage('clear');
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ export default async function(mios: MiOS, force = false, silent = false) {
 | 
			
		||||
		localStorage.setItem('should-refresh', 'true');
 | 
			
		||||
		localStorage.setItem('v', newer);
 | 
			
		||||
 | 
			
		||||
		// Clear cache (serive worker)
 | 
			
		||||
		// Clear cache (service worker)
 | 
			
		||||
		try {
 | 
			
		||||
			if (navigator.serviceWorker.controller) {
 | 
			
		||||
				navigator.serviceWorker.controller.postMessage('clear');
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import { EventEmitter } from 'eventemitter3';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import Connection from './stream';
 | 
			
		||||
import { erase } from '../../../../../prelude/array';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ストリーム接続を管理するクラス
 | 
			
		||||
@@ -89,7 +90,7 @@ export default abstract class StreamManager<T extends Connection> extends EventE
 | 
			
		||||
	 * @param userId use で発行したユーザーID
 | 
			
		||||
	 */
 | 
			
		||||
	public dispose(userId) {
 | 
			
		||||
		this.users = this.users.filter(id => id != userId);
 | 
			
		||||
		this.users = erase(userId, this.users);
 | 
			
		||||
 | 
			
		||||
		this._connection.user = `Managed (${ this.users.length })`;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
<span class="mk-acct">
 | 
			
		||||
	<span class="name">@{{ user.username }}</span>
 | 
			
		||||
	<span class="host" v-if="user.host || detail">@{{ user.host || host }}</span>
 | 
			
		||||
	<span class="host" :class="{ fade: $store.state.settings.contrastedAcct }" v-if="user.host || detail">@{{ user.host || host }}</span>
 | 
			
		||||
</span>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -20,6 +20,6 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.mk-acct
 | 
			
		||||
	> .host
 | 
			
		||||
	> .host.fade
 | 
			
		||||
		opacity 0.5
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
import tagCloud from './tag-cloud.vue';
 | 
			
		||||
import trends from './trends.vue';
 | 
			
		||||
import analogClock from './analog-clock.vue';
 | 
			
		||||
import menu from './menu.vue';
 | 
			
		||||
@@ -41,6 +42,7 @@ import uiSelect from './ui/select.vue';
 | 
			
		||||
import formButton from './ui/form/button.vue';
 | 
			
		||||
import formRadio from './ui/form/radio.vue';
 | 
			
		||||
 | 
			
		||||
Vue.component('mk-tag-cloud', tagCloud);
 | 
			
		||||
Vue.component('mk-trends', trends);
 | 
			
		||||
Vue.component('mk-analog-clock', analogClock);
 | 
			
		||||
Vue.component('mk-menu', menu);
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import { erase } from '../../../../../prelude/array';
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
@@ -53,7 +54,7 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
		get() {
 | 
			
		||||
			return {
 | 
			
		||||
				choices: this.choices.filter(choice => choice != '')
 | 
			
		||||
				choices: erase('', this.choices)
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import { sum } from '../../../../../prelude/array';
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: ['note'],
 | 
			
		||||
	data() {
 | 
			
		||||
@@ -33,7 +34,7 @@ export default Vue.extend({
 | 
			
		||||
			return this.note.poll;
 | 
			
		||||
		},
 | 
			
		||||
		total(): number {
 | 
			
		||||
			return this.poll.choices.reduce((a, b) => a + b.votes, 0);
 | 
			
		||||
			return sum(this.poll.choices.map(x => x.votes));
 | 
			
		||||
		},
 | 
			
		||||
		isVoted(): boolean {
 | 
			
		||||
			return this.poll.choices.some(c => c.isVoted);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										90
									
								
								src/client/app/common/views/components/tag-cloud.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/client/app/common/views/components/tag-cloud.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="jtivnzhfwquxpsfidertopbmwmchmnmo">
 | 
			
		||||
	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
 | 
			
		||||
	<p class="empty" v-else-if="tags.length == 0">%fa:exclamation-circle%%i18n:@empty%</p>
 | 
			
		||||
	<div v-else>
 | 
			
		||||
		<vue-word-cloud
 | 
			
		||||
				:words="tags.slice(0, 20).map(x => [x.name, x.count])"
 | 
			
		||||
				:color="color"
 | 
			
		||||
				:spacing="1">
 | 
			
		||||
			<template slot-scope="{word, text, weight}">
 | 
			
		||||
				<div style="cursor: pointer;" :title="weight">
 | 
			
		||||
					{{ text }}
 | 
			
		||||
				</div>
 | 
			
		||||
			</template>
 | 
			
		||||
		</vue-word-cloud>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import * as VueWordCloud from 'vuewordcloud';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		[VueWordCloud.name]: VueWordCloud
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			tags: [],
 | 
			
		||||
			fetching: true,
 | 
			
		||||
			clock: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.fetch();
 | 
			
		||||
		this.clock = setInterval(this.fetch, 1000 * 60);
 | 
			
		||||
	},
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		clearInterval(this.clock);
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		fetch() {
 | 
			
		||||
			(this as any).api('aggregation/hashtags').then(tags => {
 | 
			
		||||
				this.tags = tags;
 | 
			
		||||
				this.fetching = false;
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		color([, weight]) {
 | 
			
		||||
			const peak = Math.max.apply(null, this.tags.map(x => x.count));
 | 
			
		||||
			const w = weight / peak;
 | 
			
		||||
 | 
			
		||||
			if (w > 0.9) {
 | 
			
		||||
				return this.$store.state.device.darkmode ? '#ff4e69' : '#ff4e69';
 | 
			
		||||
			} else if (w > 0.5) {
 | 
			
		||||
				return this.$store.state.device.darkmode ? '#3bc4c7' : '#3bc4c7';
 | 
			
		||||
			} else {
 | 
			
		||||
				return this.$store.state.device.darkmode ? '#fff' : '#555';
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
root(isDark)
 | 
			
		||||
	height 100%
 | 
			
		||||
	width 100%
 | 
			
		||||
 | 
			
		||||
	> .fetching
 | 
			
		||||
	> .empty
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
 | 
			
		||||
		> [data-fa]
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
 | 
			
		||||
	> div
 | 
			
		||||
		height 100%
 | 
			
		||||
		width 100%
 | 
			
		||||
 | 
			
		||||
.jtivnzhfwquxpsfidertopbmwmchmnmo[data-darkmode]
 | 
			
		||||
	root(true)
 | 
			
		||||
 | 
			
		||||
.jtivnzhfwquxpsfidertopbmwmchmnmo:not([data-darkmode])
 | 
			
		||||
	root(false)
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
			<header>
 | 
			
		||||
				<h1>{{ title }}</h1>
 | 
			
		||||
			</header>
 | 
			
		||||
			<p>{{ description }}</p>
 | 
			
		||||
			<p>{{ description.length > 85 ? description.slice(0, 85) + '…' : description }}</p>
 | 
			
		||||
			<footer>
 | 
			
		||||
				<img class="icon" v-if="icon" :src="icon"/>
 | 
			
		||||
				<p>{{ sitename }}</p>
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ export default Vue.extend({
 | 
			
		||||
	props: ['source', 'compact'],
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			v: this.$store.state.device.visibility || 'public'
 | 
			
		||||
			v: this.$store.state.settings.rememberNoteVisibility ? (this.$store.state.device.visibility || this.$store.state.settings.defaultNoteVisibility) : this.$store.state.settings.defaultNoteVisibility
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
@@ -97,7 +97,9 @@ export default Vue.extend({
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		choose(visibility) {
 | 
			
		||||
			this.$store.commit('device/setVisibility', visibility);
 | 
			
		||||
			if (this.$store.state.settings.rememberNoteVisibility) {
 | 
			
		||||
				this.$store.commit('device/setVisibility', visibility);
 | 
			
		||||
			}
 | 
			
		||||
			this.$emit('chosen', visibility);
 | 
			
		||||
			this.$destroy();
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,24 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-welcome-timeline">
 | 
			
		||||
	<div v-for="note in notes">
 | 
			
		||||
		<mk-avatar class="avatar" :user="note.user" target="_blank"/>
 | 
			
		||||
		<div class="body">
 | 
			
		||||
			<header>
 | 
			
		||||
				<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link>
 | 
			
		||||
				<span class="username">@{{ note.user | acct }}</span>
 | 
			
		||||
				<div class="info">
 | 
			
		||||
					<router-link class="created-at" :to="note | notePage">
 | 
			
		||||
						<mk-time :time="note.createdAt"/>
 | 
			
		||||
					</router-link>
 | 
			
		||||
	<transition-group name="ldzpakcixzickvggyixyrhqwjaefknon" tag="div">
 | 
			
		||||
		<div v-for="note in notes" :key="note.id">
 | 
			
		||||
			<mk-avatar class="avatar" :user="note.user" target="_blank"/>
 | 
			
		||||
			<div class="body">
 | 
			
		||||
				<header>
 | 
			
		||||
					<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link>
 | 
			
		||||
					<span class="username">@{{ note.user | acct }}</span>
 | 
			
		||||
					<div class="info">
 | 
			
		||||
						<router-link class="created-at" :to="note | notePage">
 | 
			
		||||
							<mk-time :time="note.createdAt"/>
 | 
			
		||||
						</router-link>
 | 
			
		||||
					</div>
 | 
			
		||||
				</header>
 | 
			
		||||
				<div class="text">
 | 
			
		||||
					<misskey-flavored-markdown v-if="note.text" :text="note.text"/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</header>
 | 
			
		||||
			<div class="text">
 | 
			
		||||
				<misskey-flavored-markdown v-if="note.text" :text="note.text"/>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	</transition-group>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -83,64 +85,73 @@ export default Vue.extend({
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.ldzpakcixzickvggyixyrhqwjaefknon-enter
 | 
			
		||||
.ldzpakcixzickvggyixyrhqwjaefknon-leave-to
 | 
			
		||||
	opacity 0
 | 
			
		||||
	transform translateY(-30px)
 | 
			
		||||
 | 
			
		||||
root(isDark)
 | 
			
		||||
	background isDark ? #282C37 : #fff
 | 
			
		||||
 | 
			
		||||
	> div
 | 
			
		||||
		padding 16px
 | 
			
		||||
		overflow-wrap break-word
 | 
			
		||||
		font-size .9em
 | 
			
		||||
		color isDark ? #fff : #4C4C4C
 | 
			
		||||
		border-bottom 1px solid isDark ? rgba(#000, 0.1) : rgba(#000, 0.05)
 | 
			
		||||
		> *
 | 
			
		||||
			transition transform .3s ease, opacity .3s ease
 | 
			
		||||
 | 
			
		||||
		&:after
 | 
			
		||||
			content ""
 | 
			
		||||
			display block
 | 
			
		||||
			clear both
 | 
			
		||||
		> div
 | 
			
		||||
			padding 16px
 | 
			
		||||
			overflow-wrap break-word
 | 
			
		||||
			font-size .9em
 | 
			
		||||
			color isDark ? #fff : #4C4C4C
 | 
			
		||||
			border-bottom 1px solid isDark ? rgba(#000, 0.1) : rgba(#000, 0.05)
 | 
			
		||||
 | 
			
		||||
		> .avatar
 | 
			
		||||
			display block
 | 
			
		||||
			float left
 | 
			
		||||
			position -webkit-sticky
 | 
			
		||||
			position sticky
 | 
			
		||||
			top 16px
 | 
			
		||||
			width 42px
 | 
			
		||||
			height 42px
 | 
			
		||||
			border-radius 6px
 | 
			
		||||
			&:after
 | 
			
		||||
				content ""
 | 
			
		||||
				display block
 | 
			
		||||
				clear both
 | 
			
		||||
 | 
			
		||||
		> .body
 | 
			
		||||
			float right
 | 
			
		||||
			width calc(100% - 42px)
 | 
			
		||||
			padding-left 12px
 | 
			
		||||
			> .avatar
 | 
			
		||||
				display block
 | 
			
		||||
				float left
 | 
			
		||||
				position -webkit-sticky
 | 
			
		||||
				position sticky
 | 
			
		||||
				top 16px
 | 
			
		||||
				width 42px
 | 
			
		||||
				height 42px
 | 
			
		||||
				border-radius 6px
 | 
			
		||||
 | 
			
		||||
			> header
 | 
			
		||||
				display flex
 | 
			
		||||
				align-items center
 | 
			
		||||
				margin-bottom 4px
 | 
			
		||||
				white-space nowrap
 | 
			
		||||
			> .body
 | 
			
		||||
				float right
 | 
			
		||||
				width calc(100% - 42px)
 | 
			
		||||
				padding-left 12px
 | 
			
		||||
 | 
			
		||||
				> .name
 | 
			
		||||
					display block
 | 
			
		||||
					margin 0 .5em 0 0
 | 
			
		||||
					padding 0
 | 
			
		||||
					overflow hidden
 | 
			
		||||
					font-weight bold
 | 
			
		||||
					text-overflow ellipsis
 | 
			
		||||
					color isDark ? #fff : #627079
 | 
			
		||||
				> header
 | 
			
		||||
					display flex
 | 
			
		||||
					align-items center
 | 
			
		||||
					margin-bottom 4px
 | 
			
		||||
					white-space nowrap
 | 
			
		||||
 | 
			
		||||
				> .username
 | 
			
		||||
					margin 0 .5em 0 0
 | 
			
		||||
					color isDark ? #606984 : #ccc
 | 
			
		||||
					> .name
 | 
			
		||||
						display block
 | 
			
		||||
						margin 0 .5em 0 0
 | 
			
		||||
						padding 0
 | 
			
		||||
						overflow hidden
 | 
			
		||||
						font-weight bold
 | 
			
		||||
						text-overflow ellipsis
 | 
			
		||||
						color isDark ? #fff : #627079
 | 
			
		||||
 | 
			
		||||
				> .info
 | 
			
		||||
					margin-left auto
 | 
			
		||||
					font-size 0.9em
 | 
			
		||||
					> .username
 | 
			
		||||
						margin 0 .5em 0 0
 | 
			
		||||
						color isDark ? #606984 : #ccc
 | 
			
		||||
 | 
			
		||||
					> .created-at
 | 
			
		||||
						color isDark ? #606984 : #c0c0c0
 | 
			
		||||
					> .info
 | 
			
		||||
						margin-left auto
 | 
			
		||||
						font-size 0.9em
 | 
			
		||||
 | 
			
		||||
			> .text
 | 
			
		||||
				text-align left
 | 
			
		||||
						> .created-at
 | 
			
		||||
							color isDark ? #606984 : #c0c0c0
 | 
			
		||||
 | 
			
		||||
				> .text
 | 
			
		||||
					text-align left
 | 
			
		||||
 | 
			
		||||
.mk-welcome-timeline[data-darkmode]
 | 
			
		||||
	root(true)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mkw-analog-clock">
 | 
			
		||||
	<mk-widget-container :naked="!(props.design % 2)" :show-header="false">
 | 
			
		||||
	<mk-widget-container :naked="props.style % 2 === 0" :show-header="false">
 | 
			
		||||
		<div class="mkw-analog-clock--body">
 | 
			
		||||
			<mk-analog-clock :dark="$store.state.device.darkmode" :smooth="!(props.design && ~props.design)"/>
 | 
			
		||||
			<mk-analog-clock :dark="$store.state.device.darkmode" :smooth="props.style < 2"/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</mk-widget-container>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -13,13 +13,12 @@ import define from '../../../common/define-widget';
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'analog-clock',
 | 
			
		||||
	props: () => ({
 | 
			
		||||
		design: -1
 | 
			
		||||
		style: 0
 | 
			
		||||
	})
 | 
			
		||||
}).extend({
 | 
			
		||||
	methods: {
 | 
			
		||||
		func() {
 | 
			
		||||
			if (++this.props.design > 2)
 | 
			
		||||
				this.props.design = -1;
 | 
			
		||||
			this.props.style = (this.props.style + 1) % 4;
 | 
			
		||||
			this.save();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="anltbovirfeutcigvwgmgxipejaeozxi"
 | 
			
		||||
	:data-found="broadcasts.length != 0"
 | 
			
		||||
	:data-found="announcements && announcements.length != 0"
 | 
			
		||||
	:data-melt="props.design == 1"
 | 
			
		||||
	:data-mobile="platform == 'mobile'"
 | 
			
		||||
>
 | 
			
		||||
@@ -14,12 +14,12 @@
 | 
			
		||||
		</svg>
 | 
			
		||||
	</div>
 | 
			
		||||
	<p class="fetching" v-if="fetching">%i18n:@fetching%<mk-ellipsis/></p>
 | 
			
		||||
	<h1 v-if="!fetching">{{ broadcasts.length == 0 ? '%i18n:@no-broadcasts%' : broadcasts[i].title }}</h1>
 | 
			
		||||
	<h1 v-if="!fetching">{{ announcements.length == 0 ? '%i18n:@no-broadcasts%' : announcements[i].title }}</h1>
 | 
			
		||||
	<p v-if="!fetching">
 | 
			
		||||
		<span v-if="broadcasts.length != 0" v-html="broadcasts[i].text"></span>
 | 
			
		||||
		<template v-if="broadcasts.length == 0">%i18n:@have-a-nice-day%</template>
 | 
			
		||||
		<span v-if="announcements.length != 0" v-html="announcements[i].text"></span>
 | 
			
		||||
		<template v-if="announcements.length == 0">%i18n:@have-a-nice-day%</template>
 | 
			
		||||
	</p>
 | 
			
		||||
	<a v-if="broadcasts.length > 1" @click="next">%i18n:@next% >></a>
 | 
			
		||||
	<a v-if="announcements.length > 1" @click="next">%i18n:@next% >></a>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -36,18 +36,18 @@ export default define({
 | 
			
		||||
		return {
 | 
			
		||||
			i: 0,
 | 
			
		||||
			fetching: true,
 | 
			
		||||
			broadcasts: []
 | 
			
		||||
			announcements: []
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		(this as any).os.getMeta().then(meta => {
 | 
			
		||||
			this.broadcasts = meta.broadcasts;
 | 
			
		||||
			this.announcements = meta.broadcasts;
 | 
			
		||||
			this.fetching = false;
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		next() {
 | 
			
		||||
			if (this.i == this.broadcasts.length - 1) {
 | 
			
		||||
			if (this.i == this.announcements.length - 1) {
 | 
			
		||||
				this.i = 0;
 | 
			
		||||
			} else {
 | 
			
		||||
				this.i++;
 | 
			
		||||
@@ -126,7 +126,7 @@ root(isDark)
 | 
			
		||||
		margin 0
 | 
			
		||||
		font-size 0.95em
 | 
			
		||||
		font-weight normal
 | 
			
		||||
		color #4078c0
 | 
			
		||||
		color isDark ? #539eff : #4078c0
 | 
			
		||||
 | 
			
		||||
	> p
 | 
			
		||||
		display block
 | 
			
		||||
 
 | 
			
		||||
@@ -86,6 +86,7 @@ import MkRenoteFormWindow from './renote-form-window.vue';
 | 
			
		||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
 | 
			
		||||
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
 | 
			
		||||
import XSub from './notes.note.sub.vue';
 | 
			
		||||
import { sum } from '../../../../../prelude/array';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
@@ -122,9 +123,7 @@ export default Vue.extend({
 | 
			
		||||
		},
 | 
			
		||||
		reactionsCount(): number {
 | 
			
		||||
			return this.p.reactionCounts
 | 
			
		||||
				? Object.keys(this.p.reactionCounts)
 | 
			
		||||
					.map(key => this.p.reactionCounts[key])
 | 
			
		||||
					.reduce((a, b) => a + b)
 | 
			
		||||
				? sum(Object.values(this.p.reactionCounts))
 | 
			
		||||
				: 0;
 | 
			
		||||
		},
 | 
			
		||||
		title(): string {
 | 
			
		||||
 
 | 
			
		||||
@@ -78,6 +78,7 @@ import MkRenoteFormWindow from './renote-form-window.vue';
 | 
			
		||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
 | 
			
		||||
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
 | 
			
		||||
import XSub from './notes.note.sub.vue';
 | 
			
		||||
import { sum } from '../../../../../prelude/array';
 | 
			
		||||
 | 
			
		||||
function focus(el, fn) {
 | 
			
		||||
	const target = fn(el);
 | 
			
		||||
@@ -120,9 +121,7 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
		reactionsCount(): number {
 | 
			
		||||
			return this.p.reactionCounts
 | 
			
		||||
				? Object.keys(this.p.reactionCounts)
 | 
			
		||||
					.map(key => this.p.reactionCounts[key])
 | 
			
		||||
					.reduce((a, b) => a + b)
 | 
			
		||||
				? sum(Object.values(this.p.reactionCounts))
 | 
			
		||||
				: 0;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -62,6 +62,7 @@ import getFace from '../../../common/scripts/get-face';
 | 
			
		||||
import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
 | 
			
		||||
import parse from '../../../../../mfm/parse';
 | 
			
		||||
import { host } from '../../../config';
 | 
			
		||||
import { erase } from '../../../../../prelude/array';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
@@ -99,7 +100,7 @@ export default Vue.extend({
 | 
			
		||||
			useCw: false,
 | 
			
		||||
			cw: null,
 | 
			
		||||
			geo: null,
 | 
			
		||||
			visibility: this.$store.state.device.visibility || 'public',
 | 
			
		||||
			visibility: this.$store.state.settings.rememberNoteVisibility ? (this.$store.state.device.visibility || this.$store.state.settings.defaultNoteVisibility) : this.$store.state.settings.defaultNoteVisibility,
 | 
			
		||||
			visibleUsers: [],
 | 
			
		||||
			autocomplete: null,
 | 
			
		||||
			draghover: false,
 | 
			
		||||
@@ -346,7 +347,7 @@ export default Vue.extend({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		removeVisibleUser(user) {
 | 
			
		||||
			this.visibleUsers = this.visibleUsers.filter(u => u != user);
 | 
			
		||||
			this.visibleUsers = erase(user, this.visibleUsers);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		post() {
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,22 @@
 | 
			
		||||
			<mk-switch v-model="autoPopout" text="%i18n:@auto-popout%">
 | 
			
		||||
				<span>%i18n:@auto-popout-desc%</span>
 | 
			
		||||
			</mk-switch>
 | 
			
		||||
 | 
			
		||||
			<section>
 | 
			
		||||
				<header>%i18n:@note-visibility%</header>
 | 
			
		||||
				<mk-switch v-model="$store.state.settings.rememberNoteVisibility" @change="onChangeRememberNoteVisibility" text="%i18n:@remember-note-visibility%"/>
 | 
			
		||||
				<section>
 | 
			
		||||
					<header>%i18n:@default-note-visibility%</header>
 | 
			
		||||
					<ui-select v-model="defaultNoteVisibility">
 | 
			
		||||
						<option value="public">%i18n:common.note-visibility.public%</option>
 | 
			
		||||
						<option value="home">%i18n:common.note-visibility.home%</option>
 | 
			
		||||
						<option value="followers">%i18n:common.note-visibility.followers%</option>
 | 
			
		||||
						<option value="specified">%i18n:common.note-visibility.specified%</option>
 | 
			
		||||
						<option value="private">%i18n:common.note-visibility.private%</option>
 | 
			
		||||
					</ui-select>
 | 
			
		||||
				</section>
 | 
			
		||||
			</section>
 | 
			
		||||
 | 
			
		||||
			<details>
 | 
			
		||||
				<summary>%i18n:@advanced%</summary>
 | 
			
		||||
				<mk-switch v-model="apiViaStream" text="%i18n:@api-via-stream%">
 | 
			
		||||
@@ -44,6 +60,7 @@
 | 
			
		||||
				<button class="ui" @click="deleteWallpaper">%i18n:@delete-wallpaper%</button>
 | 
			
		||||
				<mk-switch v-model="darkmode" text="%i18n:@dark-mode%"/>
 | 
			
		||||
				<mk-switch v-model="$store.state.settings.circleIcons" @change="onChangeCircleIcons" text="%i18n:@circle-icons%"/>
 | 
			
		||||
				<mk-switch v-model="$store.state.settings.contrastedAcct" @change="onChangeContrastedAcct" text="%i18n:@contrasted-acct%"/>
 | 
			
		||||
				<mk-switch v-model="$store.state.settings.gradientWindowHeader" @change="onChangeGradientWindowHeader" text="%i18n:@gradient-window-header%"/>
 | 
			
		||||
				<mk-switch v-model="$store.state.settings.iLikeSushi" @change="onChangeILikeSushi" text="%i18n:common.i-like-sushi%"/>
 | 
			
		||||
			</div>
 | 
			
		||||
@@ -238,6 +255,11 @@ export default Vue.extend({
 | 
			
		||||
			set(value) { this.$store.commit('device/set', { key: 'apiViaStream', value }); }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		defaultNoteVisibility: {
 | 
			
		||||
			get() { return this.$store.state.settings.defaultNoteVisibility; },
 | 
			
		||||
			set(value) { this.$store.commit('settings/set', { key: 'defaultNoteVisibility', value }); }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		autoPopout: {
 | 
			
		||||
			get() { return this.$store.state.device.autoPopout; },
 | 
			
		||||
			set(value) { this.$store.commit('device/set', { key: 'autoPopout', value }); }
 | 
			
		||||
@@ -311,6 +333,12 @@ export default Vue.extend({
 | 
			
		||||
				value: v
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		onChangeRememberNoteVisibility(v) {
 | 
			
		||||
			this.$store.dispatch('settings/set', {
 | 
			
		||||
				key: 'rememberNoteVisibility',
 | 
			
		||||
				value: v
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		onChangeAutoWatch(v) {
 | 
			
		||||
			(this as any).api('i/update', {
 | 
			
		||||
				autoWatch: v
 | 
			
		||||
@@ -376,6 +404,12 @@ export default Vue.extend({
 | 
			
		||||
				value: v
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		onChangeContrastedAcct(v) {
 | 
			
		||||
			this.$store.dispatch('settings/set', {
 | 
			
		||||
				key: 'contrastedAcct',
 | 
			
		||||
				value: v
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		onChangeILikeSushi(v) {
 | 
			
		||||
			this.$store.dispatch('settings/set', {
 | 
			
		||||
				key: 'iLikeSushi',
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										41
									
								
								src/client/app/desktop/views/pages/admin/admin.hashtags.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/client/app/desktop/views/pages/admin/admin.hashtags.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="jdnqwkzlnxcfftthoybjxrebyolvoucw mk-admin-card">
 | 
			
		||||
	<header>%i18n:@hided-tags%</header>
 | 
			
		||||
	<textarea v-model="hidedTags"></textarea>
 | 
			
		||||
	<button class="ui" @click="save">%i18n:@save%</button>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			hidedTags: '',
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	created() {
 | 
			
		||||
		(this as any).os.getMeta().then(meta => {
 | 
			
		||||
			this.hidedTags = meta.hidedTags.join('\n');
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		save() {
 | 
			
		||||
			(this as any).api('admin/update-meta', {
 | 
			
		||||
				hidedTags: this.hidedTags.split('\n')
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
@import '~const.styl'
 | 
			
		||||
 | 
			
		||||
.jdnqwkzlnxcfftthoybjxrebyolvoucw
 | 
			
		||||
	textarea
 | 
			
		||||
		width 100%
 | 
			
		||||
		min-height 300px
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -5,6 +5,8 @@
 | 
			
		||||
			<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }">%fa:chalkboard .fw%%i18n:@dashboard%</li>
 | 
			
		||||
			<li @click="nav('users')" :class="{ active: page == 'users' }">%fa:users .fw%%i18n:@users%</li>
 | 
			
		||||
			<li @click="nav('announcements')" :class="{ active: page == 'announcements' }">%fa:broadcast-tower .fw%%i18n:@announcements%</li>
 | 
			
		||||
			<li @click="nav('hashtags')" :class="{ active: page == 'hashtags' }">%fa:hashtag .fw%%i18n:@hashtags%</li>
 | 
			
		||||
 | 
			
		||||
			<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }">%fa:cloud .fw%%i18n:@drive%</li> -->
 | 
			
		||||
			<!-- <li @click="nav('update')" :class="{ active: page == 'update' }">%i18n:@update%</li> -->
 | 
			
		||||
		</ul>
 | 
			
		||||
@@ -17,6 +19,9 @@
 | 
			
		||||
		<div v-show="page == 'announcements'">
 | 
			
		||||
			<x-announcements/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div v-show="page == 'hashtags'">
 | 
			
		||||
			<x-hashtags/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div v-if="page == 'users'">
 | 
			
		||||
			<x-suspend-user/>
 | 
			
		||||
			<x-unsuspend-user/>
 | 
			
		||||
@@ -33,6 +38,7 @@
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
import XDashboard from "./admin.dashboard.vue";
 | 
			
		||||
import XAnnouncements from "./admin.announcements.vue";
 | 
			
		||||
import XHashtags from "./admin.hashtags.vue";
 | 
			
		||||
import XSuspendUser from "./admin.suspend-user.vue";
 | 
			
		||||
import XUnsuspendUser from "./admin.unsuspend-user.vue";
 | 
			
		||||
import XVerifyUser from "./admin.verify-user.vue";
 | 
			
		||||
@@ -43,6 +49,7 @@ export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XDashboard,
 | 
			
		||||
		XAnnouncements,
 | 
			
		||||
		XHashtags,
 | 
			
		||||
		XSuspendUser,
 | 
			
		||||
		XUnsuspendUser,
 | 
			
		||||
		XVerifyUser,
 | 
			
		||||
 
 | 
			
		||||
@@ -28,12 +28,14 @@
 | 
			
		||||
					<span class="divider">|</span>
 | 
			
		||||
					<span class="signin" @click="signin">%i18n:@signin%</span>
 | 
			
		||||
				</p>
 | 
			
		||||
 | 
			
		||||
				<img src="/assets/pointer.png" alt="" class="char">
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div class="announcements block">
 | 
			
		||||
			<header>%fa:broadcast-tower% %i18n:@announcements%</header>
 | 
			
		||||
			<div>
 | 
			
		||||
			<div v-if="announcements && announcements.length > 0">
 | 
			
		||||
				<div v-for="announcement in announcements">
 | 
			
		||||
					<h1 v-html="announcement.title"></h1>
 | 
			
		||||
					<div v-html="announcement.text"></div>
 | 
			
		||||
@@ -48,6 +50,12 @@
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div class="tag-cloud block">
 | 
			
		||||
			<div>
 | 
			
		||||
				<mk-tag-cloud/>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div class="nav block">
 | 
			
		||||
			<div>
 | 
			
		||||
				<mk-nav class="nav"/>
 | 
			
		||||
@@ -67,6 +75,16 @@
 | 
			
		||||
					<mk-welcome-timeline class="tl" :max="20"/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="info block">
 | 
			
		||||
				<header>%fa:info-circle% %i18n:@info%</header>
 | 
			
		||||
				<div>
 | 
			
		||||
					<div v-if="meta" class="body">
 | 
			
		||||
						<p>Version: <b>{{ meta.version }}</b></p>
 | 
			
		||||
						<p>Maintainer: <b><a :href="meta.maintainer.url" target="_blank">{{ meta.maintainer.name }}</a></b></p>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
@@ -85,10 +103,12 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import { host, copyright } from '../../../config';
 | 
			
		||||
import { concat } from '../../../../../prelude/array';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			meta: null,
 | 
			
		||||
			stats: null,
 | 
			
		||||
			copyright,
 | 
			
		||||
			host,
 | 
			
		||||
@@ -101,6 +121,7 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
	created() {
 | 
			
		||||
		(this as any).os.getMeta().then(meta => {
 | 
			
		||||
			this.meta = meta;
 | 
			
		||||
			this.name = meta.name;
 | 
			
		||||
			this.description = meta.description;
 | 
			
		||||
			this.announcements = meta.broadcasts;
 | 
			
		||||
@@ -119,8 +140,8 @@ export default Vue.extend({
 | 
			
		||||
		(this as any).api('notes/local-timeline', {
 | 
			
		||||
			fileType: image,
 | 
			
		||||
			limit: 6
 | 
			
		||||
		}).then(notes => {
 | 
			
		||||
			const files = [].concat(...notes.map(n => n.files));
 | 
			
		||||
		}).then((notes: any[]) => {
 | 
			
		||||
			const files = concat(notes.map((n: any): any[] => n.files));
 | 
			
		||||
			this.photos = files.filter(f => image.includes(f.type)).slice(0, 6);
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
@@ -207,13 +228,12 @@ root(isDark)
 | 
			
		||||
 | 
			
		||||
	> .body
 | 
			
		||||
		display grid
 | 
			
		||||
		grid-template-rows 1fr 1fr 64px
 | 
			
		||||
		grid-template-rows 1fr 1fr 256px 64px
 | 
			
		||||
		grid-template-columns 1fr 1fr 350px
 | 
			
		||||
		gap 16px
 | 
			
		||||
		width 100%
 | 
			
		||||
		max-width 1200px
 | 
			
		||||
		height 100vh
 | 
			
		||||
		min-height 1000px
 | 
			
		||||
		height 1200px
 | 
			
		||||
		margin 0 auto
 | 
			
		||||
		padding 64px
 | 
			
		||||
 | 
			
		||||
@@ -246,6 +266,7 @@ root(isDark)
 | 
			
		||||
 | 
			
		||||
			> div
 | 
			
		||||
				padding 32px
 | 
			
		||||
				min-height 100%
 | 
			
		||||
 | 
			
		||||
				> h1
 | 
			
		||||
					margin 0
 | 
			
		||||
@@ -280,6 +301,17 @@ root(isDark)
 | 
			
		||||
						&:hover
 | 
			
		||||
							color $theme-color
 | 
			
		||||
 | 
			
		||||
				> .char
 | 
			
		||||
					display block
 | 
			
		||||
					position absolute
 | 
			
		||||
					right 16px
 | 
			
		||||
					bottom 16px
 | 
			
		||||
					width 180px
 | 
			
		||||
					opacity 0.3
 | 
			
		||||
 | 
			
		||||
				> *:not(.char)
 | 
			
		||||
					z-index 1
 | 
			
		||||
 | 
			
		||||
		> .announcements
 | 
			
		||||
			grid-row 2
 | 
			
		||||
			grid-column 1
 | 
			
		||||
@@ -313,17 +345,25 @@ root(isDark)
 | 
			
		||||
					background-position center center
 | 
			
		||||
					background-size cover
 | 
			
		||||
 | 
			
		||||
		> .tag-cloud
 | 
			
		||||
			grid-row 3
 | 
			
		||||
			grid-column 1 / 3
 | 
			
		||||
 | 
			
		||||
			> div
 | 
			
		||||
				height 256px
 | 
			
		||||
				padding 32px
 | 
			
		||||
 | 
			
		||||
		> .nav
 | 
			
		||||
			display flex
 | 
			
		||||
			justify-content center
 | 
			
		||||
			align-items center
 | 
			
		||||
			grid-row 3
 | 
			
		||||
			grid-row 4
 | 
			
		||||
			grid-column 1 / 3
 | 
			
		||||
			font-size 14px
 | 
			
		||||
 | 
			
		||||
		> .side
 | 
			
		||||
			display grid
 | 
			
		||||
			grid-row 1 / 4
 | 
			
		||||
			grid-row 1 / 5
 | 
			
		||||
			grid-column 3
 | 
			
		||||
			grid-template-rows 1fr 350px
 | 
			
		||||
			grid-template-columns 1fr
 | 
			
		||||
@@ -339,6 +379,18 @@ root(isDark)
 | 
			
		||||
				grid-column 1
 | 
			
		||||
				padding 8px
 | 
			
		||||
 | 
			
		||||
			> .info
 | 
			
		||||
				grid-row 3
 | 
			
		||||
				grid-column 1
 | 
			
		||||
 | 
			
		||||
				> div
 | 
			
		||||
					padding 16px
 | 
			
		||||
 | 
			
		||||
					> .body
 | 
			
		||||
						> p
 | 
			
		||||
							display block
 | 
			
		||||
							margin 0
 | 
			
		||||
 | 
			
		||||
.mk-welcome[data-darkmode]
 | 
			
		||||
	root(true)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ import Err from './common/views/components/connect-failed.vue';
 | 
			
		||||
import { LocalTimelineStreamManager } from './common/scripts/streaming/local-timeline';
 | 
			
		||||
import { HybridTimelineStreamManager } from './common/scripts/streaming/hybrid-timeline';
 | 
			
		||||
import { GlobalTimelineStreamManager } from './common/scripts/streaming/global-timeline';
 | 
			
		||||
import { erase } from '../../prelude/array';
 | 
			
		||||
 | 
			
		||||
//#region api requests
 | 
			
		||||
let spinner = null;
 | 
			
		||||
@@ -537,7 +538,7 @@ export default class MiOS extends EventEmitter {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public unregisterStreamConnection(connection: Connection) {
 | 
			
		||||
		this.connections = this.connections.filter(c => c != connection);
 | 
			
		||||
		this.connections = erase(connection, this.connections);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -85,6 +85,7 @@ import parse from '../../../../../mfm/parse';
 | 
			
		||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
 | 
			
		||||
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
 | 
			
		||||
import XSub from './note.sub.vue';
 | 
			
		||||
import { sum } from '../../../../../prelude/array';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
@@ -123,9 +124,7 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
		reactionsCount(): number {
 | 
			
		||||
			return this.p.reactionCounts
 | 
			
		||||
				? Object.keys(this.p.reactionCounts)
 | 
			
		||||
					.map(key => this.p.reactionCounts[key])
 | 
			
		||||
					.reduce((a, b) => a + b)
 | 
			
		||||
				? sum(Object.values(this.p.reactionCounts))
 | 
			
		||||
				: 0;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -70,6 +70,7 @@ import parse from '../../../../../mfm/parse';
 | 
			
		||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
 | 
			
		||||
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
 | 
			
		||||
import XSub from './note.sub.vue';
 | 
			
		||||
import { sum } from '../../../../../prelude/array';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
@@ -100,9 +101,7 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
		reactionsCount(): number {
 | 
			
		||||
			return this.p.reactionCounts
 | 
			
		||||
				? Object.keys(this.p.reactionCounts)
 | 
			
		||||
					.map(key => this.p.reactionCounts[key])
 | 
			
		||||
					.reduce((a, b) => a + b)
 | 
			
		||||
				? sum(Object.values(this.p.reactionCounts))
 | 
			
		||||
				: 0;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -59,6 +59,7 @@ import MkVisibilityChooser from '../../../common/views/components/visibility-cho
 | 
			
		||||
import getFace from '../../../common/scripts/get-face';
 | 
			
		||||
import parse from '../../../../../mfm/parse';
 | 
			
		||||
import { host } from '../../../config';
 | 
			
		||||
import { erase } from '../../../../../prelude/array';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
@@ -94,7 +95,7 @@ export default Vue.extend({
 | 
			
		||||
			files: [],
 | 
			
		||||
			poll: false,
 | 
			
		||||
			geo: null,
 | 
			
		||||
			visibility: this.$store.state.device.visibility || 'public',
 | 
			
		||||
			visibility: this.$store.state.settings.rememberNoteVisibility ? (this.$store.state.device.visibility || this.$store.state.settings.defaultNoteVisibility) : this.$store.state.settings.defaultNoteVisibility,
 | 
			
		||||
			visibleUsers: [],
 | 
			
		||||
			useCw: false,
 | 
			
		||||
			cw: null,
 | 
			
		||||
@@ -262,7 +263,7 @@ export default Vue.extend({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		removeVisibleUser(user) {
 | 
			
		||||
			this.visibleUsers = this.visibleUsers.filter(u => u != user);
 | 
			
		||||
			this.visibleUsers = erase(user, this.visibleUsers);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		clear() {
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@
 | 
			
		||||
					<li @click="dark"><p><template v-if="$store.state.device.darkmode">%fa:moon%</template><template v-else>%fa:R moon%</template><span>%i18n:@darkmode%</span></p></li>
 | 
			
		||||
				</ul>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="announcements" v-if="announcements.length > 0">
 | 
			
		||||
			<div class="announcements" v-if="announcements && announcements.length > 0">
 | 
			
		||||
				<article v-for="announcement in announcements">
 | 
			
		||||
					<span v-html="announcement.title" class="title"></span>
 | 
			
		||||
					<div v-html="announcement.text"></div>
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@
 | 
			
		||||
				<section>
 | 
			
		||||
					<ui-switch v-model="darkmode">%i18n:@dark-mode%</ui-switch>
 | 
			
		||||
					<ui-switch v-model="$store.state.settings.circleIcons" @change="onChangeCircleIcons">%i18n:@circle-icons%</ui-switch>
 | 
			
		||||
					<ui-switch v-model="$store.state.settings.contrastedAcct" @change="onChangeContrastedAcct">%i18n:@contrasted-acct%</ui-switch>
 | 
			
		||||
					<ui-switch v-model="$store.state.settings.iLikeSushi" @change="onChangeILikeSushi">%i18n:common.i-like-sushi%</ui-switch>
 | 
			
		||||
					<ui-switch v-model="$store.state.settings.disableAnimatedMfm" @change="onChangeDisableAnimatedMfm">%i18n:common.disable-animated-mfm%</ui-switch>
 | 
			
		||||
					<ui-switch v-model="$store.state.settings.games.reversi.showBoardLabels" @change="onChangeReversiBoardLabels">%i18n:common.show-reversi-board-labels%</ui-switch>
 | 
			
		||||
@@ -52,6 +53,21 @@
 | 
			
		||||
					<ui-switch v-model="$store.state.settings.loadRemoteMedia" @change="onChangeLoadRemoteMedia">%i18n:@load-remote-media%</ui-switch>
 | 
			
		||||
					<ui-switch v-model="lightmode">%i18n:@i-am-under-limited-internet%</ui-switch>
 | 
			
		||||
				</section>
 | 
			
		||||
 | 
			
		||||
				<section>
 | 
			
		||||
					<header>%i18n:@note-visibility%</header>
 | 
			
		||||
					<ui-switch v-model="$store.state.settings.rememberNoteVisibility" @change="onChangeRememberNoteVisibility">%i18n:@remember-note-visibility%</ui-switch>
 | 
			
		||||
					<section>
 | 
			
		||||
						<header>%i18n:@default-note-visibility%</header>
 | 
			
		||||
						<ui-select v-model="defaultNoteVisibility">
 | 
			
		||||
							<option value="public">%i18n:common.note-visibility.public%</option>
 | 
			
		||||
							<option value="home">%i18n:common.note-visibility.home%</option>
 | 
			
		||||
							<option value="followers">%i18n:common.note-visibility.followers%</option>
 | 
			
		||||
							<option value="specified">%i18n:common.note-visibility.specified%</option>
 | 
			
		||||
							<option value="private">%i18n:common.note-visibility.private%</option>
 | 
			
		||||
						</ui-select>
 | 
			
		||||
					</section>
 | 
			
		||||
				</section>
 | 
			
		||||
			</ui-card>
 | 
			
		||||
 | 
			
		||||
			<ui-card>
 | 
			
		||||
@@ -160,6 +176,11 @@ export default Vue.extend({
 | 
			
		||||
			set(value) { this.$store.commit('device/set', { key: 'mobileNotificationPosition', value }); }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		defaultNoteVisibility: {
 | 
			
		||||
			get() { return this.$store.state.settings.defaultNoteVisibility; },
 | 
			
		||||
			set(value) { this.$store.commit('settings/set', { key: 'defaultNoteVisibility', value }); }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		lightmode: {
 | 
			
		||||
			get() { return this.$store.state.device.lightmode; },
 | 
			
		||||
			set(value) { this.$store.commit('device/set', { key: 'lightmode', value }); }
 | 
			
		||||
@@ -197,6 +218,13 @@ export default Vue.extend({
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onChangeRememberNoteVisibility(v) {
 | 
			
		||||
			this.$store.dispatch('settings/set', {
 | 
			
		||||
				key: 'rememberNoteVisibility',
 | 
			
		||||
				value: v
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onChangeDisableViaMobile(v) {
 | 
			
		||||
			this.$store.dispatch('settings/set', {
 | 
			
		||||
				key: 'disableViaMobile',
 | 
			
		||||
@@ -218,6 +246,13 @@ export default Vue.extend({
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onChangeContrastedAcct(v) {
 | 
			
		||||
			this.$store.dispatch('settings/set', {
 | 
			
		||||
				key: 'contrastedAcct',
 | 
			
		||||
				value: v
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onChangeILikeSushi(v) {
 | 
			
		||||
			this.$store.dispatch('settings/set', {
 | 
			
		||||
				key: 'iLikeSushi',
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
			<mk-welcome-timeline/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="hashtags">
 | 
			
		||||
			<router-link v-for="tag in tags" :key="tag" :to="`/tags/${ tag }`" :title="tag">#{{ tag }}</router-link>
 | 
			
		||||
			<mk-tag-cloud/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="photos">
 | 
			
		||||
			<div v-for="photo in photos" :style="`background-image: url(${photo.thumbnailUrl})`"></div>
 | 
			
		||||
@@ -30,6 +30,10 @@
 | 
			
		||||
				<div v-html="announcement.text"></div>
 | 
			
		||||
			</article>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="info" v-if="meta">
 | 
			
		||||
			<p>Version: <b>{{ meta.version }}</b></p>
 | 
			
		||||
			<p>Maintainer: <b><a :href="meta.maintainer.url" target="_blank">{{ meta.maintainer.name }}</a></b></p>
 | 
			
		||||
		</div>
 | 
			
		||||
		<footer>
 | 
			
		||||
			<small>{{ copyright }}</small>
 | 
			
		||||
		</footer>
 | 
			
		||||
@@ -39,24 +43,25 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import { apiUrl, copyright, host } from '../../../config';
 | 
			
		||||
import { copyright, host } from '../../../config';
 | 
			
		||||
import { concat } from '../../../../../prelude/array';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			apiUrl,
 | 
			
		||||
			meta: null,
 | 
			
		||||
			copyright,
 | 
			
		||||
			stats: null,
 | 
			
		||||
			host,
 | 
			
		||||
			name: 'Misskey',
 | 
			
		||||
			description: '',
 | 
			
		||||
			tags: [],
 | 
			
		||||
			photos: [],
 | 
			
		||||
			announcements: []
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	created() {
 | 
			
		||||
		(this as any).os.getMeta().then(meta => {
 | 
			
		||||
			this.meta = meta;
 | 
			
		||||
			this.name = meta.name;
 | 
			
		||||
			this.description = meta.description;
 | 
			
		||||
			this.announcements = meta.broadcasts;
 | 
			
		||||
@@ -66,10 +71,6 @@ export default Vue.extend({
 | 
			
		||||
			this.stats = stats;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		(this as any).api('hashtags/trend').then(stats => {
 | 
			
		||||
			this.tags = stats.map(x => x.tag);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		const image = [
 | 
			
		||||
			'image/jpeg',
 | 
			
		||||
			'image/png',
 | 
			
		||||
@@ -79,8 +80,8 @@ export default Vue.extend({
 | 
			
		||||
		(this as any).api('notes/local-timeline', {
 | 
			
		||||
			fileType: image,
 | 
			
		||||
			limit: 6
 | 
			
		||||
		}).then(notes => {
 | 
			
		||||
			const files = [].concat(...notes.map(n => n.files));
 | 
			
		||||
		}).then((notes: any[]) => {
 | 
			
		||||
			const files = concat(notes.map((n: any): any[] => n.files));
 | 
			
		||||
			this.photos = files.filter(f => image.includes(f.type)).slice(0, 6);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
@@ -164,12 +165,8 @@ root(isDark)
 | 
			
		||||
				-webkit-overflow-scrolling touch
 | 
			
		||||
 | 
			
		||||
		> .hashtags
 | 
			
		||||
			padding 16px 0
 | 
			
		||||
			border solid 2px #ddd
 | 
			
		||||
			border-radius 8px
 | 
			
		||||
 | 
			
		||||
			> *
 | 
			
		||||
				margin 0 16px
 | 
			
		||||
			padding 0 8px
 | 
			
		||||
			height 200px
 | 
			
		||||
 | 
			
		||||
		> .photos
 | 
			
		||||
			display grid
 | 
			
		||||
@@ -209,6 +206,14 @@ root(isDark)
 | 
			
		||||
				> .title
 | 
			
		||||
					font-weight bold
 | 
			
		||||
 | 
			
		||||
		> .info
 | 
			
		||||
			padding 16px 0
 | 
			
		||||
			border solid 2px #ddd
 | 
			
		||||
			border-radius 8px
 | 
			
		||||
 | 
			
		||||
			> *
 | 
			
		||||
				margin 0 16px
 | 
			
		||||
 | 
			
		||||
		> footer
 | 
			
		||||
			text-align center
 | 
			
		||||
			color #444
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import * as nestedProperty from 'nested-property';
 | 
			
		||||
 | 
			
		||||
import MiOS from './mios';
 | 
			
		||||
import { hostname } from './config';
 | 
			
		||||
import { erase } from '../../prelude/array';
 | 
			
		||||
 | 
			
		||||
const defaultSettings = {
 | 
			
		||||
	home: null,
 | 
			
		||||
@@ -15,6 +16,7 @@ const defaultSettings = {
 | 
			
		||||
	suggestRecentHashtags: true,
 | 
			
		||||
	showClockOnHeader: true,
 | 
			
		||||
	circleIcons: true,
 | 
			
		||||
	contrastedAcct: true,
 | 
			
		||||
	gradientWindowHeader: false,
 | 
			
		||||
	showReplyTarget: true,
 | 
			
		||||
	showMyRenotes: true,
 | 
			
		||||
@@ -24,6 +26,8 @@ const defaultSettings = {
 | 
			
		||||
	disableViaMobile: false,
 | 
			
		||||
	memo: null,
 | 
			
		||||
	iLikeSushi: false,
 | 
			
		||||
	rememberNoteVisibility: false,
 | 
			
		||||
	defaultNoteVisibility: 'public',
 | 
			
		||||
	games: {
 | 
			
		||||
		reversi: {
 | 
			
		||||
			showBoardLabels: false,
 | 
			
		||||
@@ -195,7 +199,7 @@ export default (os: MiOS) => new Vuex.Store({
 | 
			
		||||
 | 
			
		||||
				removeDeckColumn(state, id) {
 | 
			
		||||
					state.deck.columns = state.deck.columns.filter(c => c.id != id);
 | 
			
		||||
					state.deck.layout = state.deck.layout.map(ids => ids.filter(x => x != id));
 | 
			
		||||
					state.deck.layout = state.deck.layout.map(ids => erase(id, ids));
 | 
			
		||||
					state.deck.layout = state.deck.layout.filter(ids => ids.length > 0);
 | 
			
		||||
				},
 | 
			
		||||
 | 
			
		||||
@@ -266,7 +270,7 @@ export default (os: MiOS) => new Vuex.Store({
 | 
			
		||||
 | 
			
		||||
				stackLeftDeckColumn(state, id) {
 | 
			
		||||
					const i = state.deck.layout.findIndex(ids => ids.indexOf(id) != -1);
 | 
			
		||||
					state.deck.layout = state.deck.layout.map(ids => ids.filter(x => x != id));
 | 
			
		||||
					state.deck.layout = state.deck.layout.map(ids => erase(id, ids));
 | 
			
		||||
					const left = state.deck.layout[i - 1];
 | 
			
		||||
					if (left) state.deck.layout[i - 1].push(id);
 | 
			
		||||
					state.deck.layout = state.deck.layout.filter(ids => ids.length > 0);
 | 
			
		||||
@@ -274,7 +278,7 @@ export default (os: MiOS) => new Vuex.Store({
 | 
			
		||||
 | 
			
		||||
				popRightDeckColumn(state, id) {
 | 
			
		||||
					const i = state.deck.layout.findIndex(ids => ids.indexOf(id) != -1);
 | 
			
		||||
					state.deck.layout = state.deck.layout.map(ids => ids.filter(x => x != id));
 | 
			
		||||
					state.deck.layout = state.deck.layout.map(ids => erase(id, ids));
 | 
			
		||||
					state.deck.layout.splice(i + 1, 0, [id]);
 | 
			
		||||
					state.deck.layout = state.deck.layout.filter(ids => ids.length > 0);
 | 
			
		||||
				},
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import composeNotification from './common/scripts/compose-notification';
 | 
			
		||||
import { erase } from '../../prelude/array';
 | 
			
		||||
 | 
			
		||||
// キャッシュするリソース
 | 
			
		||||
const cachee = [
 | 
			
		||||
@@ -24,8 +25,7 @@ self.addEventListener('activate', ev => {
 | 
			
		||||
	// Clean up old caches
 | 
			
		||||
	ev.waitUntil(
 | 
			
		||||
		caches.keys().then(keys => Promise.all(
 | 
			
		||||
			keys
 | 
			
		||||
				.filter(key => key != _VERSION_)
 | 
			
		||||
			erase(_VERSION_, keys)
 | 
			
		||||
				.map(key => caches.delete(key))
 | 
			
		||||
		))
 | 
			
		||||
	);
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 232 KiB After Width: | Height: | Size: 274 KiB  | 
@@ -1,4 +1,4 @@
 | 
			
		||||
import { count, countIf } from "../../prelude/array";
 | 
			
		||||
import { count, concat } from "../../prelude/array";
 | 
			
		||||
 | 
			
		||||
// MISSKEY REVERSI ENGINE
 | 
			
		||||
 | 
			
		||||
@@ -110,7 +110,7 @@ export default class Reversi {
 | 
			
		||||
	 * 白石の数
 | 
			
		||||
	 */
 | 
			
		||||
	public get whiteCount() {
 | 
			
		||||
		return count(BLACK, this.board);
 | 
			
		||||
		return count(WHITE, this.board);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
@@ -238,87 +238,55 @@ export default class Reversi {
 | 
			
		||||
	/**
 | 
			
		||||
	 * 指定のマスに石を置いた時の、反転させられる石を取得します
 | 
			
		||||
	 * @param color 自分の色
 | 
			
		||||
	 * @param pos 位置
 | 
			
		||||
	 * @param initPos 位置
 | 
			
		||||
	 */
 | 
			
		||||
	public effects(color: Color, pos: number): number[] {
 | 
			
		||||
	public effects(color: Color, initPos: number): number[] {
 | 
			
		||||
		const enemyColor = !color;
 | 
			
		||||
 | 
			
		||||
		// ひっくり返せる石(の位置)リスト
 | 
			
		||||
		let stones: number[] = [];
 | 
			
		||||
		const diffVectors: [number, number][] = [
 | 
			
		||||
			[  0,  -1], // 上
 | 
			
		||||
			[ +1,  -1], // 右上
 | 
			
		||||
			[ +1,   0], // 右
 | 
			
		||||
			[ +1,  +1], // 右下
 | 
			
		||||
			[  0,  +1], // 下
 | 
			
		||||
			[ -1,  +1], // 左下
 | 
			
		||||
			[ -1,   0], // 左
 | 
			
		||||
			[ -1,  -1]  // 左上
 | 
			
		||||
		];
 | 
			
		||||
 | 
			
		||||
		const initPos = pos;
 | 
			
		||||
 | 
			
		||||
		// 走査
 | 
			
		||||
		const iterate = (fn: (i: number) => number[]) => {
 | 
			
		||||
			let i = 1;
 | 
			
		||||
			const found = [];
 | 
			
		||||
		const effectsInLine = ([dx, dy]: [number, number]): number[] => {
 | 
			
		||||
			const nextPos = (x: number, y: number): [number, number] => [x + dx, y + dy];
 | 
			
		||||
 | 
			
		||||
			const found: number[] = []; // 挟めるかもしれない相手の石を入れておく配列
 | 
			
		||||
			let [x, y] = this.transformPosToXy(initPos);
 | 
			
		||||
			while (true) {
 | 
			
		||||
				let [x, y] = fn(i);
 | 
			
		||||
				[x, y] = nextPos(x, y);
 | 
			
		||||
 | 
			
		||||
				// 座標が指し示す位置がボード外に出たとき
 | 
			
		||||
				if (this.opts.loopedBoard) {
 | 
			
		||||
					if (x <  0             ) x = this.mapWidth  - ((-x) % this.mapWidth);
 | 
			
		||||
					if (y <  0             ) y = this.mapHeight - ((-y) % this.mapHeight);
 | 
			
		||||
					if (x >= this.mapWidth ) x = x % this.mapWidth;
 | 
			
		||||
					if (y >= this.mapHeight) y = y % this.mapHeight;
 | 
			
		||||
					x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth;
 | 
			
		||||
					y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight;
 | 
			
		||||
 | 
			
		||||
					// for debug
 | 
			
		||||
					//if (x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight) {
 | 
			
		||||
					//	console.log(x, y);
 | 
			
		||||
					//}
 | 
			
		||||
 | 
			
		||||
					// 一周して自分に帰ってきたら
 | 
			
		||||
					if (this.transformXyToPos(x, y) == initPos) {
 | 
			
		||||
						// ↓のコメントアウトを外すと、「現時点で自分の石が隣接していないが、
 | 
			
		||||
						// そこに置いたとするとループして最終的に挟んだことになる」というケースを有効化します。(Test4のマップで違いが分かります)
 | 
			
		||||
						// このケースを有効にした方が良いのか無効にした方が良いのか判断がつかなかったためとりあえず無効としておきます
 | 
			
		||||
						// (あと無効な方がゲームとしておもしろそうだった)
 | 
			
		||||
						stones = stones.concat(found);
 | 
			
		||||
						break;
 | 
			
		||||
						// 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ)
 | 
			
		||||
						return found;
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					if (x == -1 || y == -1 || x == this.mapWidth || y == this.mapHeight) break;
 | 
			
		||||
					if (x == -1 || y == -1 || x == this.mapWidth || y == this.mapHeight) {
 | 
			
		||||
						return []; // 挟めないことが確定 (盤面外に到達)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				const pos = this.transformXyToPos(x, y);
 | 
			
		||||
 | 
			
		||||
				//#region 「配置不能」マスに当たった場合走査終了
 | 
			
		||||
				const pixel = this.mapDataGet(pos);
 | 
			
		||||
				if (pixel == 'null') break;
 | 
			
		||||
				//#endregion
 | 
			
		||||
 | 
			
		||||
				// 石取得
 | 
			
		||||
				if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達)
 | 
			
		||||
				const stone = this.board[pos];
 | 
			
		||||
 | 
			
		||||
				// 石が置かれていないマスなら走査終了
 | 
			
		||||
				if (stone === null) break;
 | 
			
		||||
 | 
			
		||||
				// 相手の石なら「ひっくり返せるかもリスト」に入れておく
 | 
			
		||||
				if (stone === enemyColor) found.push(pos);
 | 
			
		||||
 | 
			
		||||
				// 自分の石なら「ひっくり返せるかもリスト」を「ひっくり返せるリスト」に入れ、走査終了
 | 
			
		||||
				if (stone === color) {
 | 
			
		||||
					stones = stones.concat(found);
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				i++;
 | 
			
		||||
				if (stone === null) return []; // 挟めないことが確定 (石が置かれていないマスに到達)
 | 
			
		||||
				if (stone === enemyColor) found.push(pos); // 挟めるかもしれない (相手の石を発見)
 | 
			
		||||
				if (stone === color) return found; // 挟めることが確定 (対となる自分の石を発見)
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		const [x, y] = this.transformPosToXy(pos);
 | 
			
		||||
 | 
			
		||||
		iterate(i => [x    , y - i]); // 上
 | 
			
		||||
		iterate(i => [x + i, y - i]); // 右上
 | 
			
		||||
		iterate(i => [x + i, y    ]); // 右
 | 
			
		||||
		iterate(i => [x + i, y + i]); // 右下
 | 
			
		||||
		iterate(i => [x    , y + i]); // 下
 | 
			
		||||
		iterate(i => [x - i, y + i]); // 左下
 | 
			
		||||
		iterate(i => [x - i, y    ]); // 左
 | 
			
		||||
		iterate(i => [x - i, y - i]); // 左上
 | 
			
		||||
 | 
			
		||||
		return stones;
 | 
			
		||||
		return concat(diffVectors.map(effectsInLine));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
import { capitalize } from "../../../prelude/string";
 | 
			
		||||
 | 
			
		||||
function escape(text: string) {
 | 
			
		||||
	return text
 | 
			
		||||
		.replace(/>/g, '>')
 | 
			
		||||
@@ -89,7 +91,7 @@ const _keywords = [
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const keywords = _keywords
 | 
			
		||||
	.concat(_keywords.map(k => k[0].toUpperCase() + k.substr(1)))
 | 
			
		||||
	.concat(_keywords.map(capitalize))
 | 
			
		||||
	.concat(_keywords.map(k => k.toUpperCase()))
 | 
			
		||||
	.sort((a, b) => b.length - a.length);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,12 +4,13 @@ const Meta = db.get<IMeta>('meta');
 | 
			
		||||
export default Meta;
 | 
			
		||||
 | 
			
		||||
export type IMeta = {
 | 
			
		||||
	broadcasts: any[];
 | 
			
		||||
	stats: {
 | 
			
		||||
	broadcasts?: any[];
 | 
			
		||||
	stats?: {
 | 
			
		||||
		notesCount: number;
 | 
			
		||||
		originalNotesCount: number;
 | 
			
		||||
		usersCount: number;
 | 
			
		||||
		originalUsersCount: number;
 | 
			
		||||
	};
 | 
			
		||||
	disableRegistration: boolean;
 | 
			
		||||
	disableRegistration?: boolean;
 | 
			
		||||
	hidedTags?: string[];
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,22 @@ export function count<T>(x: T, xs: T[]): number {
 | 
			
		||||
	return countIf(y => x === y, xs);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function intersperse<T>(sep: T, xs: T[]): T[] {
 | 
			
		||||
	return [].concat(...xs.map(x => [sep, x])).slice(1);
 | 
			
		||||
export function concat<T>(xss: T[][]): T[] {
 | 
			
		||||
	return ([] as T[]).concat(...xss);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function intersperse<T>(sep: T, xs: T[]): T[] {
 | 
			
		||||
	return concat(xs.map(x => [sep, x])).slice(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function erase<T>(x: T, xs: T[]): T[] {
 | 
			
		||||
	return xs.filter(y => x !== y);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function unique<T>(xs: T[]): T[] {
 | 
			
		||||
	return [...new Set(xs)];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function sum(xs: number[]): number {
 | 
			
		||||
	return xs.reduce((a, b) => a + b, 0);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								src/prelude/string.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/prelude/string.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
export function capitalize(s: string): string {
 | 
			
		||||
	return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
 | 
			
		||||
}
 | 
			
		||||
@@ -5,7 +5,7 @@ export default (object: any, note: INote) => {
 | 
			
		||||
	const attributedTo = `${config.url}/users/${note.userId}`;
 | 
			
		||||
 | 
			
		||||
	return {
 | 
			
		||||
		id: `${config.url}/notes/${note._id}`,
 | 
			
		||||
		id: `${config.url}/notes/${note._id}/activity`,
 | 
			
		||||
		actor: `${config.url}/users/${note.userId}`,
 | 
			
		||||
		type: 'Announce',
 | 
			
		||||
		published: note.createdAt.toISOString(),
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ import User, { isLocalUser, ILocalUser, IUser } from '../models/user';
 | 
			
		||||
import renderNote from '../remote/activitypub/renderer/note';
 | 
			
		||||
import renderKey from '../remote/activitypub/renderer/key';
 | 
			
		||||
import renderPerson from '../remote/activitypub/renderer/person';
 | 
			
		||||
import Outbox from './activitypub/outbox';
 | 
			
		||||
import Outbox, { packActivity } from './activitypub/outbox';
 | 
			
		||||
import Followers from './activitypub/followers';
 | 
			
		||||
import Following from './activitypub/following';
 | 
			
		||||
 | 
			
		||||
@@ -77,6 +77,22 @@ router.get('/notes/:note', async (ctx, next) => {
 | 
			
		||||
	setResponseType(ctx);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// note activity
 | 
			
		||||
router.get('/notes/:note/activity', async ctx => {
 | 
			
		||||
	const note = await Note.findOne({
 | 
			
		||||
		_id: new mongo.ObjectID(ctx.params.note),
 | 
			
		||||
		visibility: { $in: ['public', 'home'] }
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	if (note === null) {
 | 
			
		||||
		ctx.status = 404;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.body = pack(await packActivity(note));
 | 
			
		||||
	setResponseType(ctx);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// outbox
 | 
			
		||||
router.get('/users/:user/outbox', Outbox);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,10 @@ import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-c
 | 
			
		||||
import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page';
 | 
			
		||||
import { setResponseType } from '../activitypub';
 | 
			
		||||
 | 
			
		||||
import Note from '../../models/note';
 | 
			
		||||
import Note, { INote } from '../../models/note';
 | 
			
		||||
import renderNote from '../../remote/activitypub/renderer/note';
 | 
			
		||||
import renderCreate from '../../remote/activitypub/renderer/create';
 | 
			
		||||
import renderAnnounce from '../../remote/activitypub/renderer/announce';
 | 
			
		||||
import { countIf } from '../../prelude/array';
 | 
			
		||||
 | 
			
		||||
export default async (ctx: Router.IRouterContext) => {
 | 
			
		||||
@@ -53,15 +55,7 @@ export default async (ctx: Router.IRouterContext) => {
 | 
			
		||||
 | 
			
		||||
		const query = {
 | 
			
		||||
			userId: user._id,
 | 
			
		||||
			$and: [{
 | 
			
		||||
				$or: [ { visibility: 'public' }, { visibility: 'home' } ]
 | 
			
		||||
			}, { // exclude renote, but include quote
 | 
			
		||||
				$or: [{
 | 
			
		||||
					text: { $ne: null }
 | 
			
		||||
				}, {
 | 
			
		||||
					fileIds: { $ne: [] }
 | 
			
		||||
				}]
 | 
			
		||||
			}]
 | 
			
		||||
			visibility: { $in: ['public', 'home'] }
 | 
			
		||||
		} as any;
 | 
			
		||||
 | 
			
		||||
		if (sinceId) {
 | 
			
		||||
@@ -85,10 +79,10 @@ export default async (ctx: Router.IRouterContext) => {
 | 
			
		||||
 | 
			
		||||
		if (sinceId) notes.reverse();
 | 
			
		||||
 | 
			
		||||
		const renderedNotes = await Promise.all(notes.map(note => renderNote(note, false)));
 | 
			
		||||
		const activities = await Promise.all(notes.map(note => packActivity(note)));
 | 
			
		||||
		const rendered = renderOrderedCollectionPage(
 | 
			
		||||
			`${partOf}?page=true${sinceId ? `&since_id=${sinceId}` : ''}${untilId ? `&until_id=${untilId}` : ''}`,
 | 
			
		||||
			user.notesCount, renderedNotes, partOf,
 | 
			
		||||
			user.notesCount, activities, partOf,
 | 
			
		||||
			notes.length > 0 ? `${partOf}?page=true&since_id=${notes[0]._id}` : null,
 | 
			
		||||
			notes.length > 0 ? `${partOf}?page=true&until_id=${notes[notes.length - 1]._id}` : null
 | 
			
		||||
		);
 | 
			
		||||
@@ -105,3 +99,16 @@ export default async (ctx: Router.IRouterContext) => {
 | 
			
		||||
		setResponseType(ctx);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Pack Create<Note> or Announce Activity
 | 
			
		||||
 * @param note Note
 | 
			
		||||
 */
 | 
			
		||||
export async function packActivity(note: INote): Promise<object> {
 | 
			
		||||
	if (note.renoteId && note.text == null) {
 | 
			
		||||
		const renote = await Note.findOne(note.renoteId);
 | 
			
		||||
		return renderAnnounce(renote.uri ? renote.uri : `${config.url}/notes/${renote._id}`, note);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return renderCreate(await renderNote(note, false), note);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -25,10 +25,8 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any)
 | 
			
		||||
		return rej('YOU_ARE_NOT_ADMIN');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (app && ep.meta.kind) {
 | 
			
		||||
		if (!app.permission.some(p => p === ep.meta.kind)) {
 | 
			
		||||
			return rej('PERMISSION_DENIED');
 | 
			
		||||
		}
 | 
			
		||||
	if (app && ep.meta.kind && !app.permission.some(p => p === ep.meta.kind)) {
 | 
			
		||||
		return rej('PERMISSION_DENIED');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (ep.meta.requireCredential && ep.meta.limit) {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,13 @@ export const meta = {
 | 
			
		||||
			desc: {
 | 
			
		||||
				'ja-JP': '招待制か否か'
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
		}),
 | 
			
		||||
 | 
			
		||||
		hidedTags: $.arr($.str).optional.nullable.note({
 | 
			
		||||
			desc: {
 | 
			
		||||
				'ja-JP': '統計などで無視するハッシュタグ'
 | 
			
		||||
			}
 | 
			
		||||
		}),
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -39,6 +45,10 @@ export default (params: any) => new Promise(async (res, rej) => {
 | 
			
		||||
		set.disableRegistration = ps.disableRegistration;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (Array.isArray(ps.hidedTags)) {
 | 
			
		||||
		set.hidedTags = ps.hidedTags;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	await Meta.update({}, {
 | 
			
		||||
		$set: set
 | 
			
		||||
	}, { upsert: true });
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										66
									
								
								src/server/api/endpoints/aggregation/hashtags.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/server/api/endpoints/aggregation/hashtags.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
			
		||||
import Note from '../../../../models/note';
 | 
			
		||||
import Meta from '../../../../models/meta';
 | 
			
		||||
 | 
			
		||||
export default () => new Promise(async (res, rej) => {
 | 
			
		||||
	const meta = await Meta.findOne({});
 | 
			
		||||
	const hidedTags = (meta.hidedTags || []).map(t => t.toLowerCase());
 | 
			
		||||
 | 
			
		||||
	const span = 1000 * 60 * 60 * 24 * 7; // 1週間
 | 
			
		||||
 | 
			
		||||
	//#region 1. 指定期間の内に投稿されたハッシュタグ(とユーザーのペア)を集計
 | 
			
		||||
	const data = await Note.aggregate([{
 | 
			
		||||
		$match: {
 | 
			
		||||
			createdAt: {
 | 
			
		||||
				$gt: new Date(Date.now() - span)
 | 
			
		||||
			},
 | 
			
		||||
			tagsLower: {
 | 
			
		||||
				$exists: true,
 | 
			
		||||
				$ne: []
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}, {
 | 
			
		||||
		$unwind: '$tagsLower'
 | 
			
		||||
	}, {
 | 
			
		||||
		$group: {
 | 
			
		||||
			_id: { tag: '$tagsLower', userId: '$userId' }
 | 
			
		||||
		}
 | 
			
		||||
	}]) as Array<{
 | 
			
		||||
		_id: {
 | 
			
		||||
			tag: string;
 | 
			
		||||
			userId: any;
 | 
			
		||||
		}
 | 
			
		||||
	}>;
 | 
			
		||||
	//#endregion
 | 
			
		||||
 | 
			
		||||
	if (data.length == 0) {
 | 
			
		||||
		return res([]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	let tags: Array<{
 | 
			
		||||
		name: string;
 | 
			
		||||
		count: number;
 | 
			
		||||
	}> = [];
 | 
			
		||||
 | 
			
		||||
	// カウント
 | 
			
		||||
	data.map(x => x._id).forEach(x => {
 | 
			
		||||
		// ブラックリストに登録されているタグなら弾く
 | 
			
		||||
		if (hidedTags.includes(x.tag)) return;
 | 
			
		||||
 | 
			
		||||
		const i = tags.findIndex(tag => tag.name == x.tag);
 | 
			
		||||
		if (i != -1) {
 | 
			
		||||
			tags[i].count++;
 | 
			
		||||
		} else {
 | 
			
		||||
			tags.push({
 | 
			
		||||
				name: x.tag,
 | 
			
		||||
				count: 1
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// タグを人気順に並べ替え
 | 
			
		||||
	tags = tags.sort((a, b) => b.count - a.count);
 | 
			
		||||
 | 
			
		||||
	tags = tags.slice(0, 30);
 | 
			
		||||
 | 
			
		||||
	res(tags);
 | 
			
		||||
});
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
import Note from '../../../../models/note';
 | 
			
		||||
import { erase } from '../../../../prelude/array';
 | 
			
		||||
import Meta from '../../../../models/meta';
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要
 | 
			
		||||
@@ -16,6 +18,9 @@ const max = 5;
 | 
			
		||||
 * Get trends of hashtags
 | 
			
		||||
 */
 | 
			
		||||
export default () => new Promise(async (res, rej) => {
 | 
			
		||||
	const meta = await Meta.findOne({});
 | 
			
		||||
	const hidedTags = (meta.hidedTags || []).map(t => t.toLowerCase());
 | 
			
		||||
 | 
			
		||||
	//#region 1. 直近Aの内に投稿されたハッシュタグ(とユーザーのペア)を集計
 | 
			
		||||
	const data = await Note.aggregate([{
 | 
			
		||||
		$match: {
 | 
			
		||||
@@ -52,6 +57,9 @@ export default () => new Promise(async (res, rej) => {
 | 
			
		||||
 | 
			
		||||
	// カウント
 | 
			
		||||
	data.map(x => x._id).forEach(x => {
 | 
			
		||||
		// ブラックリストに登録されているタグなら弾く
 | 
			
		||||
		if (hidedTags.includes(x.tag)) return;
 | 
			
		||||
 | 
			
		||||
		const i = tags.findIndex(tag => tag.name == x.tag);
 | 
			
		||||
		if (i != -1) {
 | 
			
		||||
			tags[i].count++;
 | 
			
		||||
@@ -85,8 +93,7 @@ export default () => new Promise(async (res, rej) => {
 | 
			
		||||
	//#endregion
 | 
			
		||||
 | 
			
		||||
	// タグを人気順に並べ替え
 | 
			
		||||
	let hots = (await Promise.all(hotsPromises))
 | 
			
		||||
		.filter(x => x != null)
 | 
			
		||||
	let hots = erase(null, await Promise.all(hotsPromises))
 | 
			
		||||
		.sort((a, b) => b.count - a.count)
 | 
			
		||||
		.map(tag => tag.name)
 | 
			
		||||
		.slice(0, max);
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
import * as os from 'os';
 | 
			
		||||
import config from '../../../config';
 | 
			
		||||
import Meta from '../../../models/meta';
 | 
			
		||||
import { ILocalUser } from '../../../models/user';
 | 
			
		||||
 | 
			
		||||
const pkg = require('../../../../package.json');
 | 
			
		||||
const client = require('../../../../built/client/meta.json');
 | 
			
		||||
@@ -11,7 +12,7 @@ const client = require('../../../../built/client/meta.json');
 | 
			
		||||
/**
 | 
			
		||||
 * Show core info
 | 
			
		||||
 */
 | 
			
		||||
export default () => new Promise(async (res, rej) => {
 | 
			
		||||
export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
 | 
			
		||||
	const meta: any = (await Meta.findOne()) || {};
 | 
			
		||||
 | 
			
		||||
	res({
 | 
			
		||||
@@ -35,6 +36,7 @@ export default () => new Promise(async (res, rej) => {
 | 
			
		||||
		disableRegistration: meta.disableRegistration,
 | 
			
		||||
		driveCapacityPerLocalUserMb: config.localDriveCapacityMb,
 | 
			
		||||
		recaptchaSitekey: config.recaptcha ? config.recaptcha.site_key : null,
 | 
			
		||||
		swPublickey: config.sw ? config.sw.public_key : null
 | 
			
		||||
		swPublickey: config.sw ? config.sw.public_key : null,
 | 
			
		||||
		hidedTags: (me && me.isAdmin) ? meta.hidedTags : undefined
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import Mute from '../../../../models/mute';
 | 
			
		||||
import { getFriendIds } from '../../common/get-friends';
 | 
			
		||||
import { pack } from '../../../../models/note';
 | 
			
		||||
import getParams from '../../get-params';
 | 
			
		||||
import { erase } from '../../../../prelude/array';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
	desc: {
 | 
			
		||||
@@ -103,23 +104,23 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 | 
			
		||||
	if (psErr) throw psErr;
 | 
			
		||||
 | 
			
		||||
	if (ps.includeUserUsernames != null) {
 | 
			
		||||
		const ids = (await Promise.all(ps.includeUserUsernames.map(async (username) => {
 | 
			
		||||
		const ids = erase(null, await Promise.all(ps.includeUserUsernames.map(async (username) => {
 | 
			
		||||
			const _user = await User.findOne({
 | 
			
		||||
				usernameLower: username.toLowerCase()
 | 
			
		||||
			});
 | 
			
		||||
			return _user ? _user._id : null;
 | 
			
		||||
		}))).filter(id => id != null);
 | 
			
		||||
		})));
 | 
			
		||||
 | 
			
		||||
		ids.forEach(id => ps.includeUserIds.push(id));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (ps.excludeUserUsernames != null) {
 | 
			
		||||
		const ids = (await Promise.all(ps.excludeUserUsernames.map(async (username) => {
 | 
			
		||||
		const ids = erase(null, await Promise.all(ps.excludeUserUsernames.map(async (username) => {
 | 
			
		||||
			const _user = await User.findOne({
 | 
			
		||||
				usernameLower: username.toLowerCase()
 | 
			
		||||
			});
 | 
			
		||||
			return _user ? _user._id : null;
 | 
			
		||||
		}))).filter(id => id != null);
 | 
			
		||||
		})));
 | 
			
		||||
 | 
			
		||||
		ids.forEach(id => ps.excludeUserIds.push(id));
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,3 +23,6 @@ block meta
 | 
			
		||||
		link(rel='prev' href=`${config.url}/notes/${note.prev}`)
 | 
			
		||||
	if note.next
 | 
			
		||||
		link(rel='next' href=`${config.url}/notes/${note.next}`)
 | 
			
		||||
 | 
			
		||||
	if !user.host
 | 
			
		||||
		link(rel='alternate' href=url type='application/activity+json')
 | 
			
		||||
 
 | 
			
		||||
@@ -18,3 +18,10 @@ block meta
 | 
			
		||||
	meta(property='og:description' content= user.description)
 | 
			
		||||
	meta(property='og:url'         content= url)
 | 
			
		||||
	meta(property='og:image'       content= img)
 | 
			
		||||
	
 | 
			
		||||
	if !user.host
 | 
			
		||||
		link(rel='alternate' href=`${config.url}/users/${user._id}` type='application/activity+json')
 | 
			
		||||
	if user.uri
 | 
			
		||||
		link(rel='alternate' href=user.uri type='application/activity+json')
 | 
			
		||||
	if user.url
 | 
			
		||||
		link(rel='alternate' href=user.url type='text/html')
 | 
			
		||||
 
 | 
			
		||||
@@ -34,8 +34,9 @@ export default async (url: string, user: IUser, folderId: mongodb.ObjectID = nul
 | 
			
		||||
	// write content at URL to temp file
 | 
			
		||||
	await new Promise((res, rej) => {
 | 
			
		||||
		const writable = fs.createWriteStream(path);
 | 
			
		||||
		const requestUrl = URL.parse(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url;
 | 
			
		||||
		request({
 | 
			
		||||
			url,
 | 
			
		||||
			url: requestUrl,
 | 
			
		||||
			headers: {
 | 
			
		||||
				'User-Agent': config.user_agent
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@ import isQuote from '../../misc/is-quote';
 | 
			
		||||
import { TextElementMention } from '../../mfm/parse/elements/mention';
 | 
			
		||||
import { TextElementHashtag } from '../../mfm/parse/elements/hashtag';
 | 
			
		||||
import { updateNoteStats } from '../update-chart';
 | 
			
		||||
import { erase, unique } from '../../prelude/array';
 | 
			
		||||
 | 
			
		||||
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
 | 
			
		||||
 | 
			
		||||
@@ -103,7 +104,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
 | 
			
		||||
	if (data.viaMobile == null) data.viaMobile = false;
 | 
			
		||||
 | 
			
		||||
	if (data.visibleUsers) {
 | 
			
		||||
		data.visibleUsers = data.visibleUsers.filter(x => x != null);
 | 
			
		||||
		data.visibleUsers = erase(null, data.visibleUsers);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (data.reply && data.reply.deletedAt != null) {
 | 
			
		||||
@@ -384,7 +385,7 @@ function extractHashtags(tokens: ReturnType<typeof parse>): string[] {
 | 
			
		||||
		.map(t => (t as TextElementHashtag).hashtag)
 | 
			
		||||
		.filter(tag => tag.length <= 100);
 | 
			
		||||
 | 
			
		||||
	return [...new Set(hashtags)];
 | 
			
		||||
	return unique(hashtags);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function index(note: INote) {
 | 
			
		||||
@@ -541,20 +542,20 @@ function incNotesCount(user: IUser) {
 | 
			
		||||
async function extractMentionedUsers(tokens: ReturnType<typeof parse>): Promise<IUser[]> {
 | 
			
		||||
	if (tokens == null) return [];
 | 
			
		||||
 | 
			
		||||
	const mentionTokens = [...new Set(
 | 
			
		||||
	const mentionTokens = unique(
 | 
			
		||||
		tokens
 | 
			
		||||
			.filter(t => t.type == 'mention') as TextElementMention[]
 | 
			
		||||
	)];
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	const mentionedUsers = [...new Set(
 | 
			
		||||
		(await Promise.all(mentionTokens.map(async m => {
 | 
			
		||||
	const mentionedUsers = unique(
 | 
			
		||||
		erase(null, await Promise.all(mentionTokens.map(async m => {
 | 
			
		||||
			try {
 | 
			
		||||
				return await resolveUser(m.username, m.host);
 | 
			
		||||
			} catch (e) {
 | 
			
		||||
				return null;
 | 
			
		||||
			}
 | 
			
		||||
		}))).filter(x => x != null)
 | 
			
		||||
	)];
 | 
			
		||||
		})))
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	return mentionedUsers;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@
 | 
			
		||||
		"no-empty":false,
 | 
			
		||||
		"ordered-imports": [false],
 | 
			
		||||
		"arrow-parens": false,
 | 
			
		||||
		"array-type": false,
 | 
			
		||||
		"object-literal-shorthand": false,
 | 
			
		||||
		"object-literal-key-quotes": false,
 | 
			
		||||
		"triple-equals": [false],
 | 
			
		||||
 
 | 
			
		||||
@@ -196,7 +196,7 @@ module.exports = {
 | 
			
		||||
			}, {
 | 
			
		||||
				loader: 'sass-loader',
 | 
			
		||||
				options: {
 | 
			
		||||
					importer: jsonImporter,
 | 
			
		||||
					importer: jsonImporter(),
 | 
			
		||||
				}
 | 
			
		||||
			}]
 | 
			
		||||
		}, {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user