Compare commits
	
		
			226 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | d423f8ae57 | ||
|   | 8e5ce7f8e3 | ||
|   | 33dfc21e4b | ||
|   | 7259887124 | ||
|   | 800de03187 | ||
|   | 78ba305e5c | ||
|   | bfed1475bb | ||
|   | 3e20ea5b2e | ||
|   | 3f30a69b8b | ||
|   | 2d2e16d9f6 | ||
|   | 9c32118b77 | ||
|   | ecf2eb4738 | ||
|   | 699879d95d | ||
|   | 6677508ba7 | ||
|   | 58da32358b | ||
|   | b9eafeee3f | ||
|   | c56ff5d88d | ||
|   | 9a295a85b1 | ||
|   | 7135c0e308 | ||
|   | 1962bfb4a5 | ||
|   | a1fca2550e | ||
|   | fa7c8cfe5b | ||
|   | 08a59591ae | ||
|   | a09a244242 | ||
|   | 483a61d90d | ||
|   | 385fb7586b | ||
|   | 2488d40421 | ||
|   | bf7875bfaa | ||
|   | a84fa30774 | ||
|   | c19a763b3d | ||
|   | 0875460974 | ||
|   | f0b08d3936 | ||
|   | 16520c7b4c | ||
|   | 65549d06d9 | ||
| ![greenkeeper[bot]](/assets/img/avatar_default.png)  | ce2d2a10c1 | ||
|   | 0ebe801af4 | ||
|   | 05daa7ac7d | ||
|   | 990a583e5e | ||
|   | d9b02a18bf | ||
|   | 30aae79e5c | ||
|   | a149c121fb | ||
|   | 47f4b51207 | ||
|   | 79f4d886d0 | ||
|   | 2b556aba03 | ||
|   | 739c993911 | ||
|   | cb180e00de | ||
|   | 4d46a61051 | ||
|   | 81969ea8b2 | ||
|   | ac474f3884 | ||
|   | a39aaf6eb1 | ||
|   | 68a7661f08 | ||
|   | ffcb2f755c | ||
| ![greenkeeper[bot]](/assets/img/avatar_default.png)  | 366e0d6bde | ||
|   | b3a3238e43 | ||
|   | 92828028db | ||
|   | edb61e52c5 | ||
|   | ded297b04c | ||
|   | 65e1d5978a | ||
|   | 86e76358b1 | ||
|   | 435e0257a4 | ||
|   | 1c45cc808b | ||
|   | ed27a2f963 | ||
|   | 1f53d1a149 | ||
|   | 8295f6d7a3 | ||
|   | b02f6341c9 | ||
|   | f6d577d411 | ||
|   | db3e73318e | ||
|   | 29c2071711 | ||
|   | 3acd524d09 | ||
|   | dae65cc123 | ||
|   | 695f154d87 | ||
|   | 59dca9a812 | ||
|   | b9f04f8f53 | ||
|   | 0985f14f18 | ||
|   | 56684dd7c3 | ||
|   | 47a5f3bc67 | ||
|   | 1c1e3009e9 | ||
|   | c7c3f6999b | ||
|   | fb4aa9bc1c | ||
|   | ef57f5907b | ||
|   | eff44f9cd1 | ||
|   | d7fa92d58f | ||
|   | 4e8033d5a4 | ||
|   | 8a207d8311 | ||
|   | 94ba9c8437 | ||
|   | 88c71c2998 | ||
|   | 7b6e55047f | ||
|   | 9d85d0bb08 | ||
|   | 27ac0bbc00 | ||
|   | affde9b4e2 | ||
|   | 88324b6dd9 | ||
|   | 9b95ffe6c6 | ||
|   | 6b137f8d69 | ||
|   | e78b2b0ab8 | ||
|   | 8b51428347 | ||
|   | b8d53a7b40 | ||
|   | b0d9e9caa2 | ||
|   | 1fc4ec8dc1 | ||
|   | a1e1e25800 | ||
|   | 3b020732ec | ||
|   | dcf92945fe | ||
|   | f9f33903d4 | ||
|   | d8bf06ab0f | ||
|   | 806dabe58b | ||
|   | a17b8c56d7 | ||
|   | 0eaaaba8f2 | ||
|   | f359d79d36 | ||
|   | 1d84000d94 | ||
|   | b70e22c150 | ||
|   | 0774ffe376 | ||
|   | 0096d7d8ac | ||
|   | 38db966b3d | ||
|   | cb7d313a66 | ||
|   | 91b7905f3f | ||
|   | 9a81fba992 | ||
|   | 43553d5c09 | ||
|   | e0ca8ce173 | ||
|   | 13624ea7c2 | ||
|   | 9502586c8b | ||
| ![greenkeeper[bot]](/assets/img/avatar_default.png)  | d6753f2cf2 | ||
|   | 5ba36efcd2 | ||
|   | fd497ef105 | ||
|   | 9c4a7bf94c | ||
|   | 91f8adc138 | ||
|   | 69fa2373cb | ||
|   | 8b37fc4772 | ||
|   | 81e4ed9591 | ||
|   | 9cda89ec04 | ||
|   | fc180f030f | ||
|   | a827b6028d | ||
|   | 4517bf7342 | ||
|   | b21287262e | ||
|   | a60ae130c1 | ||
|   | dd88acd411 | ||
|   | b63fc1a9e5 | ||
|   | 17f143cfb2 | ||
|   | cf57d847d1 | ||
|   | 7c4c7bea14 | ||
|   | e5ec47fc75 | ||
|   | ad91dc2423 | ||
|   | 7acbe53948 | ||
|   | fe1b8ba0cb | ||
|   | b0a8a51b69 | ||
|   | c3ca21e610 | ||
|   | c1b47a2119 | ||
|   | caf625afee | ||
|   | 2bad3865a3 | ||
|   | 3f7d248684 | ||
|   | 4e7382b793 | ||
|   | 552ff4a044 | ||
|   | afb52a0cd5 | ||
|   | 1ac89b0f5b | ||
|   | 5039ca7ee1 | ||
|   | a48fd9ce18 | ||
|   | 58859c4811 | ||
|   | 5988fb3111 | ||
|   | 8dce821789 | ||
|   | 16fde0b507 | ||
|   | 9723662706 | ||
|   | df4415b4fe | ||
|   | 6e179e7cde | ||
|   | 87f248b8ec | ||
|   | 027140eccc | ||
|   | 92cf205c66 | ||
|   | fa3299840f | ||
|   | f54529d46f | ||
|   | b772add064 | ||
|   | 231f2e77a4 | ||
|   | a000a9e607 | ||
|   | e8da15ab1e | ||
|   | e070ccb313 | ||
|   | 2b06579228 | ||
|   | 853c847ba1 | ||
|   | e852680b0a | ||
|   | 0b0ee915b3 | ||
|   | 516d1d093f | ||
|   | aee9c79c0f | ||
|   | 2d4b183c14 | ||
|   | 03b20e11ca | ||
|   | e0e006e284 | ||
|   | a294a881ec | ||
|   | 53926082e7 | ||
|   | 2d185becc3 | ||
|   | 67fff324b0 | ||
|   | f64b7fcabc | ||
|   | 4cefa16db6 | ||
|   | 22722379df | ||
|   | b2b83dc45d | ||
|   | ebf6f8bbfd | ||
|   | 3e1631d180 | ||
|   | 97cb3c8613 | ||
|   | 6dda3a5d8a | ||
|   | e50b0540f5 | ||
|   | 63b4aee9bd | ||
|   | 5b5de6a89c | ||
|   | a11c991f83 | ||
|   | a0adcf0d1a | ||
|   | 93b2b82993 | ||
|   | 4dee7d91b1 | ||
|   | 839be6477d | ||
|   | 59e2ed8ab0 | ||
|   | 83790004dd | ||
| ![greenkeeper[bot]](/assets/img/avatar_default.png)  | b70e9824ac | ||
| ![greenkeeper[bot]](/assets/img/avatar_default.png)  | 155d49e8ac | ||
| ![greenkeeper[bot]](/assets/img/avatar_default.png)  | 4ae10ab33d | ||
|   | 7124586eb1 | ||
|   | 74f6ed1851 | ||
|   | e59f13e8ff | ||
|   | 31ed8949b9 | ||
|   | f1174a15e0 | ||
|   | 912ffae600 | ||
|   | 71a5662195 | ||
|   | 774834a31f | ||
|   | 1d9c88e9a1 | ||
|   | 8eb8243153 | ||
|   | b4967b862c | ||
|   | aee3517736 | ||
|   | 52ff8e84fa | ||
|   | 9bb6db649c | ||
|   | da99be9897 | ||
|   | 2d7ec8a471 | ||
|   | 4cbbfdad1a | ||
|   | 2924858311 | ||
|   | 85916bfea1 | ||
|   | 38ccd9e794 | ||
|   | c64b6be915 | 
| @@ -138,3 +138,6 @@ drive: | ||||
|  | ||||
| # Clustering | ||||
| # clusterLimit: 1 | ||||
|  | ||||
| # Summaly proxy | ||||
| # summalyProxy: "http://example.com" | ||||
|   | ||||
							
								
								
									
										10
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -5,6 +5,16 @@ ChangeLog | ||||
|  | ||||
| This document describes breaking changes only. | ||||
|  | ||||
| 8.0.0 | ||||
| ----- | ||||
|  | ||||
| ### Migration | ||||
|  | ||||
| 起動する前に、`node cli/migration/8.0.0`してください。 | ||||
|  | ||||
| Please run `node cli/migration/8.0.0` before launch. | ||||
|  | ||||
|  | ||||
| 7.0.0 | ||||
| ----- | ||||
|  | ||||
|   | ||||
| @@ -1,40 +0,0 @@ | ||||
| const { default: User, deleteUser } = require('../built/models/user'); | ||||
| const { default: zip } = require('@prezzemolo/zip') | ||||
|  | ||||
| const migrate = async (user) => { | ||||
| 	try { | ||||
| 		await deleteUser(user._id); | ||||
| 		return true; | ||||
| 	} catch (e) { | ||||
| 		return false; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| async function main() { | ||||
| 	const count = await User.count({ | ||||
| 		uri: /#/ | ||||
| 	}); | ||||
|  | ||||
| 	const dop = 1 | ||||
| 	const idop = ((count - (count % dop)) / dop) + 1 | ||||
|  | ||||
| 	return zip( | ||||
| 		1, | ||||
| 		async (time) => { | ||||
| 			console.log(`${time} / ${idop}`) | ||||
| 			const doc = await User.find({ | ||||
| 				uri: /#/ | ||||
| 			}, { | ||||
| 				limit: dop, skip: time * dop | ||||
| 			}) | ||||
| 			return Promise.all(doc.map(migrate)) | ||||
| 		}, | ||||
| 		idop | ||||
| 	).then(a => { | ||||
| 		const rv = [] | ||||
| 		a.forEach(e => rv.push(...e)) | ||||
| 		return rv | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| main().then(console.dir).catch(console.error) | ||||
| @@ -58,7 +58,7 @@ common: | ||||
|     friday: "金曜日" | ||||
|     saturday: "土曜日" | ||||
|   reactions: | ||||
|     like: "ええやん" | ||||
|     like: "いいね" | ||||
|     love: "しゅき" | ||||
|     laugh: "笑" | ||||
|     hmm: "ふぅ~む" | ||||
| @@ -84,6 +84,7 @@ common: | ||||
|   my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。" | ||||
|   i-like-sushi: "私は(プリンよりむしろ)寿司が好き" | ||||
|   show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示" | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "公式アカウント" | ||||
|   disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" | ||||
|   reversi: | ||||
| @@ -423,6 +424,24 @@ desktop/views/components/calendar.vue: | ||||
|   prev: "前の月" | ||||
|   next: "次の月" | ||||
|   go: "クリックして時間遡行" | ||||
| desktop/views/components/charts.vue: | ||||
|   title: "チャート" | ||||
|   per-day: "1日ごと" | ||||
|   per-hour: "1時間ごと" | ||||
|   notes: "投稿" | ||||
|   users: "ユーザー" | ||||
|   drive: "ドライブ" | ||||
|   charts: | ||||
|     notes: "投稿の増減 (統合)" | ||||
|     local-notes: "投稿の増減 (ローカル)" | ||||
|     remote-notes: "投稿の増減 (リモート)" | ||||
|     notes-total: "投稿の累計" | ||||
|     users: "ユーザーの増減" | ||||
|     users-total: "ユーザーの累計" | ||||
|     drive: "ドライブ使用量の増減" | ||||
|     drive-total: "ドライブ使用量の累計" | ||||
|     drive-files: "ドライブのファイル数の増減" | ||||
|     drive-files-total: "ドライブのファイル数の累計" | ||||
| desktop/views/components/choose-file-from-drive-window.vue: | ||||
|   choose-file: "ファイル選択中" | ||||
|   upload: "PCからドライブにファイルをアップロード" | ||||
| @@ -754,6 +773,7 @@ desktop/views/components/ui.header.account.vue: | ||||
|   lists: "リスト" | ||||
|   follow-requests: "フォロー申請" | ||||
|   customize: "ホームのカスタマイズ" | ||||
|   admin: "管理" | ||||
|   settings: "設定" | ||||
|   signout: "サインアウト" | ||||
|   dark: "闇に飲まれる" | ||||
| @@ -818,18 +838,6 @@ desktop/views/pages/admin/admin.unverify-user.vue: | ||||
|   unverify-user: "ユーザーの公式アカウント解除" | ||||
|   unverify: "公式アカウントを解除する" | ||||
|   unverified: "公式アカウントを解除しました" | ||||
| desktop/views/pages/admin/admin.notes-chart.vue: | ||||
|   title: "投稿" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.users-chart.vue: | ||||
|   title: "ユーザー" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.drive-chart.vue: | ||||
|   title: "ドライブ" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/deck/deck.tl-column.vue: | ||||
|   is-media-only: "メディア投稿のみ" | ||||
|   is-media-view: "メディアビュー" | ||||
| @@ -1044,6 +1052,7 @@ mobile/views/components/ui.nav.vue: | ||||
|   game: "ゲーム" | ||||
|   darkmode: "ダークモード" | ||||
|   settings: "設定" | ||||
|   admin: "管理" | ||||
|   about: "Misskeyについて" | ||||
| mobile/views/components/user-timeline.vue: | ||||
|   no-notes: "このユーザーは投稿していないようです。" | ||||
|   | ||||
| @@ -58,7 +58,7 @@ common: | ||||
|     friday: "金曜日" | ||||
|     saturday: "土曜日" | ||||
|   reactions: | ||||
|     like: "ええやん" | ||||
|     like: "いいね" | ||||
|     love: "Lieben" | ||||
|     laugh: "Lachen" | ||||
|     hmm: "Hmm...?" | ||||
| @@ -84,6 +84,7 @@ common: | ||||
|   my-token-regenerated: "Dein Token wurde generiert. Du wirst jetzt abgemeldet." | ||||
|   i-like-sushi: "私は(プリンよりむしろ)寿司が好き" | ||||
|   show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示" | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "公式アカウント" | ||||
|   disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" | ||||
|   reversi: | ||||
| @@ -423,6 +424,24 @@ desktop/views/components/calendar.vue: | ||||
|   prev: "Vorheriger Monat" | ||||
|   next: "Nächster Monat" | ||||
|   go: "Klicke zur Navigation" | ||||
| desktop/views/components/charts.vue: | ||||
|   title: "チャート" | ||||
|   per-day: "1日ごと" | ||||
|   per-hour: "1時間ごと" | ||||
|   notes: "投稿" | ||||
|   users: "ユーザー" | ||||
|   drive: "ドライブ" | ||||
|   charts: | ||||
|     notes: "投稿の増減 (統合)" | ||||
|     local-notes: "投稿の増減 (ローカル)" | ||||
|     remote-notes: "投稿の増減 (リモート)" | ||||
|     notes-total: "投稿の累計" | ||||
|     users: "ユーザーの増減" | ||||
|     users-total: "ユーザーの累計" | ||||
|     drive: "ドライブ使用量の増減" | ||||
|     drive-total: "ドライブ使用量の累計" | ||||
|     drive-files: "ドライブのファイル数の増減" | ||||
|     drive-files-total: "ドライブのファイル数の累計" | ||||
| desktop/views/components/choose-file-from-drive-window.vue: | ||||
|   choose-file: "Datei auswählen" | ||||
|   upload: "Dateien von deinem PC hochladen" | ||||
| @@ -754,6 +773,7 @@ desktop/views/components/ui.header.account.vue: | ||||
|   lists: "Listen" | ||||
|   follow-requests: "フォロー申請" | ||||
|   customize: "ホームのカスタマイズ" | ||||
|   admin: "管理" | ||||
|   settings: "Einstellungen" | ||||
|   signout: "Ausloggen" | ||||
|   dark: "Verdunkeln" | ||||
| @@ -818,18 +838,6 @@ desktop/views/pages/admin/admin.unverify-user.vue: | ||||
|   unverify-user: "ユーザーの公式アカウント解除" | ||||
|   unverify: "公式アカウントを解除する" | ||||
|   unverified: "公式アカウントを解除しました" | ||||
| desktop/views/pages/admin/admin.notes-chart.vue: | ||||
|   title: "投稿" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.users-chart.vue: | ||||
|   title: "ユーザー" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.drive-chart.vue: | ||||
|   title: "ドライブ" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/deck/deck.tl-column.vue: | ||||
|   is-media-only: "メディア投稿のみ" | ||||
|   is-media-view: "メディアビュー" | ||||
| @@ -1044,6 +1052,7 @@ mobile/views/components/ui.nav.vue: | ||||
|   game: "ゲーム" | ||||
|   darkmode: "ダークモード" | ||||
|   settings: "設定" | ||||
|   admin: "管理" | ||||
|   about: "Misskeyについて" | ||||
| mobile/views/components/user-timeline.vue: | ||||
|   no-notes: "このユーザーは投稿していないようです。" | ||||
|   | ||||
| @@ -84,6 +84,7 @@ common: | ||||
|   my-token-regenerated: "Your token has been regenerated, so you will be signed out." | ||||
|   i-like-sushi: "I prefer sushi rather than pudding" | ||||
|   show-reversi-board-labels: "Show row and column labels in Reversi" | ||||
|   use-contrast-reversi-stones: "Make the stone color clear" | ||||
|   verified-user: "Verified account" | ||||
|   disable-animated-mfm: "Disable animated texts in a post" | ||||
|   reversi: | ||||
| @@ -423,6 +424,24 @@ desktop/views/components/calendar.vue: | ||||
|   prev: "Previous month" | ||||
|   next: "Next month" | ||||
|   go: "Click to navigate" | ||||
| desktop/views/components/charts.vue: | ||||
|   title: "Charts" | ||||
|   per-day: "per Day" | ||||
|   per-hour: "per Hour" | ||||
|   notes: "Posts" | ||||
|   users: "Users" | ||||
|   drive: "Drive" | ||||
|   charts: | ||||
|     notes: "The number of posts: increase/decrease (Combined)" | ||||
|     local-notes: "The number of posts: increase/decrease (Local)" | ||||
|     remote-notes: "The number of posts: increase/decrease (Remote)" | ||||
|     notes-total: "The number of posts: cumulative total" | ||||
|     users: "The number of users: increase/decrease" | ||||
|     users-total: "The number of users: cumulative total" | ||||
|     drive: "Capacity used as the storage: increase/decrease" | ||||
|     drive-total: "Capacity used as the storage: cumulative total" | ||||
|     drive-files: "The number of files on the storage: increase/decrease" | ||||
|     drive-files-total: "The number of files on the storage: cumulative total" | ||||
| desktop/views/components/choose-file-from-drive-window.vue: | ||||
|   choose-file: "Choose files" | ||||
|   upload: "Upload files from your device" | ||||
| @@ -754,6 +773,7 @@ desktop/views/components/ui.header.account.vue: | ||||
|   lists: "Lists" | ||||
|   follow-requests: "Follow requests" | ||||
|   customize: "Customize home layout" | ||||
|   admin: "Admin" | ||||
|   settings: "Settings" | ||||
|   signout: "Sign out" | ||||
|   dark: "Submerge in dark" | ||||
| @@ -818,18 +838,6 @@ desktop/views/pages/admin/admin.unverify-user.vue: | ||||
|   unverify-user: "User account unverification settings" | ||||
|   unverify: "Unverify account" | ||||
|   unverified: "The account is now being unverified" | ||||
| desktop/views/pages/admin/admin.notes-chart.vue: | ||||
|   title: "Posts" | ||||
|   local: "Local" | ||||
|   remote: "Remote" | ||||
| desktop/views/pages/admin/admin.users-chart.vue: | ||||
|   title: "Users" | ||||
|   local: "Local" | ||||
|   remote: "Remote" | ||||
| desktop/views/pages/admin/admin.drive-chart.vue: | ||||
|   title: "Drive" | ||||
|   local: "Local" | ||||
|   remote: "Remote" | ||||
| desktop/views/pages/deck/deck.tl-column.vue: | ||||
|   is-media-only: "Only media posts" | ||||
|   is-media-view: "Media view" | ||||
| @@ -1044,6 +1052,7 @@ mobile/views/components/ui.nav.vue: | ||||
|   game: "Games" | ||||
|   darkmode: "Dark theme" | ||||
|   settings: "Settings" | ||||
|   admin: "Admin" | ||||
|   about: "About Misskey" | ||||
| mobile/views/components/user-timeline.vue: | ||||
|   no-notes: "It seems this user hasn't posted anything yet." | ||||
|   | ||||
| @@ -58,7 +58,7 @@ common: | ||||
|     friday: "Viernes" | ||||
|     saturday: "Sábado" | ||||
|   reactions: | ||||
|     like: "ええやん" | ||||
|     like: "いいね" | ||||
|     love: "amor" | ||||
|     laugh: "risa" | ||||
|     hmm: "hmm" | ||||
| @@ -84,6 +84,7 @@ common: | ||||
|   my-token-regenerated: "Tu token se ha regenerado vas a ser desconectado." | ||||
|   i-like-sushi: "Prefiero sushi a pudín" | ||||
|   show-reversi-board-labels: "Mostrar etiquetas de filas y columnas en Reversi" | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "公式アカウント" | ||||
|   disable-animated-mfm: "Desactivar texto animado en una publicación" | ||||
|   reversi: | ||||
| @@ -423,6 +424,24 @@ desktop/views/components/calendar.vue: | ||||
|   prev: "Mes anterior" | ||||
|   next: "Próximo mes" | ||||
|   go: "Click para navegar" | ||||
| desktop/views/components/charts.vue: | ||||
|   title: "チャート" | ||||
|   per-day: "1日ごと" | ||||
|   per-hour: "1時間ごと" | ||||
|   notes: "投稿" | ||||
|   users: "ユーザー" | ||||
|   drive: "ドライブ" | ||||
|   charts: | ||||
|     notes: "投稿の増減 (統合)" | ||||
|     local-notes: "投稿の増減 (ローカル)" | ||||
|     remote-notes: "投稿の増減 (リモート)" | ||||
|     notes-total: "投稿の累計" | ||||
|     users: "ユーザーの増減" | ||||
|     users-total: "ユーザーの累計" | ||||
|     drive: "ドライブ使用量の増減" | ||||
|     drive-total: "ドライブ使用量の累計" | ||||
|     drive-files: "ドライブのファイル数の増減" | ||||
|     drive-files-total: "ドライブのファイル数の累計" | ||||
| desktop/views/components/choose-file-from-drive-window.vue: | ||||
|   choose-file: "Escoger archivos" | ||||
|   upload: "Cargar archivos de tu dispositivo" | ||||
| @@ -754,6 +773,7 @@ desktop/views/components/ui.header.account.vue: | ||||
|   lists: "リスト" | ||||
|   follow-requests: "フォロー申請" | ||||
|   customize: "ホームのカスタマイズ" | ||||
|   admin: "管理" | ||||
|   settings: "設定" | ||||
|   signout: "サインアウト" | ||||
|   dark: "闇に飲まれる" | ||||
| @@ -818,18 +838,6 @@ desktop/views/pages/admin/admin.unverify-user.vue: | ||||
|   unverify-user: "ユーザーの公式アカウント解除" | ||||
|   unverify: "公式アカウントを解除する" | ||||
|   unverified: "公式アカウントを解除しました" | ||||
| desktop/views/pages/admin/admin.notes-chart.vue: | ||||
|   title: "投稿" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.users-chart.vue: | ||||
|   title: "ユーザー" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.drive-chart.vue: | ||||
|   title: "ドライブ" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/deck/deck.tl-column.vue: | ||||
|   is-media-only: "メディア投稿のみ" | ||||
|   is-media-view: "メディアビュー" | ||||
| @@ -1044,6 +1052,7 @@ mobile/views/components/ui.nav.vue: | ||||
|   game: "ゲーム" | ||||
|   darkmode: "ダークモード" | ||||
|   settings: "設定" | ||||
|   admin: "管理" | ||||
|   about: "Misskeyについて" | ||||
| mobile/views/components/user-timeline.vue: | ||||
|   no-notes: "このユーザーは投稿していないようです。" | ||||
|   | ||||
| @@ -58,10 +58,10 @@ common: | ||||
|     friday: "Vendredi" | ||||
|     saturday: "Samedi" | ||||
|   reactions: | ||||
|     like: "ええやん" | ||||
|     like: "J'aime" | ||||
|     love: "Adore" | ||||
|     laugh: "Rire" | ||||
|     hmm: "Hmm ... ?" | ||||
|     hmm: "Hmm … ?" | ||||
|     surprise: "Wow" | ||||
|     congrats: "Félicitations !" | ||||
|     angry: "En colère" | ||||
| @@ -69,10 +69,10 @@ common: | ||||
|     rip: "RIP" | ||||
|     pudding: "Pudding" | ||||
|   note-placeholders: | ||||
|     a: "Que faites vous maintenant ?" | ||||
|     a: "Que faites-vous maintenant ?" | ||||
|     b: "Quoi de neuf ?" | ||||
|     c: "Qu'avez-vous en tête ?" | ||||
|     d: "Voulez-vous exprimer quelque chose ?" | ||||
|     d: "Désirez-vous publier quelques mots ?" | ||||
|     e: "Écrivez ici" | ||||
|     f: "En attente de vos écrits" | ||||
|   search: "Recherche" | ||||
| @@ -84,6 +84,7 @@ common: | ||||
|   my-token-regenerated: "Votre token vient d'être généré, vous allez maintenant être déconnecté." | ||||
|   i-like-sushi: "Je préfère les sushis plutôt que le pudding" | ||||
|   show-reversi-board-labels: "Afficher les étiquettes des lignes et colonnes dans Reversi" | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "Compte vérifié" | ||||
|   disable-animated-mfm: "Désactiver les textes animés dans les publications" | ||||
|   reversi: | ||||
| @@ -230,7 +231,7 @@ common/views/components/connect-failed.troubleshooter.vue: | ||||
|   flush: "Vider le cache" | ||||
|   set-version: "Choisissez une version" | ||||
| common/views/components/messaging.vue: | ||||
|   search-user: "Trouver un·e utilisateur·rice" | ||||
|   search-user: "Trouver un·e utilisateur·trice" | ||||
|   you: "Vous" | ||||
|   no-history: "Pas d'historique" | ||||
| common/views/components/messaging-room.vue: | ||||
| @@ -378,7 +379,7 @@ common/views/widgets/tips.vue: | ||||
|   tips-line3: "Vous pouvez glisser et déposer des fichiers sur la fenêtre de la note" | ||||
|   tips-line4: "Vous pouvez coller des images à partir du presse-papier sur la fenêtre de la note" | ||||
|   tips-line5: "Vous pouvez téléverser des fichiers sur le Drive en faisant un glisser-déposer" | ||||
|   tips-line6: "ドライブでファイルをドラッグしてフォルダ移動できます" | ||||
|   tips-line6: "Vous pouvez déplacer un dossier en le glissant dans le Drive" | ||||
|   tips-line7: "ドライブでフォルダをドラッグしてフォルダ移動できます" | ||||
|   tips-line8: "Vous pouvez personnaliser l'Accueil via les paramètres" | ||||
|   tips-line9: "Misskey est sous licence AGPLv3" | ||||
| @@ -423,6 +424,24 @@ desktop/views/components/calendar.vue: | ||||
|   prev: "Mois dernier" | ||||
|   next: "Mois prochain" | ||||
|   go: "Cliquez pour naviguer" | ||||
| desktop/views/components/charts.vue: | ||||
|   title: "チャート" | ||||
|   per-day: "1日ごと" | ||||
|   per-hour: "1時間ごと" | ||||
|   notes: "投稿" | ||||
|   users: "ユーザー" | ||||
|   drive: "ドライブ" | ||||
|   charts: | ||||
|     notes: "投稿の増減 (統合)" | ||||
|     local-notes: "投稿の増減 (ローカル)" | ||||
|     remote-notes: "投稿の増減 (リモート)" | ||||
|     notes-total: "投稿の累計" | ||||
|     users: "ユーザーの増減" | ||||
|     users-total: "ユーザーの累計" | ||||
|     drive: "ドライブ使用量の増減" | ||||
|     drive-total: "ドライブ使用量の累計" | ||||
|     drive-files: "ドライブのファイル数の増減" | ||||
|     drive-files-total: "ドライブのファイル数の累計" | ||||
| desktop/views/components/choose-file-from-drive-window.vue: | ||||
|   choose-file: "Sélection de fichiers" | ||||
|   upload: "Téléverser des fichiers à partir de votre ordinateur" | ||||
| @@ -512,7 +531,7 @@ desktop/views/components/following.vue: | ||||
|   empty: "Vous ne suivez aucun compte." | ||||
| desktop/views/components/friends-maker.vue: | ||||
|   title: "Utilisateurs recommandés :" | ||||
|   empty: "Impossible de trouver des utilisateurs à recommander." | ||||
|   empty: "Impossible de trouver des utilisateurs·trices à recommander." | ||||
|   fetching: "Chargement" | ||||
|   refresh: "Plus" | ||||
|   close: "Fermer" | ||||
| @@ -629,7 +648,7 @@ desktop/views/components/settings.vue: | ||||
|   circle-icons: "Utiliser des icônes circulaires" | ||||
|   gradient-window-header: "Utiliser les dégradés sur la barre de titre de la fenêtre" | ||||
|   post-form-on-timeline: "タイムライン上部に投稿フォームを表示する" | ||||
|   suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する" | ||||
|   suggest-recent-hashtags: "Afficher les hashtags populaires dans le champs de saisie" | ||||
|   show-reply-target: "Afficher les réponses" | ||||
|   show-my-renotes: "Afficher mes republications dans le fil" | ||||
|   show-renoted-my-notes: "自分の投稿のRenoteをタイムラインに表示する" | ||||
| @@ -754,6 +773,7 @@ desktop/views/components/ui.header.account.vue: | ||||
|   lists: "Listes" | ||||
|   follow-requests: "Demandes de suivi" | ||||
|   customize: "Personnaliser l'Accueil" | ||||
|   admin: "Admin" | ||||
|   settings: "Réglages" | ||||
|   signout: "Déconnexion" | ||||
|   dark: "Fall in dark" | ||||
| @@ -791,12 +811,12 @@ desktop/views/components/window.vue: | ||||
|   popout: "ポップアウト" | ||||
|   close: "Fermer" | ||||
| desktop/views/pages/admin/admin.vue: | ||||
|   dashboard: "ダッシュボード" | ||||
|   dashboard: "Tableau de bord" | ||||
|   drive: "Drive" | ||||
|   users: "Utilisateur·rice·s" | ||||
|   update: "Mises à jour" | ||||
| desktop/views/pages/admin/admin.dashboard.vue: | ||||
|   dashboard: "ダッシュボード" | ||||
|   dashboard: "Tableau de bord" | ||||
|   all-users: "Tou·te·s les utilisateur·rice·s" | ||||
|   original-users: "Utilisateur·rice·s sur cette instance" | ||||
|   all-notes: "Toutes les publications" | ||||
| @@ -811,25 +831,13 @@ desktop/views/pages/admin/admin.unsuspend-user.vue: | ||||
|   unsuspend: "Suspension levée" | ||||
|   unsuspended: "La suspension de l’utilisateur·rice a été levée avec succès" | ||||
| desktop/views/pages/admin/admin.verify-user.vue: | ||||
|   verify-user: "ユーザーの公式アカウント設定" | ||||
|   verify-user: "Paramètres de vérification du compte utilisateur" | ||||
|   verify: "Vérification du compte" | ||||
|   verified: "Le compte a été vérifié" | ||||
| desktop/views/pages/admin/admin.unverify-user.vue: | ||||
|   unverify-user: "ユーザーの公式アカウント解除" | ||||
|   unverify: "公式アカウントを解除する" | ||||
|   unverify: "Ôter la vérification du compte" | ||||
|   unverified: "公式アカウントを解除しました" | ||||
| desktop/views/pages/admin/admin.notes-chart.vue: | ||||
|   title: "投稿" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.users-chart.vue: | ||||
|   title: "ユーザー" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.drive-chart.vue: | ||||
|   title: "ドライブ" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/deck/deck.tl-column.vue: | ||||
|   is-media-only: "Les publications médias uniquement" | ||||
|   is-media-view: "Vue média" | ||||
| @@ -977,7 +985,7 @@ mobile/views/components/follow-button.vue: | ||||
|   follow-request: "Demande d'abonnement" | ||||
| mobile/views/components/friends-maker.vue: | ||||
|   title: "Abonnez-vous aux utilisateurs" | ||||
|   empty: "Impossible de trouver des utilisateurs à recommander." | ||||
|   empty: "Impossible de trouver des utilisateurs·trices à recommander." | ||||
|   fetching: "Chargement" | ||||
|   refresh: "Voir plus" | ||||
|   close: "Fermer" | ||||
| @@ -1044,6 +1052,7 @@ mobile/views/components/ui.nav.vue: | ||||
|   game: "Jeux" | ||||
|   darkmode: "Mode nuit" | ||||
|   settings: "Réglages" | ||||
|   admin: "管理" | ||||
|   about: "À propose de Misskey" | ||||
| mobile/views/components/user-timeline.vue: | ||||
|   no-notes: "Cette utilisateur semble n'avoir rien poster pour le moment" | ||||
|   | ||||
| @@ -58,7 +58,7 @@ common: | ||||
|     friday: "金曜日" | ||||
|     saturday: "土曜日" | ||||
|   reactions: | ||||
|     like: "ええやん" | ||||
|     like: "いいね" | ||||
|     love: "しゅき" | ||||
|     laugh: "笑" | ||||
|     hmm: "ふぅ~む" | ||||
| @@ -84,6 +84,7 @@ common: | ||||
|   my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。" | ||||
|   i-like-sushi: "私は(プリンよりむしろ)寿司が好き" | ||||
|   show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示" | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "公式アカウント" | ||||
|   disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" | ||||
|   reversi: | ||||
| @@ -423,6 +424,24 @@ desktop/views/components/calendar.vue: | ||||
|   prev: "前の月" | ||||
|   next: "次の月" | ||||
|   go: "クリックして時間遡行" | ||||
| desktop/views/components/charts.vue: | ||||
|   title: "チャート" | ||||
|   per-day: "1日ごと" | ||||
|   per-hour: "1時間ごと" | ||||
|   notes: "投稿" | ||||
|   users: "ユーザー" | ||||
|   drive: "ドライブ" | ||||
|   charts: | ||||
|     notes: "投稿の増減 (統合)" | ||||
|     local-notes: "投稿の増減 (ローカル)" | ||||
|     remote-notes: "投稿の増減 (リモート)" | ||||
|     notes-total: "投稿の累計" | ||||
|     users: "ユーザーの増減" | ||||
|     users-total: "ユーザーの累計" | ||||
|     drive: "ドライブ使用量の増減" | ||||
|     drive-total: "ドライブ使用量の累計" | ||||
|     drive-files: "ドライブのファイル数の増減" | ||||
|     drive-files-total: "ドライブのファイル数の累計" | ||||
| desktop/views/components/choose-file-from-drive-window.vue: | ||||
|   choose-file: "ファイル選択中" | ||||
|   upload: "PCからドライブにファイルをアップロード" | ||||
| @@ -754,6 +773,7 @@ desktop/views/components/ui.header.account.vue: | ||||
|   lists: "リスト" | ||||
|   follow-requests: "フォロー申請" | ||||
|   customize: "ホームのカスタマイズ" | ||||
|   admin: "管理" | ||||
|   settings: "設定" | ||||
|   signout: "サインアウト" | ||||
|   dark: "闇に飲まれる" | ||||
| @@ -818,18 +838,6 @@ desktop/views/pages/admin/admin.unverify-user.vue: | ||||
|   unverify-user: "ユーザーの公式アカウント解除" | ||||
|   unverify: "公式アカウントを解除する" | ||||
|   unverified: "公式アカウントを解除しました" | ||||
| desktop/views/pages/admin/admin.notes-chart.vue: | ||||
|   title: "投稿" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.users-chart.vue: | ||||
|   title: "ユーザー" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.drive-chart.vue: | ||||
|   title: "ドライブ" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/deck/deck.tl-column.vue: | ||||
|   is-media-only: "メディア投稿のみ" | ||||
|   is-media-view: "メディアビュー" | ||||
| @@ -1044,6 +1052,7 @@ mobile/views/components/ui.nav.vue: | ||||
|   game: "ゲーム" | ||||
|   darkmode: "ダークモード" | ||||
|   settings: "設定" | ||||
|   admin: "管理" | ||||
|   about: "Misskeyについて" | ||||
| mobile/views/components/user-timeline.vue: | ||||
|   no-notes: "このユーザーは投稿していないようです。" | ||||
|   | ||||
| @@ -473,6 +473,25 @@ desktop/views/components/calendar.vue: | ||||
|   next: "次の月" | ||||
|   go: "クリックして時間遡行" | ||||
|  | ||||
| desktop/views/components/charts.vue: | ||||
|   title: "チャート" | ||||
|   per-day: "1日ごと" | ||||
|   per-hour: "1時間ごと" | ||||
|   notes: "投稿" | ||||
|   users: "ユーザー" | ||||
|   drive: "ドライブ" | ||||
|   charts: | ||||
|     notes: "投稿の増減 (統合)" | ||||
|     local-notes: "投稿の増減 (ローカル)" | ||||
|     remote-notes: "投稿の増減 (リモート)" | ||||
|     notes-total: "投稿の累計" | ||||
|     users: "ユーザーの増減" | ||||
|     users-total: "ユーザーの累計" | ||||
|     drive: "ドライブ使用量の増減" | ||||
|     drive-total: "ドライブ使用量の累計" | ||||
|     drive-files: "ドライブのファイル数の増減" | ||||
|     drive-files-total: "ドライブのファイル数の累計" | ||||
|  | ||||
| desktop/views/components/choose-file-from-drive-window.vue: | ||||
|   choose-file: "ファイル選択中" | ||||
|   upload: "PCからドライブにファイルをアップロード" | ||||
| @@ -713,6 +732,7 @@ desktop/views/components/settings.vue: | ||||
|   gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用" | ||||
|   post-form-on-timeline: "タイムライン上部に投稿フォームを表示する" | ||||
|   suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する" | ||||
|   show-clock-on-header: "右上に時計を表示する" | ||||
|   show-reply-target: "リプライ先を表示する" | ||||
|   show-my-renotes: "自分の行ったRenoteをタイムラインに表示する" | ||||
|   show-renoted-my-notes: "自分の投稿のRenoteをタイムラインに表示する" | ||||
| @@ -915,8 +935,8 @@ desktop/views/pages/admin/admin.dashboard.vue: | ||||
|   dashboard: "ダッシュボード" | ||||
|   all-users: "全てのユーザー" | ||||
|   original-users: "このインスタンスのユーザー" | ||||
|   all-notes: "全てのノート" | ||||
|   original-notes: "このインスタンスのノート" | ||||
|   all-notes: "全ての投稿" | ||||
|   original-notes: "このインスタンスの投稿" | ||||
|   invite: "招待" | ||||
|  | ||||
| desktop/views/pages/admin/admin.suspend-user.vue: | ||||
| @@ -939,21 +959,6 @@ desktop/views/pages/admin/admin.unverify-user.vue: | ||||
|   unverify: "公式アカウントを解除する" | ||||
|   unverified: "公式アカウントを解除しました" | ||||
|  | ||||
| desktop/views/pages/admin/admin.notes-chart.vue: | ||||
|   title: "投稿" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
|  | ||||
| desktop/views/pages/admin/admin.users-chart.vue: | ||||
|   title: "ユーザー" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
|  | ||||
| desktop/views/pages/admin/admin.drive-chart.vue: | ||||
|   title: "ドライブ" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
|  | ||||
| desktop/views/pages/deck/deck.tl-column.vue: | ||||
|   is-media-only: "メディア投稿のみ" | ||||
|   is-media-view: "メディアビュー" | ||||
| @@ -964,6 +969,12 @@ desktop/views/pages/deck/deck.note.vue: | ||||
|   private: "この投稿は非公開です" | ||||
|   deleted: "この投稿は削除されました" | ||||
|  | ||||
| desktop/views/pages/stats/stats.vue: | ||||
|   all-users: "全てのユーザー" | ||||
|   original-users: "このインスタンスのユーザー" | ||||
|   all-notes: "全ての投稿" | ||||
|   original-notes: "このインスタンスの投稿" | ||||
|  | ||||
| desktop/views/pages/welcome.vue: | ||||
|   about: "詳しく..." | ||||
|   gotit: "わかった" | ||||
|   | ||||
| @@ -84,6 +84,7 @@ common: | ||||
|   my-token-regenerated: "あんさんのトークンが更新されたらしいわ。すまんがとりあえずサインアウトすんで。" | ||||
|   i-like-sushi: "寿司(のほうがプリンよりむしろ)ウマい、タコ焼きはあらへんけど。" | ||||
|   show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示や!" | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストをつけんで!" | ||||
|   verified-user: "アメちゃん付きアカウント" | ||||
|   disable-animated-mfm: "投稿内のちょろちょろ動いてんのを止める" | ||||
|   reversi: | ||||
| @@ -190,17 +191,17 @@ common/views/components/games/reversi/reversi.index.vue: | ||||
| common/views/components/games/reversi/reversi.room.vue: | ||||
|   settings-of-the-game: "ゲームの設定" | ||||
|   choose-map: "マップを選択" | ||||
|   random: "ランダム" | ||||
|   random: "いんじゃんほい" | ||||
|   black-or-white: "先手/後手" | ||||
|   black-is: "{}が黒" | ||||
|   black-is: "{}が黒や" | ||||
|   rules: "ルール" | ||||
|   is-llotheo: "石の少ない方が勝ち(ロセオ)" | ||||
|   is-llotheo: "石の少ない方が勝ちや!(ロセオ)" | ||||
|   looped-map: "ループマップ" | ||||
|   can-put-everywhere: "どこでも置けるモード" | ||||
|   can-put-everywhere: "どこに置いてもええモード" | ||||
|   settings-of-the-bot: "Botの設定" | ||||
|   this-game-is-started-soon: "ゲームは数秒後に開始されます" | ||||
|   waiting-for-other: "相手の準備が完了するのを待っています" | ||||
|   waiting-for-me: "あなたの準備が完了するのを待っています" | ||||
|   this-game-is-started-soon: "ゲームは数秒後に開始されんで" | ||||
|   waiting-for-other: "相手の準備が完了すんのを待ってんで" | ||||
|   waiting-for-me: "あんさんの準備が完了すんのを待ってんで" | ||||
|   waiting-for-both: "準備中" | ||||
|   cancel: "やめとくわ" | ||||
|   ready: "準備完了" | ||||
| @@ -423,6 +424,24 @@ desktop/views/components/calendar.vue: | ||||
|   prev: "前の月" | ||||
|   next: "次の月" | ||||
|   go: "クリックして時間遡行" | ||||
| desktop/views/components/charts.vue: | ||||
|   title: "チャート" | ||||
|   per-day: "1日ごと" | ||||
|   per-hour: "1時間ごと" | ||||
|   notes: "投稿" | ||||
|   users: "ユーザー" | ||||
|   drive: "ドライブ" | ||||
|   charts: | ||||
|     notes: "投稿の増減 (統合)" | ||||
|     local-notes: "投稿の増減 (ローカル)" | ||||
|     remote-notes: "投稿の増減 (リモート)" | ||||
|     notes-total: "投稿の累計" | ||||
|     users: "ユーザーの増減" | ||||
|     users-total: "ユーザーの累計" | ||||
|     drive: "ドライブ使用量の増減" | ||||
|     drive-total: "ドライブ使用量の累計" | ||||
|     drive-files: "ドライブのファイル数の増減" | ||||
|     drive-files-total: "ドライブのファイル数の累計" | ||||
| desktop/views/components/choose-file-from-drive-window.vue: | ||||
|   choose-file: "ファイル選択中" | ||||
|   upload: "PCからドライブにファイルをアップロード" | ||||
| @@ -754,6 +773,7 @@ desktop/views/components/ui.header.account.vue: | ||||
|   lists: "リスト" | ||||
|   follow-requests: "フォロー申請" | ||||
|   customize: "ホームのカスタマイズ" | ||||
|   admin: "管理" | ||||
|   settings: "設定" | ||||
|   signout: "サインアウト" | ||||
|   dark: "闇に飲まれる" | ||||
| @@ -818,18 +838,6 @@ desktop/views/pages/admin/admin.unverify-user.vue: | ||||
|   unverify-user: "ユーザーの公式アカウント解除" | ||||
|   unverify: "公式アカウントを解除する" | ||||
|   unverified: "公式アカウントを解除しました" | ||||
| desktop/views/pages/admin/admin.notes-chart.vue: | ||||
|   title: "投稿" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.users-chart.vue: | ||||
|   title: "ユーザー" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.drive-chart.vue: | ||||
|   title: "ドライブ" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/deck/deck.tl-column.vue: | ||||
|   is-media-only: "メディア投稿のみ" | ||||
|   is-media-view: "メディアビュー" | ||||
| @@ -1044,6 +1052,7 @@ mobile/views/components/ui.nav.vue: | ||||
|   game: "ゲーム" | ||||
|   darkmode: "ダークモード" | ||||
|   settings: "設定" | ||||
|   admin: "管理" | ||||
|   about: "Misskeyについて" | ||||
| mobile/views/components/user-timeline.vue: | ||||
|   no-notes: "このユーザーは投稿していないようです。" | ||||
|   | ||||
| @@ -58,7 +58,7 @@ common: | ||||
|     friday: "금요일" | ||||
|     saturday: "토요일" | ||||
|   reactions: | ||||
|     like: "ええやん" | ||||
|     like: "いいね" | ||||
|     love: "좋아" | ||||
|     laugh: "크크" | ||||
|     hmm: "음..." | ||||
| @@ -84,6 +84,7 @@ common: | ||||
|   my-token-regenerated: "당신의 토큰이 업데이트되어 있기 때문에 로그 아웃합니다." | ||||
|   i-like-sushi: "나는(푸딩보다 오히려)스시가 좋아" | ||||
|   show-reversi-board-labels: "리버시 보드의 행과 열 레이블을 표시" | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "公式アカウント" | ||||
|   disable-animated-mfm: "게시물의 문자 애니메이션을 비활성화 할" | ||||
|   reversi: | ||||
| @@ -423,6 +424,24 @@ desktop/views/components/calendar.vue: | ||||
|   prev: "前の月" | ||||
|   next: "次の月" | ||||
|   go: "クリックして時間遡行" | ||||
| desktop/views/components/charts.vue: | ||||
|   title: "チャート" | ||||
|   per-day: "1日ごと" | ||||
|   per-hour: "1時間ごと" | ||||
|   notes: "投稿" | ||||
|   users: "ユーザー" | ||||
|   drive: "ドライブ" | ||||
|   charts: | ||||
|     notes: "投稿の増減 (統合)" | ||||
|     local-notes: "投稿の増減 (ローカル)" | ||||
|     remote-notes: "投稿の増減 (リモート)" | ||||
|     notes-total: "投稿の累計" | ||||
|     users: "ユーザーの増減" | ||||
|     users-total: "ユーザーの累計" | ||||
|     drive: "ドライブ使用量の増減" | ||||
|     drive-total: "ドライブ使用量の累計" | ||||
|     drive-files: "ドライブのファイル数の増減" | ||||
|     drive-files-total: "ドライブのファイル数の累計" | ||||
| desktop/views/components/choose-file-from-drive-window.vue: | ||||
|   choose-file: "ファイル選択中" | ||||
|   upload: "PCからドライブにファイルをアップロード" | ||||
| @@ -754,6 +773,7 @@ desktop/views/components/ui.header.account.vue: | ||||
|   lists: "リスト" | ||||
|   follow-requests: "フォロー申請" | ||||
|   customize: "ホームのカスタマイズ" | ||||
|   admin: "管理" | ||||
|   settings: "設定" | ||||
|   signout: "サインアウト" | ||||
|   dark: "闇に飲まれる" | ||||
| @@ -818,18 +838,6 @@ desktop/views/pages/admin/admin.unverify-user.vue: | ||||
|   unverify-user: "ユーザーの公式アカウント解除" | ||||
|   unverify: "公式アカウントを解除する" | ||||
|   unverified: "公式アカウントを解除しました" | ||||
| desktop/views/pages/admin/admin.notes-chart.vue: | ||||
|   title: "投稿" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.users-chart.vue: | ||||
|   title: "ユーザー" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.drive-chart.vue: | ||||
|   title: "ドライブ" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/deck/deck.tl-column.vue: | ||||
|   is-media-only: "メディア投稿のみ" | ||||
|   is-media-view: "メディアビュー" | ||||
| @@ -1044,6 +1052,7 @@ mobile/views/components/ui.nav.vue: | ||||
|   game: "ゲーム" | ||||
|   darkmode: "ダークモード" | ||||
|   settings: "設定" | ||||
|   admin: "管理" | ||||
|   about: "Misskeyについて" | ||||
| mobile/views/components/user-timeline.vue: | ||||
|   no-notes: "このユーザーは投稿していないようです。" | ||||
|   | ||||
| @@ -58,7 +58,7 @@ common: | ||||
|     friday: "Piątek" | ||||
|     saturday: "Sobota" | ||||
|   reactions: | ||||
|     like: "ええやん" | ||||
|     like: "いいね" | ||||
|     love: "Kocham" | ||||
|     laugh: "Śmieszne" | ||||
|     hmm: "Hmm…?" | ||||
| @@ -84,6 +84,7 @@ common: | ||||
|   my-token-regenerated: "Twój token został wygenerowany. Zostaniesz wylogowany." | ||||
|   i-like-sushi: "Wolę sushi od puddingu" | ||||
|   show-reversi-board-labels: "Pokazuj podpisy wierszy i kolumn w Reversi" | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "公式アカウント" | ||||
|   disable-animated-mfm: "Wyłącz animowany tekst we wpisach" | ||||
|   reversi: | ||||
| @@ -423,6 +424,24 @@ desktop/views/components/calendar.vue: | ||||
|   prev: "Poprzedni miesiąc" | ||||
|   next: "Następny miesiąc" | ||||
|   go: "Naciśnij, aby przejść" | ||||
| desktop/views/components/charts.vue: | ||||
|   title: "チャート" | ||||
|   per-day: "1日ごと" | ||||
|   per-hour: "1時間ごと" | ||||
|   notes: "投稿" | ||||
|   users: "ユーザー" | ||||
|   drive: "ドライブ" | ||||
|   charts: | ||||
|     notes: "投稿の増減 (統合)" | ||||
|     local-notes: "投稿の増減 (ローカル)" | ||||
|     remote-notes: "投稿の増減 (リモート)" | ||||
|     notes-total: "投稿の累計" | ||||
|     users: "ユーザーの増減" | ||||
|     users-total: "ユーザーの累計" | ||||
|     drive: "ドライブ使用量の増減" | ||||
|     drive-total: "ドライブ使用量の累計" | ||||
|     drive-files: "ドライブのファイル数の増減" | ||||
|     drive-files-total: "ドライブのファイル数の累計" | ||||
| desktop/views/components/choose-file-from-drive-window.vue: | ||||
|   choose-file: "Wybierz plik" | ||||
|   upload: "Wyślij pliki z Twojego komputera" | ||||
| @@ -754,6 +773,7 @@ desktop/views/components/ui.header.account.vue: | ||||
|   lists: "Listy" | ||||
|   follow-requests: "Prośby o śledzenie" | ||||
|   customize: "Dostosuj stronę główną" | ||||
|   admin: "管理" | ||||
|   settings: "Ustawienia" | ||||
|   signout: "Wyloguj się" | ||||
|   dark: "Sprowadź ciemność" | ||||
| @@ -818,18 +838,6 @@ desktop/views/pages/admin/admin.unverify-user.vue: | ||||
|   unverify-user: "ユーザーの公式アカウント解除" | ||||
|   unverify: "公式アカウントを解除する" | ||||
|   unverified: "公式アカウントを解除しました" | ||||
| desktop/views/pages/admin/admin.notes-chart.vue: | ||||
|   title: "投稿" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.users-chart.vue: | ||||
|   title: "ユーザー" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.drive-chart.vue: | ||||
|   title: "ドライブ" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/deck/deck.tl-column.vue: | ||||
|   is-media-only: "Tylko wpisy z zawartością multimedialną" | ||||
|   is-media-view: "Widok multimediów" | ||||
| @@ -1044,6 +1052,7 @@ mobile/views/components/ui.nav.vue: | ||||
|   game: "Gry" | ||||
|   darkmode: "Tryb ciemny" | ||||
|   settings: "Ustawienia" | ||||
|   admin: "管理" | ||||
|   about: "O Misskey" | ||||
| mobile/views/components/user-timeline.vue: | ||||
|   no-notes: "Wygląda na to, że ten użytkownik nie opublikował jeszcze niczego" | ||||
|   | ||||
| @@ -58,7 +58,7 @@ common: | ||||
|     friday: "sexta" | ||||
|     saturday: "sábado" | ||||
|   reactions: | ||||
|     like: "Legal..." | ||||
|     like: "いいね" | ||||
|     love: "Amei" | ||||
|     laugh: "Riso" | ||||
|     hmm: "Hmm...?" | ||||
| @@ -84,6 +84,7 @@ common: | ||||
|   my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。" | ||||
|   i-like-sushi: "Eu prefiro sushi a pudim" | ||||
|   show-reversi-board-labels: "Mostrar etiquetas de colunas e linhas no Reversi" | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "Conta verificada" | ||||
|   disable-animated-mfm: "Desativar texto animado nas publicações" | ||||
|   reversi: | ||||
| @@ -423,6 +424,24 @@ desktop/views/components/calendar.vue: | ||||
|   prev: "前の月" | ||||
|   next: "次の月" | ||||
|   go: "クリックして時間遡行" | ||||
| desktop/views/components/charts.vue: | ||||
|   title: "チャート" | ||||
|   per-day: "1日ごと" | ||||
|   per-hour: "1時間ごと" | ||||
|   notes: "投稿" | ||||
|   users: "ユーザー" | ||||
|   drive: "ドライブ" | ||||
|   charts: | ||||
|     notes: "投稿の増減 (統合)" | ||||
|     local-notes: "投稿の増減 (ローカル)" | ||||
|     remote-notes: "投稿の増減 (リモート)" | ||||
|     notes-total: "投稿の累計" | ||||
|     users: "ユーザーの増減" | ||||
|     users-total: "ユーザーの累計" | ||||
|     drive: "ドライブ使用量の増減" | ||||
|     drive-total: "ドライブ使用量の累計" | ||||
|     drive-files: "ドライブのファイル数の増減" | ||||
|     drive-files-total: "ドライブのファイル数の累計" | ||||
| desktop/views/components/choose-file-from-drive-window.vue: | ||||
|   choose-file: "ファイル選択中" | ||||
|   upload: "PCからドライブにファイルをアップロード" | ||||
| @@ -754,6 +773,7 @@ desktop/views/components/ui.header.account.vue: | ||||
|   lists: "リスト" | ||||
|   follow-requests: "フォロー申請" | ||||
|   customize: "ホームのカスタマイズ" | ||||
|   admin: "管理" | ||||
|   settings: "設定" | ||||
|   signout: "サインアウト" | ||||
|   dark: "闇に飲まれる" | ||||
| @@ -818,18 +838,6 @@ desktop/views/pages/admin/admin.unverify-user.vue: | ||||
|   unverify-user: "ユーザーの公式アカウント解除" | ||||
|   unverify: "公式アカウントを解除する" | ||||
|   unverified: "公式アカウントを解除しました" | ||||
| desktop/views/pages/admin/admin.notes-chart.vue: | ||||
|   title: "投稿" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.users-chart.vue: | ||||
|   title: "ユーザー" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.drive-chart.vue: | ||||
|   title: "ドライブ" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/deck/deck.tl-column.vue: | ||||
|   is-media-only: "メディア投稿のみ" | ||||
|   is-media-view: "メディアビュー" | ||||
| @@ -1044,6 +1052,7 @@ mobile/views/components/ui.nav.vue: | ||||
|   game: "ゲーム" | ||||
|   darkmode: "ダークモード" | ||||
|   settings: "設定" | ||||
|   admin: "管理" | ||||
|   about: "Misskeyについて" | ||||
| mobile/views/components/user-timeline.vue: | ||||
|   no-notes: "このユーザーは投稿していないようです。" | ||||
|   | ||||
| @@ -58,7 +58,7 @@ common: | ||||
|     friday: "金曜日" | ||||
|     saturday: "土曜日" | ||||
|   reactions: | ||||
|     like: "ええやん" | ||||
|     like: "いいね" | ||||
|     love: "しゅき" | ||||
|     laugh: "笑" | ||||
|     hmm: "ふぅ~む" | ||||
| @@ -84,6 +84,7 @@ common: | ||||
|   my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。" | ||||
|   i-like-sushi: "私は(プリンよりむしろ)寿司が好き" | ||||
|   show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示" | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "公式アカウント" | ||||
|   disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" | ||||
|   reversi: | ||||
| @@ -423,6 +424,24 @@ desktop/views/components/calendar.vue: | ||||
|   prev: "前の月" | ||||
|   next: "次の月" | ||||
|   go: "クリックして時間遡行" | ||||
| desktop/views/components/charts.vue: | ||||
|   title: "チャート" | ||||
|   per-day: "1日ごと" | ||||
|   per-hour: "1時間ごと" | ||||
|   notes: "投稿" | ||||
|   users: "ユーザー" | ||||
|   drive: "ドライブ" | ||||
|   charts: | ||||
|     notes: "投稿の増減 (統合)" | ||||
|     local-notes: "投稿の増減 (ローカル)" | ||||
|     remote-notes: "投稿の増減 (リモート)" | ||||
|     notes-total: "投稿の累計" | ||||
|     users: "ユーザーの増減" | ||||
|     users-total: "ユーザーの累計" | ||||
|     drive: "ドライブ使用量の増減" | ||||
|     drive-total: "ドライブ使用量の累計" | ||||
|     drive-files: "ドライブのファイル数の増減" | ||||
|     drive-files-total: "ドライブのファイル数の累計" | ||||
| desktop/views/components/choose-file-from-drive-window.vue: | ||||
|   choose-file: "ファイル選択中" | ||||
|   upload: "PCからドライブにファイルをアップロード" | ||||
| @@ -754,6 +773,7 @@ desktop/views/components/ui.header.account.vue: | ||||
|   lists: "リスト" | ||||
|   follow-requests: "フォロー申請" | ||||
|   customize: "ホームのカスタマイズ" | ||||
|   admin: "管理" | ||||
|   settings: "設定" | ||||
|   signout: "サインアウト" | ||||
|   dark: "闇に飲まれる" | ||||
| @@ -818,18 +838,6 @@ desktop/views/pages/admin/admin.unverify-user.vue: | ||||
|   unverify-user: "ユーザーの公式アカウント解除" | ||||
|   unverify: "公式アカウントを解除する" | ||||
|   unverified: "公式アカウントを解除しました" | ||||
| desktop/views/pages/admin/admin.notes-chart.vue: | ||||
|   title: "投稿" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.users-chart.vue: | ||||
|   title: "ユーザー" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.drive-chart.vue: | ||||
|   title: "ドライブ" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/deck/deck.tl-column.vue: | ||||
|   is-media-only: "メディア投稿のみ" | ||||
|   is-media-view: "メディアビュー" | ||||
| @@ -1044,6 +1052,7 @@ mobile/views/components/ui.nav.vue: | ||||
|   game: "ゲーム" | ||||
|   darkmode: "ダークモード" | ||||
|   settings: "設定" | ||||
|   admin: "管理" | ||||
|   about: "Misskeyについて" | ||||
| mobile/views/components/user-timeline.vue: | ||||
|   no-notes: "このユーザーは投稿していないようです。" | ||||
|   | ||||
| @@ -58,7 +58,7 @@ common: | ||||
|     friday: "金曜日" | ||||
|     saturday: "土曜日" | ||||
|   reactions: | ||||
|     like: "ええやん" | ||||
|     like: "いいね" | ||||
|     love: "しゅき" | ||||
|     laugh: "笑" | ||||
|     hmm: "ふぅ~む" | ||||
| @@ -84,6 +84,7 @@ common: | ||||
|   my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。" | ||||
|   i-like-sushi: "私は(プリンよりむしろ)寿司が好き" | ||||
|   show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示" | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "公式アカウント" | ||||
|   disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" | ||||
|   reversi: | ||||
| @@ -423,6 +424,24 @@ desktop/views/components/calendar.vue: | ||||
|   prev: "前の月" | ||||
|   next: "次の月" | ||||
|   go: "クリックして時間遡行" | ||||
| desktop/views/components/charts.vue: | ||||
|   title: "チャート" | ||||
|   per-day: "1日ごと" | ||||
|   per-hour: "1時間ごと" | ||||
|   notes: "投稿" | ||||
|   users: "ユーザー" | ||||
|   drive: "ドライブ" | ||||
|   charts: | ||||
|     notes: "投稿の増減 (統合)" | ||||
|     local-notes: "投稿の増減 (ローカル)" | ||||
|     remote-notes: "投稿の増減 (リモート)" | ||||
|     notes-total: "投稿の累計" | ||||
|     users: "ユーザーの増減" | ||||
|     users-total: "ユーザーの累計" | ||||
|     drive: "ドライブ使用量の増減" | ||||
|     drive-total: "ドライブ使用量の累計" | ||||
|     drive-files: "ドライブのファイル数の増減" | ||||
|     drive-files-total: "ドライブのファイル数の累計" | ||||
| desktop/views/components/choose-file-from-drive-window.vue: | ||||
|   choose-file: "ファイル選択中" | ||||
|   upload: "PCからドライブにファイルをアップロード" | ||||
| @@ -754,6 +773,7 @@ desktop/views/components/ui.header.account.vue: | ||||
|   lists: "リスト" | ||||
|   follow-requests: "フォロー申請" | ||||
|   customize: "ホームのカスタマイズ" | ||||
|   admin: "管理" | ||||
|   settings: "設定" | ||||
|   signout: "サインアウト" | ||||
|   dark: "闇に飲まれる" | ||||
| @@ -818,18 +838,6 @@ desktop/views/pages/admin/admin.unverify-user.vue: | ||||
|   unverify-user: "ユーザーの公式アカウント解除" | ||||
|   unverify: "公式アカウントを解除する" | ||||
|   unverified: "公式アカウントを解除しました" | ||||
| desktop/views/pages/admin/admin.notes-chart.vue: | ||||
|   title: "投稿" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.users-chart.vue: | ||||
|   title: "ユーザー" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/admin/admin.drive-chart.vue: | ||||
|   title: "ドライブ" | ||||
|   local: "ローカル" | ||||
|   remote: "リモート" | ||||
| desktop/views/pages/deck/deck.tl-column.vue: | ||||
|   is-media-only: "メディア投稿のみ" | ||||
|   is-media-view: "メディアビュー" | ||||
| @@ -1044,6 +1052,7 @@ mobile/views/components/ui.nav.vue: | ||||
|   game: "ゲーム" | ||||
|   darkmode: "ダークモード" | ||||
|   settings: "設定" | ||||
|   admin: "管理" | ||||
|   about: "Misskeyについて" | ||||
| mobile/views/components/user-timeline.vue: | ||||
|   no-notes: "このユーザーは投稿していないようです。" | ||||
|   | ||||
							
								
								
									
										17
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,8 +1,8 @@ | ||||
| { | ||||
| 	"name": "misskey", | ||||
| 	"author": "syuilo <i@syuilo.com>", | ||||
| 	"version": "8.1.0", | ||||
| 	"clientVersion": "1.0.8811", | ||||
| 	"version": "8.15.0", | ||||
| 	"clientVersion": "1.0.9031", | ||||
| 	"codename": "nighthike", | ||||
| 	"main": "./built/index.js", | ||||
| 	"private": true, | ||||
| @@ -60,7 +60,7 @@ | ||||
| 		"@types/mocha": "5.2.3", | ||||
| 		"@types/mongodb": "3.1.4", | ||||
| 		"@types/ms": "0.7.30", | ||||
| 		"@types/node": "10.7.1", | ||||
| 		"@types/node": "10.9.2", | ||||
| 		"@types/portscanner": "2.1.0", | ||||
| 		"@types/pug": "2.0.4", | ||||
| 		"@types/qrcode": "1.2.0", | ||||
| @@ -70,14 +70,14 @@ | ||||
| 		"@types/request-promise-native": "1.0.15", | ||||
| 		"@types/rimraf": "2.0.2", | ||||
| 		"@types/seedrandom": "2.4.27", | ||||
| 		"@types/sharp": "0.17.9", | ||||
| 		"@types/sharp": "0.17.10", | ||||
| 		"@types/showdown": "1.7.5", | ||||
| 		"@types/single-line-log": "1.1.0", | ||||
| 		"@types/speakeasy": "2.0.2", | ||||
| 		"@types/systeminformation": "3.23.0", | ||||
| 		"@types/tmp": "0.0.33", | ||||
| 		"@types/uuid": "3.4.3", | ||||
| 		"@types/webpack": "4.4.10", | ||||
| 		"@types/webpack": "4.4.11", | ||||
| 		"@types/webpack-stream": "3.2.10", | ||||
| 		"@types/websocket": "0.0.39", | ||||
| 		"@types/ws": "6.0.0", | ||||
| @@ -89,6 +89,7 @@ | ||||
| 		"bootstrap-vue": "2.0.0-rc.11", | ||||
| 		"cafy": "11.3.0", | ||||
| 		"chalk": "2.4.1", | ||||
| 		"chart.js": "2.7.2", | ||||
| 		"commander": "2.17.1", | ||||
| 		"crc-32": "1.2.0", | ||||
| 		"css-loader": "1.0.0", | ||||
| @@ -149,6 +150,7 @@ | ||||
| 		"loader-utils": "1.1.0", | ||||
| 		"lodash.assign": "4.2.0", | ||||
| 		"mecab-async": "0.1.2", | ||||
| 		"merge-options": "1.0.1", | ||||
| 		"minio": "7.0.0", | ||||
| 		"mkdirp": "0.5.1", | ||||
| 		"mocha": "5.2.0", | ||||
| @@ -192,7 +194,7 @@ | ||||
| 		"stylus": "0.54.5", | ||||
| 		"stylus-loader": "3.0.2", | ||||
| 		"summaly": "2.1.4", | ||||
| 		"systeminformation": "3.42.9", | ||||
| 		"systeminformation": "3.43.0", | ||||
| 		"syuilo-password-strength": "0.0.1", | ||||
| 		"textarea-caret": "3.1.0", | ||||
| 		"tmp": "0.0.33", | ||||
| @@ -206,8 +208,9 @@ | ||||
| 		"uuid": "3.3.2", | ||||
| 		"v-animate-css": "0.0.2", | ||||
| 		"vue": "2.5.17", | ||||
| 		"vue-chartjs": "3.4.0", | ||||
| 		"vue-cropperjs": "2.2.1", | ||||
| 		"vue-js-modal": "1.3.18", | ||||
| 		"vue-js-modal": "1.3.19", | ||||
| 		"vue-json-tree-view": "2.1.4", | ||||
| 		"vue-loader": "15.4.0", | ||||
| 		"vue-router": "3.0.1", | ||||
|   | ||||
| @@ -51,8 +51,9 @@ | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (settings) { | ||||
| 		if (settings.device.lang) lang = settings.device.lang; | ||||
| 	if (settings && settings.device.lang && | ||||
| 		LANGS.includes(settings.device.lang)) { | ||||
| 		lang = settings.device.lang; | ||||
| 	} | ||||
| 	//#endregion | ||||
|  | ||||
|   | ||||
| @@ -26,8 +26,8 @@ export default Vue.extend({ | ||||
| 	}, | ||||
| 	created() { | ||||
| 		(this as any).os.getMeta().then(meta => { | ||||
| 			if (meta.repositoryUrl) this.repositoryUrl = meta.repositoryUrl; | ||||
| 			if (meta.feedbackUrl) this.feedbackUrl = meta.feedbackUrl; | ||||
| 			if (meta.maintainer.repository_url) this.repositoryUrl = meta.maintainer.repository_url; | ||||
| 			if (meta.maintainer.feedback_url) this.feedbackUrl = meta.maintainer.feedback_url; | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
|   | ||||
| @@ -1,8 +1,10 @@ | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| Vue.filter('bytes', (v, digits = 0) => { | ||||
| 	const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; | ||||
| 	if (v == 0) return '0Byte'; | ||||
| 	const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; | ||||
| 	if (v == 0) return '0'; | ||||
| 	const isMinus = v < 0; | ||||
| 	if (isMinus) v = -v; | ||||
| 	const i = Math.floor(Math.log(v) / Math.log(1024)); | ||||
| 	return (v / Math.pow(1024, i)).toFixed(digits).replace(/\.0+$/, '') + sizes[i]; | ||||
| 	return (isMinus ? '-' : '') + (v / Math.pow(1024, i)).toFixed(digits).replace(/\.0+$/, '') + sizes[i]; | ||||
| }); | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| 		<h1>%fa:heart%%i18n:@title%</h1> | ||||
| 		<p> | ||||
| 			{{ '%i18n:@text%'.substr(0, '%i18n:@text%'.indexOf('{')) }} | ||||
| 			<a href="https://syuilo.com">@syuilo</a> | ||||
| 			<a :href="meta.maintainer.url">{{ meta.maintainer.name }}</a> | ||||
| 			{{ '%i18n:@text%'.substr('%i18n:@text%'.indexOf('}') + 1) }} | ||||
| 		</p> | ||||
| 	</article> | ||||
| @@ -15,6 +15,17 @@ | ||||
| import define from '../../../common/define-widget'; | ||||
| export default define({ | ||||
| 	name: 'donation' | ||||
| }).extend({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			meta: null | ||||
| 		}; | ||||
| 	}, | ||||
| 	created() { | ||||
| 		(this as any).os.getMeta().then(meta => { | ||||
| 			this.meta = meta; | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -25,6 +25,7 @@ import updateBanner from './api/update-banner'; | ||||
| import MkIndex from './views/pages/index.vue'; | ||||
| import MkDeck from './views/pages/deck/deck.vue'; | ||||
| import MkAdmin from './views/pages/admin/admin.vue'; | ||||
| import MkStats from './views/pages/stats/stats.vue'; | ||||
| import MkUser from './views/pages/user/user.vue'; | ||||
| import MkFavorites from './views/pages/favorites.vue'; | ||||
| import MkSelectDrive from './views/pages/selectdrive.vue'; | ||||
| @@ -57,6 +58,7 @@ init(async (launch) => { | ||||
| 			{ path: '/', name: 'index', component: MkIndex }, | ||||
| 			{ path: '/deck', name: 'deck', component: MkDeck }, | ||||
| 			{ path: '/admin', name: 'admin', component: MkAdmin }, | ||||
| 			{ path: '/stats', name: 'stats', component: MkStats }, | ||||
| 			{ path: '/i/customize-home', component: MkHomeCustomize }, | ||||
| 			{ path: '/i/favorites', component: MkFavorites }, | ||||
| 			{ path: '/i/messaging/:user', component: MkMessagingRoom }, | ||||
| @@ -94,7 +96,7 @@ init(async (launch) => { | ||||
| 	/** | ||||
| 	 * Init Notification | ||||
| 	 */ | ||||
| 	if ('Notification' in window) { | ||||
| 	if ('Notification' in window && os.store.getters.isSignedIn) { | ||||
| 		// 許可を得ていなかったらリクエスト | ||||
| 		if ((Notification as any).permission == 'default') { | ||||
| 			await Notification.requestPermission(); | ||||
|   | ||||
							
								
								
									
										42
									
								
								src/client/app/desktop/views/components/charts.chart.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/client/app/desktop/views/components/charts.chart.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| import Vue from 'vue'; | ||||
| import { Line } from 'vue-chartjs'; | ||||
| import * as mergeOptions from 'merge-options'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	extends: Line, | ||||
| 	props: { | ||||
| 		data: { | ||||
| 			required: true | ||||
| 		}, | ||||
| 		opts: { | ||||
| 			required: false | ||||
| 		} | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		data() { | ||||
| 			this.render(); | ||||
| 		} | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.render(); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		render() { | ||||
| 			this.renderChart(this.data, mergeOptions({ | ||||
| 				responsive: true, | ||||
| 				maintainAspectRatio: false, | ||||
| 				scales: { | ||||
| 					xAxes: [{ | ||||
| 						type: 'time', | ||||
| 						distribution: 'series' | ||||
| 					}] | ||||
| 				}, | ||||
| 				tooltips: { | ||||
| 					intersect: false, | ||||
| 					mode: 'x', | ||||
| 					position: 'nearest' | ||||
| 				} | ||||
| 			}, this.opts || {})); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
							
								
								
									
										585
									
								
								src/client/app/desktop/views/components/charts.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										585
									
								
								src/client/app/desktop/views/components/charts.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,585 @@ | ||||
| <template> | ||||
| <div class="gkgckalzgidaygcxnugepioremxvxvpt"> | ||||
| 	<header> | ||||
| 		<b>%i18n:@title%:</b> | ||||
| 		<select v-model="chartType"> | ||||
| 			<optgroup label="%i18n:@users%"> | ||||
| 				<option value="users">%i18n:@charts.users%</option> | ||||
| 				<option value="users-total">%i18n:@charts.users-total%</option> | ||||
| 			</optgroup> | ||||
| 			<optgroup label="%i18n:@notes%"> | ||||
| 				<option value="notes">%i18n:@charts.notes%</option> | ||||
| 				<option value="local-notes">%i18n:@charts.local-notes%</option> | ||||
| 				<option value="remote-notes">%i18n:@charts.remote-notes%</option> | ||||
| 				<option value="notes-total">%i18n:@charts.notes-total%</option> | ||||
| 			</optgroup> | ||||
| 			<optgroup label="%i18n:@drive%"> | ||||
| 				<option value="drive-files">%i18n:@charts.drive-files%</option> | ||||
| 				<option value="drive-files-total">%i18n:@charts.drive-files-total%</option> | ||||
| 				<option value="drive">%i18n:@charts.drive%</option> | ||||
| 				<option value="drive-total">%i18n:@charts.drive-total%</option> | ||||
| 			</optgroup> | ||||
| 		</select> | ||||
| 		<div> | ||||
| 			<span @click="span = 'day'" :class="{ active: span == 'day' }">%i18n:@per-day%</span> | <span @click="span = 'hour'" :class="{ active: span == 'hour' }">%i18n:@per-hour%</span> | ||||
| 		</div> | ||||
| 	</header> | ||||
| 	<div> | ||||
| 		<x-chart v-if="chart" :data="data[0]" :opts="data[1]"/> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import XChart from './charts.chart.ts'; | ||||
|  | ||||
| const colors = { | ||||
| 	local: 'rgb(246, 88, 79)', | ||||
| 	remote: 'rgb(65, 221, 222)', | ||||
|  | ||||
| 	localPlus: 'rgb(52, 178, 118)', | ||||
| 	remotePlus: 'rgb(158, 255, 209)', | ||||
| 	localMinus: 'rgb(255, 97, 74)', | ||||
| 	remoteMinus: 'rgb(255, 149, 134)' | ||||
| }; | ||||
|  | ||||
| const rgba = (color: string): string => { | ||||
| 	return color.replace('rgb', 'rgba').replace(')', ', 0.1)'); | ||||
| }; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XChart | ||||
| 	}, | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			chart: null, | ||||
| 			chartType: 'notes', | ||||
| 			span: 'hour' | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	computed: { | ||||
| 		data(): any { | ||||
| 			if (this.chart == null) return null; | ||||
| 			switch (this.chartType) { | ||||
| 				case 'users': return this.usersChart(false); | ||||
| 				case 'users-total': return this.usersChart(true); | ||||
| 				case 'notes': return this.notesChart('combined'); | ||||
| 				case 'local-notes': return this.notesChart('local'); | ||||
| 				case 'remote-notes': return this.notesChart('remote'); | ||||
| 				case 'notes-total': return this.notesTotalChart(); | ||||
| 				case 'drive': return this.driveChart(); | ||||
| 				case 'drive-total': return this.driveTotalChart(); | ||||
| 				case 'drive-files': return this.driveFilesChart(); | ||||
| 				case 'drive-files-total': return this.driveFilesTotalChart(); | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		stats(): any[] { | ||||
| 			return ( | ||||
| 				this.span == 'day' ? this.chart.perDay : | ||||
| 				this.span == 'hour' ? this.chart.perHour : | ||||
| 				null | ||||
| 			); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	created() { | ||||
| 		(this as any).api('chart').then(chart => { | ||||
| 			this.chart = chart; | ||||
| 		}); | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		notesChart(type: string): any { | ||||
| 			const data = this.stats.slice().reverse().map(x => ({ | ||||
| 				date: new Date(x.date), | ||||
| 				normal: type == 'local' ? x.notes.local.diffs.normal : type == 'remote' ? x.notes.remote.diffs.normal : x.notes.local.diffs.normal + x.notes.remote.diffs.normal, | ||||
| 				reply: type == 'local' ? x.notes.local.diffs.reply : type == 'remote' ? x.notes.remote.diffs.reply : x.notes.local.diffs.reply + x.notes.remote.diffs.reply, | ||||
| 				renote: type == 'local' ? x.notes.local.diffs.renote : type == 'remote' ? x.notes.remote.diffs.renote : x.notes.local.diffs.renote + x.notes.remote.diffs.renote, | ||||
| 				all: type == 'local' ? (x.notes.local.inc + -x.notes.local.dec) : type == 'remote' ? (x.notes.remote.inc + -x.notes.remote.dec) : (x.notes.local.inc + -x.notes.local.dec) + (x.notes.remote.inc + -x.notes.remote.dec) | ||||
| 			})); | ||||
|  | ||||
| 			return [{ | ||||
| 				datasets: [{ | ||||
| 					label: 'All', | ||||
| 					fill: false, | ||||
| 					borderColor: '#555', | ||||
| 					borderWidth: 2, | ||||
| 					borderDash: [4, 4], | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.all })) | ||||
| 				}, { | ||||
| 					label: 'Renotes', | ||||
| 					fill: true, | ||||
| 					backgroundColor: 'rgba(161, 222, 65, 0.1)', | ||||
| 					borderColor: '#a1de41', | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.renote })) | ||||
| 				}, { | ||||
| 					label: 'Replies', | ||||
| 					fill: true, | ||||
| 					backgroundColor: 'rgba(247, 121, 108, 0.1)', | ||||
| 					borderColor: '#f7796c', | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.reply })) | ||||
| 				}, { | ||||
| 					label: 'Normal', | ||||
| 					fill: true, | ||||
| 					backgroundColor: 'rgba(65, 221, 222, 0.1)', | ||||
| 					borderColor: '#41ddde', | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.normal })) | ||||
| 				}] | ||||
| 			}, { | ||||
| 				scales: { | ||||
| 					yAxes: [{ | ||||
| 						ticks: { | ||||
| 							callback: value => { | ||||
| 								return Vue.filter('number')(value); | ||||
| 							} | ||||
| 						} | ||||
| 					}] | ||||
| 				}, | ||||
| 				tooltips: { | ||||
| 					callbacks: { | ||||
| 						label: (tooltipItem, data) => { | ||||
| 							const label = data.datasets[tooltipItem.datasetIndex].label || ''; | ||||
| 							return `${label}: ${Vue.filter('number')(tooltipItem.yLabel)}`; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			}]; | ||||
| 		}, | ||||
|  | ||||
| 		notesTotalChart(): any { | ||||
| 			const data = this.stats.slice().reverse().map(x => ({ | ||||
| 				date: new Date(x.date), | ||||
| 				localCount: x.notes.local.total, | ||||
| 				remoteCount: x.notes.remote.total | ||||
| 			})); | ||||
|  | ||||
| 			return [{ | ||||
| 				datasets: [{ | ||||
| 					label: 'Combined', | ||||
| 					fill: false, | ||||
| 					borderColor: '#555', | ||||
| 					borderWidth: 2, | ||||
| 					borderDash: [4, 4], | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.remoteCount + x.localCount })) | ||||
| 				}, { | ||||
| 					label: 'Local', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.local), | ||||
| 					borderColor: colors.local, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.localCount })) | ||||
| 				}, { | ||||
| 					label: 'Remote', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.remote), | ||||
| 					borderColor: colors.remote, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.remoteCount })) | ||||
| 				}] | ||||
| 			}, { | ||||
| 				scales: { | ||||
| 					yAxes: [{ | ||||
| 						ticks: { | ||||
| 							callback: value => { | ||||
| 								return Vue.filter('number')(value); | ||||
| 							} | ||||
| 						} | ||||
| 					}] | ||||
| 				}, | ||||
| 				tooltips: { | ||||
| 					callbacks: { | ||||
| 						label: (tooltipItem, data) => { | ||||
| 							const label = data.datasets[tooltipItem.datasetIndex].label || ''; | ||||
| 							return `${label}: ${Vue.filter('number')(tooltipItem.yLabel)}`; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			}]; | ||||
| 		}, | ||||
|  | ||||
| 		usersChart(total: boolean): any { | ||||
| 			const data = this.stats.slice().reverse().map(x => ({ | ||||
| 				date: new Date(x.date), | ||||
| 				localCount: total ? x.users.local.total : (x.users.local.inc + -x.users.local.dec), | ||||
| 				remoteCount: total ? x.users.remote.total : (x.users.remote.inc + -x.users.remote.dec) | ||||
| 			})); | ||||
|  | ||||
| 			return [{ | ||||
| 				datasets: [{ | ||||
| 					label: 'Combined', | ||||
| 					fill: false, | ||||
| 					borderColor: '#555', | ||||
| 					borderWidth: 2, | ||||
| 					borderDash: [4, 4], | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.remoteCount + x.localCount })) | ||||
| 				}, { | ||||
| 					label: 'Local', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.local), | ||||
| 					borderColor: colors.local, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.localCount })) | ||||
| 				}, { | ||||
| 					label: 'Remote', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.remote), | ||||
| 					borderColor: colors.remote, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.remoteCount })) | ||||
| 				}] | ||||
| 			}, { | ||||
| 				scales: { | ||||
| 					yAxes: [{ | ||||
| 						ticks: { | ||||
| 							callback: value => { | ||||
| 								return Vue.filter('number')(value); | ||||
| 							} | ||||
| 						} | ||||
| 					}] | ||||
| 				}, | ||||
| 				tooltips: { | ||||
| 					callbacks: { | ||||
| 						label: (tooltipItem, data) => { | ||||
| 							const label = data.datasets[tooltipItem.datasetIndex].label || ''; | ||||
| 							return `${label}: ${Vue.filter('number')(tooltipItem.yLabel)}`; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			}]; | ||||
| 		}, | ||||
|  | ||||
| 		driveChart(): any { | ||||
| 			const data = this.stats.slice().reverse().map(x => ({ | ||||
| 				date: new Date(x.date), | ||||
| 				localInc: x.drive.local.incSize, | ||||
| 				localDec: -x.drive.local.decSize, | ||||
| 				remoteInc: x.drive.remote.incSize, | ||||
| 				remoteDec: -x.drive.remote.decSize, | ||||
| 			})); | ||||
|  | ||||
| 			return [{ | ||||
| 				datasets: [{ | ||||
| 					label: 'All', | ||||
| 					fill: false, | ||||
| 					borderColor: '#555', | ||||
| 					borderWidth: 2, | ||||
| 					borderDash: [4, 4], | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.localInc + x.localDec + x.remoteInc + x.remoteDec })) | ||||
| 				}, { | ||||
| 					label: 'Local +', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.localPlus), | ||||
| 					borderColor: colors.localPlus, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.localInc })) | ||||
| 				}, { | ||||
| 					label: 'Local -', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.localMinus), | ||||
| 					borderColor: colors.localMinus, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.localDec })) | ||||
| 				}, { | ||||
| 					label: 'Remote +', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.remotePlus), | ||||
| 					borderColor: colors.remotePlus, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.remoteInc })) | ||||
| 				}, { | ||||
| 					label: 'Remote -', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.remoteMinus), | ||||
| 					borderColor: colors.remoteMinus, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.remoteDec })) | ||||
| 				}] | ||||
| 			}, { | ||||
| 				scales: { | ||||
| 					yAxes: [{ | ||||
| 						ticks: { | ||||
| 							callback: value => { | ||||
| 								return Vue.filter('bytes')(value, 1); | ||||
| 							} | ||||
| 						} | ||||
| 					}] | ||||
| 				}, | ||||
| 				tooltips: { | ||||
| 					callbacks: { | ||||
| 						label: (tooltipItem, data) => { | ||||
| 							const label = data.datasets[tooltipItem.datasetIndex].label || ''; | ||||
| 							return `${label}: ${Vue.filter('bytes')(tooltipItem.yLabel, 1)}`; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			}]; | ||||
| 		}, | ||||
|  | ||||
| 		driveTotalChart(): any { | ||||
| 			const data = this.stats.slice().reverse().map(x => ({ | ||||
| 				date: new Date(x.date), | ||||
| 				localSize: x.drive.local.totalSize, | ||||
| 				remoteSize: x.drive.remote.totalSize | ||||
| 			})); | ||||
|  | ||||
| 			return [{ | ||||
| 				datasets: [{ | ||||
| 					label: 'Combined', | ||||
| 					fill: false, | ||||
| 					borderColor: '#555', | ||||
| 					borderWidth: 2, | ||||
| 					borderDash: [4, 4], | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.remoteSize + x.localSize })) | ||||
| 				}, { | ||||
| 					label: 'Local', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.local), | ||||
| 					borderColor: colors.local, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.localSize })) | ||||
| 				}, { | ||||
| 					label: 'Remote', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.remote), | ||||
| 					borderColor: colors.remote, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.remoteSize })) | ||||
| 				}] | ||||
| 			}, { | ||||
| 				scales: { | ||||
| 					yAxes: [{ | ||||
| 						ticks: { | ||||
| 							callback: value => { | ||||
| 								return Vue.filter('bytes')(value, 1); | ||||
| 							} | ||||
| 						} | ||||
| 					}] | ||||
| 				}, | ||||
| 				tooltips: { | ||||
| 					callbacks: { | ||||
| 						label: (tooltipItem, data) => { | ||||
| 							const label = data.datasets[tooltipItem.datasetIndex].label || ''; | ||||
| 							return `${label}: ${Vue.filter('bytes')(tooltipItem.yLabel, 1)}`; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			}]; | ||||
| 		}, | ||||
|  | ||||
| 		driveFilesChart(): any { | ||||
| 			const data = this.stats.slice().reverse().map(x => ({ | ||||
| 				date: new Date(x.date), | ||||
| 				localInc: x.drive.local.incCount, | ||||
| 				localDec: -x.drive.local.decCount, | ||||
| 				remoteInc: x.drive.remote.incCount, | ||||
| 				remoteDec: -x.drive.remote.decCount | ||||
| 			})); | ||||
|  | ||||
| 			return [{ | ||||
| 				datasets: [{ | ||||
| 					label: 'All', | ||||
| 					fill: false, | ||||
| 					borderColor: '#555', | ||||
| 					borderWidth: 2, | ||||
| 					borderDash: [4, 4], | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.localInc + x.localDec + x.remoteInc + x.remoteDec })) | ||||
| 				}, { | ||||
| 					label: 'Local +', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.localPlus), | ||||
| 					borderColor: colors.localPlus, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.localInc })) | ||||
| 				}, { | ||||
| 					label: 'Local -', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.localMinus), | ||||
| 					borderColor: colors.localMinus, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.localDec })) | ||||
| 				}, { | ||||
| 					label: 'Remote +', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.remotePlus), | ||||
| 					borderColor: colors.remotePlus, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.remoteInc })) | ||||
| 				}, { | ||||
| 					label: 'Remote -', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.remoteMinus), | ||||
| 					borderColor: colors.remoteMinus, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.remoteDec })) | ||||
| 				}] | ||||
| 			}, { | ||||
| 				scales: { | ||||
| 					yAxes: [{ | ||||
| 						ticks: { | ||||
| 							callback: value => { | ||||
| 								return Vue.filter('number')(value); | ||||
| 							} | ||||
| 						} | ||||
| 					}] | ||||
| 				}, | ||||
| 				tooltips: { | ||||
| 					callbacks: { | ||||
| 						label: (tooltipItem, data) => { | ||||
| 							const label = data.datasets[tooltipItem.datasetIndex].label || ''; | ||||
| 							return `${label}: ${Vue.filter('number')(tooltipItem.yLabel)}`; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			}]; | ||||
| 		}, | ||||
|  | ||||
| 		driveFilesTotalChart(): any { | ||||
| 			const data = this.stats.slice().reverse().map(x => ({ | ||||
| 				date: new Date(x.date), | ||||
| 				localCount: x.drive.local.totalCount, | ||||
| 				remoteCount: x.drive.remote.totalCount, | ||||
| 			})); | ||||
|  | ||||
| 			return [{ | ||||
| 				datasets: [{ | ||||
| 					label: 'Combined', | ||||
| 					fill: false, | ||||
| 					borderColor: '#555', | ||||
| 					borderWidth: 2, | ||||
| 					borderDash: [4, 4], | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.localCount + x.remoteCount })) | ||||
| 				}, { | ||||
| 					label: 'Local', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.local), | ||||
| 					borderColor: colors.local, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.localCount })) | ||||
| 				}, { | ||||
| 					label: 'Remote', | ||||
| 					fill: true, | ||||
| 					backgroundColor: rgba(colors.remote), | ||||
| 					borderColor: colors.remote, | ||||
| 					borderWidth: 2, | ||||
| 					pointBackgroundColor: '#fff', | ||||
| 					lineTension: 0, | ||||
| 					data: data.map(x => ({ t: x.date, y: x.remoteCount })) | ||||
| 				}] | ||||
| 			}, { | ||||
| 				scales: { | ||||
| 					yAxes: [{ | ||||
| 						ticks: { | ||||
| 							callback: value => { | ||||
| 								return Vue.filter('number')(value); | ||||
| 							} | ||||
| 						} | ||||
| 					}] | ||||
| 				}, | ||||
| 				tooltips: { | ||||
| 					callbacks: { | ||||
| 						label: (tooltipItem, data) => { | ||||
| 							const label = data.datasets[tooltipItem.datasetIndex].label || ''; | ||||
| 							return `${label}: ${Vue.filter('number')(tooltipItem.yLabel)}`; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			}]; | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| @import '~const.styl' | ||||
|  | ||||
| .gkgckalzgidaygcxnugepioremxvxvpt | ||||
| 	padding 32px | ||||
| 	background #fff | ||||
| 	box-shadow 0 2px 8px rgba(#000, 0.1) | ||||
|  | ||||
| 	* | ||||
| 		user-select none | ||||
|  | ||||
| 	> header | ||||
| 		display flex | ||||
| 		margin 0 0 1em 0 | ||||
| 		padding 0 0 8px 0 | ||||
| 		font-size 1em | ||||
| 		color #555 | ||||
| 		border-bottom solid 1px #eee | ||||
|  | ||||
| 		> b | ||||
| 			margin-right 8px | ||||
|  | ||||
| 		> *:last-child | ||||
| 			margin-left auto | ||||
|  | ||||
| 			* | ||||
| 				&:not(.active) | ||||
| 					color $theme-color | ||||
| 					cursor pointer | ||||
|  | ||||
| 	> div | ||||
| 		> * | ||||
| 			display block | ||||
| 			height 300px | ||||
|  | ||||
| </style> | ||||
| @@ -49,6 +49,7 @@ | ||||
| 			</div> | ||||
| 			<mk-switch v-model="$store.state.settings.showPostFormOnTopOfTl" @change="onChangeShowPostFormOnTopOfTl" text="%i18n:@post-form-on-timeline%"/> | ||||
| 			<mk-switch v-model="$store.state.settings.suggestRecentHashtags" @change="onChangeSuggestRecentHashtags" text="%i18n:@suggest-recent-hashtags%"/> | ||||
| 			<mk-switch v-model="$store.state.settings.showClockOnHeader" @change="onChangeShowClockOnHeader" text="%i18n:@show-clock-on-header%"/> | ||||
| 			<mk-switch v-model="$store.state.settings.showReplyTarget" @change="onChangeShowReplyTarget" text="%i18n:@show-reply-target%"/> | ||||
| 			<mk-switch v-model="$store.state.settings.showMyRenotes" @change="onChangeShowMyRenotes" text="%i18n:@show-my-renotes%"/> | ||||
| 			<mk-switch v-model="$store.state.settings.showRenotedMyNotes" @change="onChangeShowRenotedMyNotes" text="%i18n:@show-renoted-my-notes%"/> | ||||
| @@ -333,6 +334,12 @@ export default Vue.extend({ | ||||
| 				value: v | ||||
| 			}); | ||||
| 		}, | ||||
| 		onChangeShowClockOnHeader(v) { | ||||
| 			this.$store.dispatch('settings/set', { | ||||
| 				key: 'showClockOnHeader', | ||||
| 				value: v | ||||
| 			}); | ||||
| 		}, | ||||
| 		onChangeShowReplyTarget(v) { | ||||
| 			this.$store.dispatch('settings/set', { | ||||
| 				key: 'showReplyTarget', | ||||
|   | ||||
| @@ -17,7 +17,7 @@ | ||||
| 					<x-account v-if="$store.getters.isSignedIn"/> | ||||
| 					<x-notifications v-if="$store.getters.isSignedIn"/> | ||||
| 					<x-post v-if="$store.getters.isSignedIn"/> | ||||
| 					<x-clock/> | ||||
| 					<x-clock v-if="$store.state.settings.showClockOnHeader"/> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</div> | ||||
|   | ||||
| @@ -48,7 +48,7 @@ export default Vue.extend({ | ||||
| 				this.open(); | ||||
| 			}); | ||||
| 		} else { | ||||
| 			const query = this.user[0] == '@' ? | ||||
| 			const query = this.user.startsWith('@') ? | ||||
| 				parseAcct(this.user.substr(1)) : | ||||
| 				{ userId: this.user }; | ||||
|  | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| <template> | ||||
| <div class="obdskegsannmntldydackcpzezagxqfy card"> | ||||
| <div class="obdskegsannmntldydackcpzezagxqfy mk-admin-card"> | ||||
| 	<header>%i18n:@dashboard%</header> | ||||
| 	<div v-if="stats" class="stats"> | ||||
| 		<div><b>%fa:user% {{ stats.originalUsersCount | number }}</b><span>%i18n:@original-users%</span></div> | ||||
| 		<div><span>%fa:user% {{ stats.usersCount | number }}</span><span>%i18n:@all-users%</span></div> | ||||
| 		<div><b>%fa:pen% {{ stats.originalNotesCount | number }}</b><span>%i18n:@original-notes%</span></div> | ||||
| 		<div><span>%fa:pen% {{ stats.notesCount | number }}</span><span>%i18n:@all-notes%</span></div> | ||||
| 		<div><b>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</b><span>%i18n:@original-notes%</span></div> | ||||
| 		<div><span>%fa:pencil-alt% {{ stats.notesCount | number }}</span><span>%i18n:@all-notes%</span></div> | ||||
| 	</div> | ||||
| 	<div class="cpu-memory"> | ||||
| 		<x-cpu-memory :connection="connection"/> | ||||
|   | ||||
| @@ -1,74 +0,0 @@ | ||||
| <template> | ||||
| <div> | ||||
| 	<a @click="span = 'day'">Per day</a> | <a @click="span = 'hour'">Per hour</a> | ||||
| 	<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`"> | ||||
| 		<polyline | ||||
| 			:points="points" | ||||
| 			fill="none" | ||||
| 			stroke-width="0.3" | ||||
| 			stroke="#555"/> | ||||
| 	</svg> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: { | ||||
| 		chart: { | ||||
| 			required: true | ||||
| 		}, | ||||
| 		type: { | ||||
| 			type: String, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			viewBoxX: 100, | ||||
| 			viewBoxY: 30, | ||||
| 			points: null, | ||||
| 			span: 'day' | ||||
| 		}; | ||||
| 	}, | ||||
| 	computed: { | ||||
| 		stats(): any[] { | ||||
| 			return ( | ||||
| 				this.span == 'day' ? this.chart.perDay : | ||||
| 				this.span == 'hour' ? this.chart.perHour : | ||||
| 				null | ||||
| 			); | ||||
| 		} | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		stats() { | ||||
| 			this.render(); | ||||
| 		} | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.render(); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		render() { | ||||
| 			const peak = Math.max.apply(null, this.stats.map(d => this.type == 'local' ? d.drive.local.totalSize : d.drive.remote.totalSize)); | ||||
|  | ||||
| 			if (peak != 0) { | ||||
| 				const data = this.stats.slice().reverse().map(x => ({ | ||||
| 					size: this.type == 'local' ? x.drive.local.totalSize : x.drive.remote.totalSize | ||||
| 				})); | ||||
|  | ||||
| 				this.points = data.map((d, i) => `${(this.viewBoxX / data.length) * i},${(1 - (d.size / peak)) * this.viewBoxY}`).join(' '); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| svg | ||||
| 	display block | ||||
| 	padding 10px | ||||
| 	width 100% | ||||
|  | ||||
| </style> | ||||
| @@ -1,34 +0,0 @@ | ||||
| <template> | ||||
| <div class="card"> | ||||
| 	<header>%i18n:@title%</header> | ||||
| 	<div class="card"> | ||||
| 		<header>%i18n:@local%</header> | ||||
| 		<x-chart v-if="chart" :chart="chart" type="local"/> | ||||
| 	</div> | ||||
| 	<div class="card"> | ||||
| 		<header>%i18n:@remote%</header> | ||||
| 		<x-chart v-if="chart" :chart="chart" type="remote"/> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from "vue"; | ||||
| import XChart from "./admin.drive-chart.chart.vue"; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XChart | ||||
| 	}, | ||||
| 	props: { | ||||
| 		chart: { | ||||
| 			required: true | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| @import '~const.styl' | ||||
|  | ||||
| </style> | ||||
| @@ -1,99 +0,0 @@ | ||||
| <template> | ||||
| <div> | ||||
| 	<a @click="span = 'day'">Per day</a> | <a @click="span = 'hour'">Per hour</a> | ||||
| 	<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`"> | ||||
| 		<polyline | ||||
| 			:points="pointsNote" | ||||
| 			fill="none" | ||||
| 			stroke-width="0.3" | ||||
| 			stroke="#41ddde"/> | ||||
| 		<polyline | ||||
| 			:points="pointsReply" | ||||
| 			fill="none" | ||||
| 			stroke-width="0.3" | ||||
| 			stroke="#f7796c"/> | ||||
| 		<polyline | ||||
| 			:points="pointsRenote" | ||||
| 			fill="none" | ||||
| 			stroke-width="0.3" | ||||
| 			stroke="#a1de41"/> | ||||
| 		<polyline | ||||
| 			:points="pointsTotal" | ||||
| 			fill="none" | ||||
| 			stroke-width="0.3" | ||||
| 			stroke="#555" | ||||
| 			stroke-dasharray="1 1"/> | ||||
| 	</svg> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: { | ||||
| 		chart: { | ||||
| 			required: true | ||||
| 		}, | ||||
| 		type: { | ||||
| 			type: String, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			viewBoxX: 100, | ||||
| 			viewBoxY: 30, | ||||
| 			pointsNote: null, | ||||
| 			pointsReply: null, | ||||
| 			pointsRenote: null, | ||||
| 			pointsTotal: null, | ||||
| 			span: 'day' | ||||
| 		}; | ||||
| 	}, | ||||
| 	computed: { | ||||
| 		stats(): any[] { | ||||
| 			return ( | ||||
| 				this.span == 'day' ? this.chart.perDay : | ||||
| 				this.span == 'hour' ? this.chart.perHour : | ||||
| 				null | ||||
| 			); | ||||
| 		} | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		stats() { | ||||
| 			this.render(); | ||||
| 		} | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.render(); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		render() { | ||||
| 			const peak = Math.max.apply(null, this.stats.map(d => this.type == 'local' ? d.notes.local.diff : d.notes.remote.diff)); | ||||
|  | ||||
| 			if (peak != 0) { | ||||
| 				const data = this.stats.slice().reverse().map(x => ({ | ||||
| 					normal: this.type == 'local' ? x.notes.local.diffs.normal : x.notes.remote.diffs.normal, | ||||
| 					reply: this.type == 'local' ? x.notes.local.diffs.reply : x.notes.remote.diffs.reply, | ||||
| 					renote: this.type == 'local' ? x.notes.local.diffs.renote : x.notes.remote.diffs.renote, | ||||
| 					total: this.type == 'local' ? x.notes.local.diff : x.notes.remote.diff | ||||
| 				})); | ||||
|  | ||||
| 				this.pointsNote = data.map((d, i) => `${(this.viewBoxX / data.length) * i},${(1 - (d.normal / peak)) * this.viewBoxY}`).join(' '); | ||||
| 				this.pointsReply = data.map((d, i) => `${(this.viewBoxX / data.length) * i},${(1 - (d.reply / peak)) * this.viewBoxY}`).join(' '); | ||||
| 				this.pointsRenote = data.map((d, i) => `${(this.viewBoxX / data.length) * i},${(1 - (d.renote / peak)) * this.viewBoxY}`).join(' '); | ||||
| 				this.pointsTotal = data.map((d, i) => `${(this.viewBoxX / data.length) * i},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' '); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| svg | ||||
| 	display block | ||||
| 	padding 10px | ||||
| 	width 100% | ||||
|  | ||||
| </style> | ||||
| @@ -1,34 +0,0 @@ | ||||
| <template> | ||||
| <div class="card"> | ||||
| 	<header>%i18n:@title%</header> | ||||
| 	<div class="card"> | ||||
| 		<header>%i18n:@local%</header> | ||||
| 		<x-chart v-if="chart" :chart="chart" type="local"/> | ||||
| 	</div> | ||||
| 	<div class="card"> | ||||
| 		<header>%i18n:@remote%</header> | ||||
| 		<x-chart v-if="chart" :chart="chart" type="remote"/> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from "vue"; | ||||
| import XChart from "./admin.notes-chart.chart.vue"; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XChart | ||||
| 	}, | ||||
| 	props: { | ||||
| 		chart: { | ||||
| 			required: true | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| @import '~const.styl' | ||||
|  | ||||
| </style> | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <div class="card"> | ||||
| <div class="mk-admin-card"> | ||||
| 	<header>%i18n:@suspend-user%</header> | ||||
| 	<input v-model="username" type="text" class="ui"/> | ||||
| 	<button class="ui" @click="suspendUser" :disabled="suspending">%i18n:@suspend%</button> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <div class="card"> | ||||
| <div class="mk-admin-card"> | ||||
| 	<header>%i18n:@unsuspend-user%</header> | ||||
| 	<input v-model="username" type="text" class="ui"/> | ||||
| 	<button class="ui" @click="unsuspendUser" :disabled="unsuspending">%i18n:@unsuspend%</button> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <div class="card"> | ||||
| <div class="mk-admin-card"> | ||||
| 	<header>%i18n:@unverify-user%</header> | ||||
| 	<input v-model="username" type="text" class="ui"/> | ||||
| 	<button class="ui" @click="unverifyUser" :disabled="unverifying">%i18n:@unverify%</button> | ||||
|   | ||||
| @@ -1,74 +0,0 @@ | ||||
| <template> | ||||
| <div> | ||||
| 	<a @click="span = 'day'">Per day</a> | <a @click="span = 'hour'">Per hour</a> | ||||
| 	<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`"> | ||||
| 		<polyline | ||||
| 			:points="points" | ||||
| 			fill="none" | ||||
| 			stroke-width="0.3" | ||||
| 			stroke="#555"/> | ||||
| 	</svg> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: { | ||||
| 		chart: { | ||||
| 			required: true | ||||
| 		}, | ||||
| 		type: { | ||||
| 			type: String, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			viewBoxX: 100, | ||||
| 			viewBoxY: 30, | ||||
| 			points: null, | ||||
| 			span: 'day' | ||||
| 		}; | ||||
| 	}, | ||||
| 	computed: { | ||||
| 		stats(): any[] { | ||||
| 			return ( | ||||
| 				this.span == 'day' ? this.chart.perDay : | ||||
| 				this.span == 'hour' ? this.chart.perHour : | ||||
| 				null | ||||
| 			); | ||||
| 		} | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		stats() { | ||||
| 			this.render(); | ||||
| 		} | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.render(); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		render() { | ||||
| 			const peak = Math.max.apply(null, this.stats.map(d => this.type == 'local' ? d.users.local.diff : d.users.remote.diff)); | ||||
|  | ||||
| 			if (peak != 0) { | ||||
| 				const data = this.stats.slice().reverse().map(x => ({ | ||||
| 					count: this.type == 'local' ? x.users.local.diff : x.users.remote.diff | ||||
| 				})); | ||||
|  | ||||
| 				this.points = data.map((d, i) => `${(this.viewBoxX / data.length) * i},${(1 - (d.count / peak)) * this.viewBoxY}`).join(' '); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| svg | ||||
| 	display block | ||||
| 	padding 10px | ||||
| 	width 100% | ||||
|  | ||||
| </style> | ||||
| @@ -1,34 +0,0 @@ | ||||
| <template> | ||||
| <div class="card"> | ||||
| 	<header>%i18n:@title%</header> | ||||
| 	<div class="card"> | ||||
| 		<header>%i18n:@local%</header> | ||||
| 		<x-chart v-if="chart" :chart="chart" type="local"/> | ||||
| 	</div> | ||||
| 	<div class="card"> | ||||
| 		<header>%i18n:@remote%</header> | ||||
| 		<x-chart v-if="chart" :chart="chart" type="remote"/> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from "vue"; | ||||
| import XChart from "./admin.users-chart.chart.vue"; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XChart | ||||
| 	}, | ||||
| 	props: { | ||||
| 		chart: { | ||||
| 			required: true | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| @import '~const.styl' | ||||
|  | ||||
| </style> | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <div class="card"> | ||||
| <div class="mk-admin-card"> | ||||
| 	<header>%i18n:@verify-user%</header> | ||||
| 	<input v-model="username" type="text" class="ui"/> | ||||
| 	<button class="ui" @click="verifyUser" :disabled="verifying">%i18n:@verify%</button> | ||||
|   | ||||
| @@ -11,9 +11,7 @@ | ||||
| 	<main> | ||||
| 		<div v-show="page == 'dashboard'"> | ||||
| 			<x-dashboard/> | ||||
| 			<x-users-chart :chart="chart"/> | ||||
| 			<x-notes-chart :chart="chart"/> | ||||
| 			<x-drive-chart :chart="chart"/> | ||||
| 			<x-charts/> | ||||
| 		</div> | ||||
| 		<div v-if="page == 'users'"> | ||||
| 			<x-suspend-user/> | ||||
| @@ -34,9 +32,7 @@ import XSuspendUser from "./admin.suspend-user.vue"; | ||||
| import XUnsuspendUser from "./admin.unsuspend-user.vue"; | ||||
| import XVerifyUser from "./admin.verify-user.vue"; | ||||
| import XUnverifyUser from "./admin.unverify-user.vue"; | ||||
| import XUsersChart from "./admin.users-chart.vue"; | ||||
| import XNotesChart from "./admin.notes-chart.vue"; | ||||
| import XDriveChart from "./admin.drive-chart.vue"; | ||||
| import XCharts from "../../components/charts.vue"; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| @@ -45,21 +41,13 @@ export default Vue.extend({ | ||||
| 		XUnsuspendUser, | ||||
| 		XVerifyUser, | ||||
| 		XUnverifyUser, | ||||
| 		XUsersChart, | ||||
| 		XNotesChart, | ||||
| 		XDriveChart | ||||
| 		XCharts | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			page: 'dashboard', | ||||
| 			chart: null | ||||
| 			page: 'dashboard' | ||||
| 		}; | ||||
| 	}, | ||||
| 	created() { | ||||
| 		(this as any).api('admin/chart').then(chart => { | ||||
| 			this.chart = chart; | ||||
| 		}); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		nav(page: string) { | ||||
| 			this.page = page; | ||||
| @@ -115,7 +103,7 @@ export default Vue.extend({ | ||||
| 			> div | ||||
| 				max-width 800px | ||||
|  | ||||
| .card | ||||
| .mk-admin-card | ||||
| 	padding 32px | ||||
| 	background #fff | ||||
| 	box-shadow 0 2px 8px rgba(#000, 0.1) | ||||
|   | ||||
| @@ -16,7 +16,7 @@ import Vue from 'vue'; | ||||
| export default Vue.extend({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			name: (this as any).os.instanceName, | ||||
| 			name: null, | ||||
| 			posted: false, | ||||
| 			text: new URLSearchParams(location.search).get('text') | ||||
| 		}; | ||||
| @@ -25,6 +25,11 @@ export default Vue.extend({ | ||||
| 		close() { | ||||
| 			window.close(); | ||||
| 		} | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		(this as any).os.getMeta().then(meta => { | ||||
| 			this.name = meta.name; | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|   | ||||
							
								
								
									
										64
									
								
								src/client/app/desktop/views/pages/stats/stats.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/client/app/desktop/views/pages/stats/stats.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| <template> | ||||
| <div class="tcrwdhwpuxrwmcttxjcsehgpagpstqey"> | ||||
| 	<div v-if="stats" class="stats"> | ||||
| 		<div><b>%fa:user% {{ stats.originalUsersCount | number }}</b><span>%i18n:@original-users%</span></div> | ||||
| 		<div><span>%fa:user% {{ stats.usersCount | number }}</span><span>%i18n:@all-users%</span></div> | ||||
| 		<div><b>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</b><span>%i18n:@original-notes%</span></div> | ||||
| 		<div><span>%fa:pencil-alt% {{ stats.notesCount | number }}</span><span>%i18n:@all-notes%</span></div> | ||||
| 	</div> | ||||
| 	<div> | ||||
| 		<x-charts/> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from "vue"; | ||||
| import XCharts from "../../components/charts.vue"; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XCharts | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			stats: null | ||||
| 		}; | ||||
| 	}, | ||||
| 	created() { | ||||
| 		(this as any).api('stats').then(stats => { | ||||
| 			this.stats = stats; | ||||
| 		}); | ||||
| 	}, | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus"> | ||||
| @import '~const.styl' | ||||
|  | ||||
| .tcrwdhwpuxrwmcttxjcsehgpagpstqey | ||||
| 	width 100% | ||||
| 	padding 16px | ||||
|  | ||||
| 	> .stats | ||||
| 		display flex | ||||
| 		justify-content center | ||||
| 		margin-bottom 16px | ||||
| 		padding 32px | ||||
| 		background #fff | ||||
| 		box-shadow 0 2px 8px rgba(#000, 0.1) | ||||
|  | ||||
| 		> div | ||||
| 			flex 1 | ||||
| 			text-align center | ||||
|  | ||||
| 			> *:first-child | ||||
| 				display block | ||||
| 				color $theme-color | ||||
|  | ||||
| 			> *:last-child | ||||
| 				font-size 70% | ||||
|  | ||||
| 	> div | ||||
| 		max-width 800px | ||||
| </style> | ||||
| @@ -16,7 +16,7 @@ import Vue from 'vue'; | ||||
| export default Vue.extend({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			name: (this as any).os.instanceName, | ||||
| 			name: null, | ||||
| 			posted: false, | ||||
| 			text: new URLSearchParams(location.search).get('text') | ||||
| 		}; | ||||
| @@ -25,6 +25,11 @@ export default Vue.extend({ | ||||
| 		close() { | ||||
| 			window.close(); | ||||
| 		} | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		(this as any).os.getMeta().then(meta => { | ||||
| 			this.name = meta.name; | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
| 					<a class="avatar"> | ||||
| 						<img :src="user.avatarUrl" alt="avatar"/> | ||||
| 					</a> | ||||
| 					<mk-mute-button v-if="$store.state.i.id != user.id" :user="user"/> | ||||
| 					<mk-mute-button v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/> | ||||
| 					<mk-follow-button v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/> | ||||
| 				</div> | ||||
| 				<div class="title"> | ||||
|   | ||||
| @@ -13,6 +13,7 @@ const defaultSettings = { | ||||
| 	showMaps: true, | ||||
| 	showPostFormOnTopOfTl: false, | ||||
| 	suggestRecentHashtags: true, | ||||
| 	showClockOnHeader: false, | ||||
| 	circleIcons: true, | ||||
| 	gradientWindowHeader: false, | ||||
| 	showReplyTarget: true, | ||||
|   | ||||
| @@ -53,5 +53,5 @@ export default function load() { | ||||
| } | ||||
|  | ||||
| function normalizeUrl(url: string) { | ||||
| 	return url[url.length - 1] === '/' ? url.substr(0, url.length - 1) : url; | ||||
| 	return url.endsWith('/') ? url.substr(0, url.length - 1) : url; | ||||
| } | ||||
|   | ||||
| @@ -62,6 +62,8 @@ export type Source = { | ||||
| 	 */ | ||||
| 	ghost?: string; | ||||
|  | ||||
| 	summalyProxy?: string; | ||||
|  | ||||
| 	accesslog?: string; | ||||
| 	twitter?: { | ||||
| 		consumer_key: string; | ||||
|   | ||||
| @@ -197,7 +197,7 @@ const elements: Element[] = [ | ||||
|  | ||||
| 		if (thisIsNotARegexp) return null; | ||||
| 		if (regexp == '') return null; | ||||
| 		if (regexp[0] == ' ' && regexp[regexp.length - 1] == ' ') return null; | ||||
| 		if (regexp.startsWith(' ') && regexp.endsWith(' ')) return null; | ||||
|  | ||||
| 		return { | ||||
| 			html: `<span class="regexp">/${escape(regexp)}/</span>`, | ||||
|   | ||||
| @@ -10,7 +10,7 @@ export type TextElementHashtag = { | ||||
|  | ||||
| export default function(text: string, i: number) { | ||||
| 	if (!(/^\s#[^\s]+/.test(text) || (i == 0 && /^#[^\s]+/.test(text)))) return null; | ||||
| 	const isHead = text[0] == '#'; | ||||
| 	const isHead = text.startsWith('#'); | ||||
| 	const hashtag = text.match(/^\s?#[^\s]+/)[0]; | ||||
| 	const res: any[] = !isHead ? [{ | ||||
| 		type: 'text', | ||||
|   | ||||
| @@ -13,7 +13,7 @@ export type TextElementLink = { | ||||
| export default function(text: string) { | ||||
| 	const match = text.match(/^\??\[([^\[\]]+?)\]\((https?:\/\/[\w\/:%#@\$&\?!\(\)\[\]~\.=\+\-]+?)\)/); | ||||
| 	if (!match) return null; | ||||
| 	const silent = text[0] == '?'; | ||||
| 	const silent = text.startsWith('?'); | ||||
| 	const link = match[0]; | ||||
| 	const title = match[1]; | ||||
| 	const url = match[2]; | ||||
|   | ||||
| @@ -25,9 +25,9 @@ export const replacement = (match: string, key: string) => { | ||||
| 				arg == 'S' ? 'fas' : | ||||
| 				arg == 'B' ? 'fab' : | ||||
| 				''; | ||||
| 		} else if (arg[0] == '.') { | ||||
| 		} else if (arg.startsWith('.')) { | ||||
| 			classes.push('fa-' + arg.substr(1)); | ||||
| 		} else if (arg[0] == '-') { | ||||
| 		} else if (arg.startsWith('-')) { | ||||
| 			transform = arg.substr(1).split('|').join(' '); | ||||
| 		} else { | ||||
| 			name = arg; | ||||
|   | ||||
| @@ -2,14 +2,21 @@ import * as mongo from 'mongodb'; | ||||
| import db from '../db/mongodb'; | ||||
|  | ||||
| const Stats = db.get<IStats>('stats'); | ||||
| Stats.createIndex({ date: -1 }, { unique: true }); | ||||
| Stats.dropIndex({ date: -1 }); // 後方互換性のため | ||||
| Stats.createIndex({ span: -1, date: -1 }, { unique: true }); | ||||
| export default Stats; | ||||
|  | ||||
| export interface IStats { | ||||
| 	_id: mongo.ObjectID; | ||||
|  | ||||
| 	/** | ||||
| 	 * 集計日時 | ||||
| 	 */ | ||||
| 	date: Date; | ||||
|  | ||||
| 	/** | ||||
| 	 * 集計期間 | ||||
| 	 */ | ||||
| 	span: 'day' | 'hour'; | ||||
|  | ||||
| 	/** | ||||
| @@ -18,26 +25,36 @@ export interface IStats { | ||||
| 	users: { | ||||
| 		local: { | ||||
| 			/** | ||||
| 			 * この日時点での、ローカルのユーザーの総計 | ||||
| 			 * 集計期間時点での、全ユーザー数 (ローカル) | ||||
| 			 */ | ||||
| 			total: number; | ||||
|  | ||||
| 			/** | ||||
| 			 * ローカルのユーザー数の前日比 | ||||
| 			 * 増加したユーザー数 (ローカル) | ||||
| 			 */ | ||||
| 			diff: number; | ||||
| 			inc: number; | ||||
|  | ||||
| 			/** | ||||
| 			 * 減少したユーザー数 (ローカル) | ||||
| 			 */ | ||||
| 			dec: number; | ||||
| 		}; | ||||
|  | ||||
| 		remote: { | ||||
| 			/** | ||||
| 			 * この日時点での、リモートのユーザーの総計 | ||||
| 			 * 集計期間時点での、全ユーザー数 (リモート) | ||||
| 			 */ | ||||
| 			total: number; | ||||
|  | ||||
| 			/** | ||||
| 			 * リモートのユーザー数の前日比 | ||||
| 			 * 増加したユーザー数 (リモート) | ||||
| 			 */ | ||||
| 			diff: number; | ||||
| 			inc: number; | ||||
|  | ||||
| 			/** | ||||
| 			 * 減少したユーザー数 (リモート) | ||||
| 			 */ | ||||
| 			dec: number; | ||||
| 		}; | ||||
| 	}; | ||||
|  | ||||
| @@ -47,28 +64,33 @@ export interface IStats { | ||||
| 	notes: { | ||||
| 		local: { | ||||
| 			/** | ||||
| 			 * この日時点での、ローカルの投稿の総計 | ||||
| 			 * 集計期間時点での、全投稿数 (ローカル) | ||||
| 			 */ | ||||
| 			total: number; | ||||
|  | ||||
| 			/** | ||||
| 			 * ローカルの投稿数の前日比 | ||||
| 			 * 増加した投稿数 (ローカル) | ||||
| 			 */ | ||||
| 			diff: number; | ||||
| 			inc: number; | ||||
|  | ||||
| 			/** | ||||
| 			 * 減少した投稿数 (ローカル) | ||||
| 			 */ | ||||
| 			dec: number; | ||||
|  | ||||
| 			diffs: { | ||||
| 				/** | ||||
| 				 * ローカルの通常の投稿数の前日比 | ||||
| 				 * 通常の投稿数の差分 (ローカル) | ||||
| 				 */ | ||||
| 				normal: number; | ||||
|  | ||||
| 				/** | ||||
| 				 * ローカルのリプライの投稿数の前日比 | ||||
| 				 * リプライの投稿数の差分 (ローカル) | ||||
| 				 */ | ||||
| 				reply: number; | ||||
|  | ||||
| 				/** | ||||
| 				 * ローカルのRenoteの投稿数の前日比 | ||||
| 				 * Renoteの投稿数の差分 (ローカル) | ||||
| 				 */ | ||||
| 				renote: number; | ||||
| 			}; | ||||
| @@ -76,28 +98,33 @@ export interface IStats { | ||||
|  | ||||
| 		remote: { | ||||
| 			/** | ||||
| 			 * この日時点での、リモートの投稿の総計 | ||||
| 			 * 集計期間時点での、全投稿数 (リモート) | ||||
| 			 */ | ||||
| 			total: number; | ||||
|  | ||||
| 			/** | ||||
| 			 * リモートの投稿数の前日比 | ||||
| 			 * 増加した投稿数 (リモート) | ||||
| 			 */ | ||||
| 			diff: number; | ||||
| 			inc: number; | ||||
|  | ||||
| 			/** | ||||
| 			 * 減少した投稿数 (リモート) | ||||
| 			 */ | ||||
| 			dec: number; | ||||
|  | ||||
| 			diffs: { | ||||
| 				/** | ||||
| 				 * リモートの通常の投稿数の前日比 | ||||
| 				 * 通常の投稿数の差分 (リモート) | ||||
| 				 */ | ||||
| 				normal: number; | ||||
|  | ||||
| 				/** | ||||
| 				 * リモートのリプライの投稿数の前日比 | ||||
| 				 * リプライの投稿数の差分 (リモート) | ||||
| 				 */ | ||||
| 				reply: number; | ||||
|  | ||||
| 				/** | ||||
| 				 * リモートのRenoteの投稿数の前日比 | ||||
| 				 * Renoteの投稿数の差分 (リモート) | ||||
| 				 */ | ||||
| 				renote: number; | ||||
| 			}; | ||||
| @@ -110,46 +137,66 @@ export interface IStats { | ||||
| 	drive: { | ||||
| 		local: { | ||||
| 			/** | ||||
| 			 * この日時点での、ローカルのドライブファイル数の総計 | ||||
| 			 * 集計期間時点での、全ドライブファイル数 (ローカル) | ||||
| 			 */ | ||||
| 			totalCount: number; | ||||
|  | ||||
| 			/** | ||||
| 			 * この日時点での、ローカルのドライブファイルサイズの総計 | ||||
| 			 * 集計期間時点での、全ドライブファイルの合計サイズ (ローカル) | ||||
| 			 */ | ||||
| 			totalSize: number; | ||||
|  | ||||
| 			/** | ||||
| 			 * ローカルのドライブファイル数の前日比 | ||||
| 			 * 増加したドライブファイル数 (ローカル) | ||||
| 			 */ | ||||
| 			diffCount: number; | ||||
| 			incCount: number; | ||||
|  | ||||
| 			/** | ||||
| 			 * ローカルのドライブファイルサイズの前日比 | ||||
| 			 * 増加したドライブ使用量 (ローカル) | ||||
| 			 */ | ||||
| 			diffSize: number; | ||||
| 			incSize: number; | ||||
|  | ||||
| 			/** | ||||
| 			 * 減少したドライブファイル数 (ローカル) | ||||
| 			 */ | ||||
| 			decCount: number; | ||||
|  | ||||
| 			/** | ||||
| 			 * 減少したドライブ使用量 (ローカル) | ||||
| 			 */ | ||||
| 			decSize: number; | ||||
| 		}; | ||||
|  | ||||
| 		remote: { | ||||
| 			/** | ||||
| 			 * この日時点での、リモートのドライブファイル数の総計 | ||||
| 			 * 集計期間時点での、全ドライブファイル数 (リモート) | ||||
| 			 */ | ||||
| 			totalCount: number; | ||||
|  | ||||
| 			/** | ||||
| 			 * この日時点での、リモートのドライブファイルサイズの総計 | ||||
| 			 * 集計期間時点での、全ドライブファイルの合計サイズ (リモート) | ||||
| 			 */ | ||||
| 			totalSize: number; | ||||
|  | ||||
| 			/** | ||||
| 			 * リモートのドライブファイル数の前日比 | ||||
| 			 * 増加したドライブファイル数 (リモート) | ||||
| 			 */ | ||||
| 			diffCount: number; | ||||
| 			incCount: number; | ||||
|  | ||||
| 			/** | ||||
| 			 * リモートのドライブファイルサイズの前日比 | ||||
| 			 * 増加したドライブ使用量 (リモート) | ||||
| 			 */ | ||||
| 			diffSize: number; | ||||
| 			incSize: number; | ||||
|  | ||||
| 			/** | ||||
| 			 * 減少したドライブファイル数 (リモート) | ||||
| 			 */ | ||||
| 			decCount: number; | ||||
|  | ||||
| 			/** | ||||
| 			 * 減少したドライブ使用量 (リモート) | ||||
| 			 */ | ||||
| 			decSize: number; | ||||
| 		}; | ||||
| 	}; | ||||
| } | ||||
|   | ||||
| @@ -46,7 +46,7 @@ export default async (job: bq.Job, done: any): Promise<void> => { | ||||
|  | ||||
| 		// アクティビティを送信してきたユーザーがまだMisskeyサーバーに登録されていなかったら登録する | ||||
| 		if (user === null) { | ||||
| 			user = await resolvePerson(signature.keyId) as IRemoteUser; | ||||
| 			user = await resolvePerson(activity.actor) as IRemoteUser; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -131,5 +131,7 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver): | ||||
| 	//#endregion | ||||
|  | ||||
| 	// リモートサーバーからフェッチしてきて登録 | ||||
| 	return await createNote(value, resolver); | ||||
| 	// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが | ||||
| 	// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。 | ||||
| 	return await createNote(uri, resolver); | ||||
| } | ||||
|   | ||||
| @@ -166,8 +166,8 @@ export async function createPerson(value: any, resolver?: Resolver): Promise<IUs | ||||
|  | ||||
| 	const avatarId = avatar ? avatar._id : null; | ||||
| 	const bannerId = banner ? banner._id : null; | ||||
| 	const avatarUrl = avatar && avatar.metadata.url ? avatar.metadata.url : null; | ||||
| 	const bannerUrl = banner && banner.metadata.url ? banner.metadata.url : null; | ||||
| 	const avatarUrl = (avatar && avatar.metadata.thumbnailUrl) ? avatar.metadata.thumbnailUrl : (avatar && avatar.metadata.url) ? avatar.metadata.url : null; | ||||
| 	const bannerUrl = (banner && banner.metadata.url) ? banner.metadata.url : null; | ||||
|  | ||||
| 	await User.update({ _id: user._id }, { | ||||
| 		$set: { | ||||
| @@ -255,7 +255,7 @@ export async function updatePerson(value: string | IObject, resolver?: Resolver) | ||||
| 			sharedInbox: person.sharedInbox, | ||||
| 			avatarId: avatar ? avatar._id : null, | ||||
| 			bannerId: banner ? banner._id : null, | ||||
| 			avatarUrl: avatar && avatar.metadata.url ? avatar.metadata.url : null, | ||||
| 			avatarUrl: (avatar && avatar.metadata.thumbnailUrl) ? avatar.metadata.thumbnailUrl : (avatar && avatar.metadata.url) ? avatar.metadata.url : null, | ||||
| 			bannerUrl: banner && banner.metadata.url ? banner.metadata.url : null, | ||||
| 			description: htmlToMFM(person.summary), | ||||
| 			followersCount, | ||||
|   | ||||
| @@ -6,6 +6,7 @@ export default (object: any, note: INote) => { | ||||
|  | ||||
| 	return { | ||||
| 		id: `${config.url}/notes/${note._id}`, | ||||
| 		actor: `${config.url}/users/${note.userId}`, | ||||
| 		type: 'Announce', | ||||
| 		published: note.createdAt.toISOString(), | ||||
| 		to: ['https://www.w3.org/ns/activitystreams#Public'], | ||||
|   | ||||
| @@ -1,4 +1,17 @@ | ||||
| export default (object: any) => ({ | ||||
| 	type: 'Create', | ||||
| 	object | ||||
| }); | ||||
| import config from '../../../config'; | ||||
| import { INote } from '../../../models/note'; | ||||
|  | ||||
| export default (object: any, note: INote) => { | ||||
| 	const activity = { | ||||
| 		id: `${config.url}/notes/${note._id}/activity`, | ||||
| 		actor: `${config.url}/users/${note.userId}`, | ||||
| 		type: 'Create', | ||||
| 		published: note.createdAt.toISOString(), | ||||
| 		object | ||||
| 	} as any; | ||||
|  | ||||
| 	if (object.to) activity.to = object.to; | ||||
| 	if (object.cc) activity.cc = object.cc; | ||||
|  | ||||
| 	return activity; | ||||
| }; | ||||
|   | ||||
| @@ -1,4 +1,8 @@ | ||||
| export default (object: any) => ({ | ||||
| import config from '../../../config'; | ||||
| import { ILocalUser } from "../../../models/user"; | ||||
|  | ||||
| export default (object: any, user: ILocalUser) => ({ | ||||
| 	type: 'Delete', | ||||
| 	actor: `${config.url}/users/${user._id}`, | ||||
| 	object | ||||
| }); | ||||
|   | ||||
| @@ -1,7 +1,16 @@ | ||||
| export default (x: any) => Object.assign({ | ||||
| 	'@context': [ | ||||
| 		'https://www.w3.org/ns/activitystreams', | ||||
| 		'https://w3id.org/security/v1', | ||||
| 		{ Hashtag: 'as:Hashtag' } | ||||
| 	] | ||||
| }, x); | ||||
| import config from '../../../config'; | ||||
| import * as uuid from 'uuid'; | ||||
|  | ||||
| export default (x: any) => { | ||||
| 	if (x !== null && typeof x === 'object' && x.id == null) { | ||||
| 		x.id = `${config.url}/${uuid.v4()}`; | ||||
| 	} | ||||
|  | ||||
| 	return Object.assign({ | ||||
| 		'@context': [ | ||||
| 			'https://www.w3.org/ns/activitystreams', | ||||
| 			'https://w3id.org/security/v1', | ||||
| 			{ Hashtag: 'as:Hashtag' } | ||||
| 		] | ||||
| 	}, x); | ||||
| }; | ||||
|   | ||||
| @@ -1,4 +1,8 @@ | ||||
| export default (object: any) => ({ | ||||
| import config from '../../../config'; | ||||
| import { ILocalUser, IUser } from "../../../models/user"; | ||||
|  | ||||
| export default (object: any, user: ILocalUser | IUser) => ({ | ||||
| 	type: 'Undo', | ||||
| 	actor: `${config.url}/users/${user._id}`, | ||||
| 	object | ||||
| }); | ||||
|   | ||||
| @@ -19,6 +19,9 @@ export default (user: ILocalUser, url: string, object: any) => new Promise((reso | ||||
| 		port, | ||||
| 		method: 'POST', | ||||
| 		path: pathname + search, | ||||
| 		headers: { | ||||
| 			'Content-Type': 'application/activity+json' | ||||
| 		} | ||||
| 	}, res => { | ||||
| 		log(`${url} --> ${res.statusCode}`); | ||||
|  | ||||
| @@ -32,7 +35,7 @@ export default (user: ILocalUser, url: string, object: any) => new Promise((reso | ||||
| 	sign(req, { | ||||
| 		authorizationHeaderName: 'Signature', | ||||
| 		key: user.keypair, | ||||
| 		keyId: `acct:${user.username}@${config.host}` | ||||
| 		keyId: `${config.url}/users/${user._id}/publickey` | ||||
| 	}); | ||||
|  | ||||
| 	// Signature: Signature ... => Signature: ... | ||||
|   | ||||
| @@ -25,7 +25,7 @@ function inbox(ctx: Router.IRouterContext) { | ||||
| 	ctx.req.headers.authorization = 'Signature ' + ctx.req.headers.signature; | ||||
|  | ||||
| 	try { | ||||
| 		signature = httpSignature.parseRequest(ctx.req); | ||||
| 		signature = httpSignature.parseRequest(ctx.req, { 'headers': [] }); | ||||
| 	} catch (e) { | ||||
| 		ctx.status = 401; | ||||
| 		return; | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| export default (token: string) => token[0] == '!'; | ||||
| export default (token: string) => token.startsWith('!'); | ||||
|   | ||||
| @@ -1,137 +0,0 @@ | ||||
| import Stats, { IStats } from '../../../../models/stats'; | ||||
|  | ||||
| type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; | ||||
|  | ||||
| export const meta = { | ||||
| 	requireCredential: true, | ||||
| 	requireAdmin: true | ||||
| }; | ||||
|  | ||||
| export default (params: any) => new Promise(async (res, rej) => { | ||||
| 	const daysRange = 365; | ||||
| 	const hoursRange = 24; | ||||
|  | ||||
| 	const now = new Date(); | ||||
| 	const y = now.getFullYear(); | ||||
| 	const m = now.getMonth(); | ||||
| 	const d = now.getDate(); | ||||
| 	const h = now.getHours(); | ||||
|  | ||||
| 	const [statsPerDay, statsPerHour] = await Promise.all([ | ||||
| 		Stats.find({ | ||||
| 			span: 'day', | ||||
| 			date: { | ||||
| 				$gt: new Date(y, m, d - daysRange) | ||||
| 			} | ||||
| 		}, { | ||||
| 			sort: { | ||||
| 				date: -1 | ||||
| 			}, | ||||
| 			fields: { | ||||
| 				_id: 0 | ||||
| 			} | ||||
| 		}), | ||||
| 		Stats.find({ | ||||
| 			span: 'hour', | ||||
| 			date: { | ||||
| 				$gt: new Date(y, m, d, h - hoursRange) | ||||
| 			} | ||||
| 		}, { | ||||
| 			sort: { | ||||
| 				date: -1 | ||||
| 			}, | ||||
| 			fields: { | ||||
| 				_id: 0 | ||||
| 			} | ||||
| 		}), | ||||
| 	]); | ||||
|  | ||||
| 	const format = (src: IStats[], span: 'day' | 'hour') => { | ||||
| 		const chart: Array<Omit<Omit<IStats, '_id'>, 'span'>> = []; | ||||
|  | ||||
| 		const range = | ||||
| 			span == 'day' ? daysRange : | ||||
| 			span == 'hour' ? hoursRange : | ||||
| 			null; | ||||
|  | ||||
| 		for (let i = (range - 1); i >= 0; i--) { | ||||
| 			const current = | ||||
| 				span == 'day' ? new Date(y, m, d - i) : | ||||
| 				span == 'hour' ? new Date(y, m, d, h - i) : | ||||
| 				null; | ||||
|  | ||||
| 			const stat = src.find(s => s.date.getTime() == current.getTime()); | ||||
|  | ||||
| 			if (stat) { | ||||
| 				chart.unshift(stat); | ||||
| 			} else { // 隙間埋め | ||||
| 				const mostRecent = src.find(s => s.date.getTime() < current.getTime()); | ||||
| 				if (mostRecent) { | ||||
| 					chart.unshift(Object.assign({}, mostRecent, { | ||||
| 						date: current | ||||
| 					})); | ||||
| 				} else { | ||||
| 					chart.unshift({ | ||||
| 						date: current, | ||||
| 						users: { | ||||
| 							local: { | ||||
| 								total: 0, | ||||
| 								diff: 0 | ||||
| 							}, | ||||
| 							remote: { | ||||
| 								total: 0, | ||||
| 								diff: 0 | ||||
| 							} | ||||
| 						}, | ||||
| 						notes: { | ||||
| 							local: { | ||||
| 								total: 0, | ||||
| 								diff: 0, | ||||
| 								diffs: { | ||||
| 									normal: 0, | ||||
| 									reply: 0, | ||||
| 									renote: 0 | ||||
| 								} | ||||
| 							}, | ||||
| 							remote: { | ||||
| 								total: 0, | ||||
| 								diff: 0, | ||||
| 								diffs: { | ||||
| 									normal: 0, | ||||
| 									reply: 0, | ||||
| 									renote: 0 | ||||
| 								} | ||||
| 							} | ||||
| 						}, | ||||
| 						drive: { | ||||
| 							local: { | ||||
| 								totalCount: 0, | ||||
| 								totalSize: 0, | ||||
| 								diffCount: 0, | ||||
| 								diffSize: 0 | ||||
| 							}, | ||||
| 							remote: { | ||||
| 								totalCount: 0, | ||||
| 								totalSize: 0, | ||||
| 								diffCount: 0, | ||||
| 								diffSize: 0 | ||||
| 							} | ||||
| 						} | ||||
| 					}); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		chart.forEach(x => { | ||||
| 			delete x.date; | ||||
| 			delete (x as any).span; | ||||
| 		}); | ||||
|  | ||||
| 		return chart; | ||||
| 	}; | ||||
|  | ||||
| 	res({ | ||||
| 		perDay: format(statsPerDay, 'day'), | ||||
| 		perHour: format(statsPerHour, 'hour') | ||||
| 	}); | ||||
| }); | ||||
							
								
								
									
										239
									
								
								src/server/api/endpoints/chart.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								src/server/api/endpoints/chart.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,239 @@ | ||||
| import Stats, { IStats } from '../../../models/stats'; | ||||
|  | ||||
| type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; | ||||
|  | ||||
| function migrateStats(stats: IStats[]) { | ||||
| 	stats.forEach(stat => { | ||||
| 		const isOldData = | ||||
| 			stat.users.local.inc == null || | ||||
| 			stat.users.local.dec == null || | ||||
| 			stat.users.remote.inc == null || | ||||
| 			stat.users.remote.dec == null || | ||||
| 			stat.notes.local.inc == null || | ||||
| 			stat.notes.local.dec == null || | ||||
| 			stat.notes.remote.inc == null || | ||||
| 			stat.notes.remote.dec == null || | ||||
| 			stat.drive.local.incCount == null || | ||||
| 			stat.drive.local.decCount == null || | ||||
| 			stat.drive.local.incSize == null || | ||||
| 			stat.drive.local.decSize == null || | ||||
| 			stat.drive.remote.incCount == null || | ||||
| 			stat.drive.remote.decCount == null || | ||||
| 			stat.drive.remote.incSize == null || | ||||
| 			stat.drive.remote.decSize == null; | ||||
|  | ||||
| 		if (!isOldData) return; | ||||
|  | ||||
| 		stat.users.local.inc = (stat as any).users.local.diff; | ||||
| 		stat.users.local.dec = 0; | ||||
| 		stat.users.remote.inc = (stat as any).users.remote.diff; | ||||
| 		stat.users.remote.dec = 0; | ||||
| 		stat.notes.local.inc = (stat as any).notes.local.diff; | ||||
| 		stat.notes.local.dec = 0; | ||||
| 		stat.notes.remote.inc = (stat as any).notes.remote.diff; | ||||
| 		stat.notes.remote.dec = 0; | ||||
| 		stat.drive.local.incCount = (stat as any).drive.local.diffCount; | ||||
| 		stat.drive.local.decCount = 0; | ||||
| 		stat.drive.local.incSize = (stat as any).drive.local.diffSize; | ||||
| 		stat.drive.local.decSize = 0; | ||||
| 		stat.drive.remote.incCount = (stat as any).drive.remote.diffCount; | ||||
| 		stat.drive.remote.decCount = 0; | ||||
| 		stat.drive.remote.incSize = (stat as any).drive.remote.diffSize; | ||||
| 		stat.drive.remote.decSize = 0; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| export const meta = { | ||||
| }; | ||||
|  | ||||
| export default (params: any) => new Promise(async (res, rej) => { | ||||
| 	const daysRange = 30; | ||||
| 	const hoursRange = 30; | ||||
|  | ||||
| 	const now = new Date(); | ||||
| 	const y = now.getFullYear(); | ||||
| 	const m = now.getMonth(); | ||||
| 	const d = now.getDate(); | ||||
| 	const h = now.getHours(); | ||||
|  | ||||
| 	const [statsPerDay, statsPerHour] = await Promise.all([ | ||||
| 		Stats.find({ | ||||
| 			span: 'day', | ||||
| 			date: { | ||||
| 				$gt: new Date(y, m, d - daysRange) | ||||
| 			} | ||||
| 		}, { | ||||
| 			sort: { | ||||
| 				date: -1 | ||||
| 			}, | ||||
| 			fields: { | ||||
| 				_id: 0 | ||||
| 			} | ||||
| 		}), | ||||
| 		Stats.find({ | ||||
| 			span: 'hour', | ||||
| 			date: { | ||||
| 				$gt: new Date(y, m, d, h - hoursRange) | ||||
| 			} | ||||
| 		}, { | ||||
| 			sort: { | ||||
| 				date: -1 | ||||
| 			}, | ||||
| 			fields: { | ||||
| 				_id: 0 | ||||
| 			} | ||||
| 		}), | ||||
| 	]); | ||||
|  | ||||
| 	// 後方互換性のため | ||||
| 	migrateStats(statsPerDay); | ||||
| 	migrateStats(statsPerHour); | ||||
|  | ||||
| 	const format = (src: IStats[], span: 'day' | 'hour') => { | ||||
| 		const chart: Array<Omit<Omit<IStats, '_id'>, 'span'>> = []; | ||||
|  | ||||
| 		const range = | ||||
| 			span == 'day' ? daysRange : | ||||
| 			span == 'hour' ? hoursRange : | ||||
| 			null; | ||||
|  | ||||
| 		for (let i = (range - 1); i >= 0; i--) { | ||||
| 			const current = | ||||
| 				span == 'day' ? new Date(y, m, d - i) : | ||||
| 				span == 'hour' ? new Date(y, m, d, h - i) : | ||||
| 				null; | ||||
|  | ||||
| 			const stat = src.find(s => s.date.getTime() == current.getTime()); | ||||
|  | ||||
| 			if (stat) { | ||||
| 				chart.unshift(stat); | ||||
| 			} else { // 隙間埋め | ||||
| 				const mostRecent = src.find(s => s.date.getTime() < current.getTime()); | ||||
| 				if (mostRecent) { | ||||
| 					chart.unshift({ | ||||
| 						date: current, | ||||
| 						users: { | ||||
| 							local: { | ||||
| 								total: mostRecent.users.local.total, | ||||
| 								inc: 0, | ||||
| 								dec: 0 | ||||
| 							}, | ||||
| 							remote: { | ||||
| 								total: mostRecent.users.remote.total, | ||||
| 								inc: 0, | ||||
| 								dec: 0 | ||||
| 							} | ||||
| 						}, | ||||
| 						notes: { | ||||
| 							local: { | ||||
| 								total: mostRecent.notes.local.total, | ||||
| 								inc: 0, | ||||
| 								dec: 0, | ||||
| 								diffs: { | ||||
| 									normal: 0, | ||||
| 									reply: 0, | ||||
| 									renote: 0 | ||||
| 								} | ||||
| 							}, | ||||
| 							remote: { | ||||
| 								total: mostRecent.notes.remote.total, | ||||
| 								inc: 0, | ||||
| 								dec: 0, | ||||
| 								diffs: { | ||||
| 									normal: 0, | ||||
| 									reply: 0, | ||||
| 									renote: 0 | ||||
| 								} | ||||
| 							} | ||||
| 						}, | ||||
| 						drive: { | ||||
| 							local: { | ||||
| 								totalCount: mostRecent.drive.local.totalCount, | ||||
| 								totalSize: mostRecent.drive.local.totalSize, | ||||
| 								incCount: 0, | ||||
| 								incSize: 0, | ||||
| 								decCount: 0, | ||||
| 								decSize: 0 | ||||
| 							}, | ||||
| 							remote: { | ||||
| 								totalCount: mostRecent.drive.remote.totalCount, | ||||
| 								totalSize: mostRecent.drive.remote.totalSize, | ||||
| 								incCount: 0, | ||||
| 								incSize: 0, | ||||
| 								decCount: 0, | ||||
| 								decSize: 0 | ||||
| 							} | ||||
| 						} | ||||
| 					}); | ||||
| 				} else { | ||||
| 					chart.unshift({ | ||||
| 						date: current, | ||||
| 						users: { | ||||
| 							local: { | ||||
| 								total: 0, | ||||
| 								inc: 0, | ||||
| 								dec: 0 | ||||
| 							}, | ||||
| 							remote: { | ||||
| 								total: 0, | ||||
| 								inc: 0, | ||||
| 								dec: 0 | ||||
| 							} | ||||
| 						}, | ||||
| 						notes: { | ||||
| 							local: { | ||||
| 								total: 0, | ||||
| 								inc: 0, | ||||
| 								dec: 0, | ||||
| 								diffs: { | ||||
| 									normal: 0, | ||||
| 									reply: 0, | ||||
| 									renote: 0 | ||||
| 								} | ||||
| 							}, | ||||
| 							remote: { | ||||
| 								total: 0, | ||||
| 								inc: 0, | ||||
| 								dec: 0, | ||||
| 								diffs: { | ||||
| 									normal: 0, | ||||
| 									reply: 0, | ||||
| 									renote: 0 | ||||
| 								} | ||||
| 							} | ||||
| 						}, | ||||
| 						drive: { | ||||
| 							local: { | ||||
| 								totalCount: 0, | ||||
| 								totalSize: 0, | ||||
| 								incCount: 0, | ||||
| 								incSize: 0, | ||||
| 								decCount: 0, | ||||
| 								decSize: 0 | ||||
| 							}, | ||||
| 							remote: { | ||||
| 								totalCount: 0, | ||||
| 								totalSize: 0, | ||||
| 								incCount: 0, | ||||
| 								incSize: 0, | ||||
| 								decCount: 0, | ||||
| 								decSize: 0 | ||||
| 							} | ||||
| 						} | ||||
| 					}); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		chart.forEach(x => { | ||||
| 			delete (x as any).span; | ||||
| 		}); | ||||
|  | ||||
| 		return chart; | ||||
| 	}; | ||||
|  | ||||
| 	res({ | ||||
| 		perDay: format(statsPerDay, 'day'), | ||||
| 		perHour: format(statsPerHour, 'hour') | ||||
| 	}); | ||||
| }); | ||||
| @@ -84,7 +84,7 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a | ||||
|  | ||||
| 		if (avatar == null) return rej('avatar not found'); | ||||
|  | ||||
| 		updates.avatarUrl = avatar.metadata.url || `${config.drive_url}/${avatar._id}`; | ||||
| 		updates.avatarUrl = avatar.metadata.thumbnailUrl || avatar.metadata.url || `${config.drive_url}/${avatar._id}`; | ||||
|  | ||||
| 		if (avatar.metadata.properties.avgColor) { | ||||
| 			updates.avatarColor = avatar.metadata.properties.avgColor; | ||||
|   | ||||
| @@ -46,6 +46,11 @@ router.post('/signin', require('./private/signin').default); | ||||
| router.use(require('./service/github').routes()); | ||||
| router.use(require('./service/twitter').routes()); | ||||
|  | ||||
| // Return 404 for unknown API | ||||
| router.all('*', async ctx => { | ||||
| 	ctx.status = 404; | ||||
| }); | ||||
|  | ||||
| // Register router | ||||
| app.use(router.routes()); | ||||
|  | ||||
|   | ||||
| @@ -14,7 +14,7 @@ function getUserToken(ctx: Koa.Context) { | ||||
|  | ||||
| function compareOrigin(ctx: Koa.Context) { | ||||
| 	function normalizeUrl(url: string) { | ||||
| 		return url[url.length - 1] === '/' ? url.substr(0, url.length - 1) : url; | ||||
| 		return url.endsWith('/') ? url.substr(0, url.length - 1) : url; | ||||
| 	} | ||||
|  | ||||
| 	const referer = ctx.headers['referer']; | ||||
|   | ||||
| @@ -122,8 +122,7 @@ router.get('/notes/:note', async ctx => { | ||||
| router.get('*', async ctx => { | ||||
| 	await send(ctx, `app/base.html`, { | ||||
| 		root: client, | ||||
| 		maxage: ms('3 days'), | ||||
| 		immutable: true | ||||
| 		maxage: ms('5m') | ||||
| 	}); | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -1,11 +1,20 @@ | ||||
| import * as Koa from 'koa'; | ||||
| import * as request from 'request-promise-native'; | ||||
| import summaly from 'summaly'; | ||||
| import config from '../../config'; | ||||
|  | ||||
| module.exports = async (ctx: Koa.Context) => { | ||||
| 	try { | ||||
| 		const summary = await summaly(ctx.query.url, { | ||||
| 		const summary = config.summalyProxy ? await request.get({ | ||||
| 			url: config.summalyProxy, | ||||
| 			qs: { | ||||
| 				url: ctx.query.url | ||||
| 			}, | ||||
| 			json: true | ||||
| 		}) : await summaly(ctx.query.url, { | ||||
| 			followRedirects: false | ||||
| 		}); | ||||
|  | ||||
| 		summary.icon = wrap(summary.icon); | ||||
| 		summary.thumbnail = wrap(summary.thumbnail); | ||||
|  | ||||
|   | ||||
| @@ -40,7 +40,7 @@ async function save(path: string, name: string, type: string, hash: string, size | ||||
| 		const thumbnailKey = `${config.drive.prefix}/${uuid.v4()}/${name}.thumbnail.jpg`; | ||||
|  | ||||
| 		const baseUrl = config.drive.baseUrl | ||||
| 			|| `${ config.drive.config.secure ? 'https' : 'http' }://${ config.drive.config.endPoint }${ config.drive.config.port ? ':' + config.drive.config.port : '' }/${ config.drive.bucket }`; | ||||
| 			|| `${ config.drive.config.useSSL ? 'https' : 'http' }://${ config.drive.config.endPoint }${ config.drive.config.port ? ':' + config.drive.config.port : '' }/${ config.drive.bucket }`; | ||||
|  | ||||
| 		await minio.putObject(config.drive.bucket, key, fs.createReadStream(path), size, { | ||||
| 			'Content-Type': type, | ||||
| @@ -116,7 +116,8 @@ async function deleteOldFile(user: IRemoteUser) { | ||||
| 	const oldFile = await DriveFile.findOne({ | ||||
| 		_id: { | ||||
| 			$nin: [user.avatarId, user.bannerId] | ||||
| 		} | ||||
| 		}, | ||||
| 		'metadata.userId': user._id | ||||
| 	}, { | ||||
| 		sort: { | ||||
| 			_id: 1 | ||||
|   | ||||
| @@ -56,7 +56,7 @@ export default async function(follower: IUser, followee: IUser) { | ||||
| 	} | ||||
|  | ||||
| 	if (isLocalUser(follower) && isRemoteUser(followee)) { | ||||
| 		const content = pack(renderUndo(renderFollow(follower, followee))); | ||||
| 		const content = pack(renderUndo(renderFollow(follower, followee), follower)); | ||||
| 		deliver(follower, content, followee.inbox); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -8,7 +8,7 @@ import { publishUserStream } from '../../../stream'; | ||||
|  | ||||
| export default async function(followee: IUser, follower: IUser) { | ||||
| 	if (isRemoteUser(followee)) { | ||||
| 		const content = pack(renderUndo(renderFollow(follower, followee))); | ||||
| 		const content = pack(renderUndo(renderFollow(follower, followee), follower)); | ||||
| 		deliver(follower as ILocalUser, content, followee.inbox); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -239,8 +239,8 @@ export default async (user: IUser, data: Option, silent = false) => new Promise< | ||||
|  | ||||
| async function renderActivity(data: Option, note: INote) { | ||||
| 	const content = data.renote && data.text == null | ||||
| 		? renderAnnounce(data.renote.uri ? data.renote.uri : await renderNote(data.renote), note) | ||||
| 		: renderCreate(await renderNote(note)); | ||||
| 		? renderAnnounce(data.renote.uri ? data.renote.uri : `${config.url}/notes/${data.renote._id}`, note) | ||||
| 		: renderCreate(await renderNote(note, false), note); | ||||
|  | ||||
| 	return packAp(content); | ||||
| } | ||||
|   | ||||
| @@ -32,7 +32,7 @@ export default async function(user: IUser, note: INote) { | ||||
|  | ||||
| 	//#region ローカルの投稿なら削除アクティビティを配送 | ||||
| 	if (isLocalUser(user)) { | ||||
| 		const content = pack(renderDelete(await renderNote(note))); | ||||
| 		const content = pack(renderDelete(await renderNote(note), user)); | ||||
|  | ||||
| 		const followings = await Following.find({ | ||||
| 			followeeId: user._id, | ||||
|   | ||||
| @@ -48,17 +48,20 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise<IStats> { | ||||
| 				users: { | ||||
| 					local: { | ||||
| 						total: mostRecentStats.users.local.total, | ||||
| 						diff: 0 | ||||
| 						inc: 0, | ||||
| 						dec: 0 | ||||
| 					}, | ||||
| 					remote: { | ||||
| 						total: mostRecentStats.users.remote.total, | ||||
| 						diff: 0 | ||||
| 						inc: 0, | ||||
| 						dec: 0 | ||||
| 					} | ||||
| 				}, | ||||
| 				notes: { | ||||
| 					local: { | ||||
| 						total: mostRecentStats.notes.local.total, | ||||
| 						diff: 0, | ||||
| 						inc: 0, | ||||
| 						dec: 0, | ||||
| 						diffs: { | ||||
| 							normal: 0, | ||||
| 							reply: 0, | ||||
| @@ -67,7 +70,8 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise<IStats> { | ||||
| 					}, | ||||
| 					remote: { | ||||
| 						total: mostRecentStats.notes.remote.total, | ||||
| 						diff: 0, | ||||
| 						inc: 0, | ||||
| 						dec: 0, | ||||
| 						diffs: { | ||||
| 							normal: 0, | ||||
| 							reply: 0, | ||||
| @@ -79,14 +83,18 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise<IStats> { | ||||
| 					local: { | ||||
| 						totalCount: mostRecentStats.drive.local.totalCount, | ||||
| 						totalSize: mostRecentStats.drive.local.totalSize, | ||||
| 						diffCount: 0, | ||||
| 						diffSize: 0 | ||||
| 						incCount: 0, | ||||
| 						incSize: 0, | ||||
| 						decCount: 0, | ||||
| 						decSize: 0 | ||||
| 					}, | ||||
| 					remote: { | ||||
| 						totalCount: mostRecentStats.drive.remote.totalCount, | ||||
| 						totalSize: mostRecentStats.drive.remote.totalSize, | ||||
| 						diffCount: 0, | ||||
| 						diffSize: 0 | ||||
| 						incCount: 0, | ||||
| 						incSize: 0, | ||||
| 						decCount: 0, | ||||
| 						decSize: 0 | ||||
| 					} | ||||
| 				} | ||||
| 			}; | ||||
| @@ -105,17 +113,20 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise<IStats> { | ||||
| 				users: { | ||||
| 					local: { | ||||
| 						total: 0, | ||||
| 						diff: 0 | ||||
| 						inc: 0, | ||||
| 						dec: 0 | ||||
| 					}, | ||||
| 					remote: { | ||||
| 						total: 0, | ||||
| 						diff: 0 | ||||
| 						inc: 0, | ||||
| 						dec: 0 | ||||
| 					} | ||||
| 				}, | ||||
| 				notes: { | ||||
| 					local: { | ||||
| 						total: 0, | ||||
| 						diff: 0, | ||||
| 						inc: 0, | ||||
| 						dec: 0, | ||||
| 						diffs: { | ||||
| 							normal: 0, | ||||
| 							reply: 0, | ||||
| @@ -124,7 +135,8 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise<IStats> { | ||||
| 					}, | ||||
| 					remote: { | ||||
| 						total: 0, | ||||
| 						diff: 0, | ||||
| 						inc: 0, | ||||
| 						dec: 0, | ||||
| 						diffs: { | ||||
| 							normal: 0, | ||||
| 							reply: 0, | ||||
| @@ -136,14 +148,18 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise<IStats> { | ||||
| 					local: { | ||||
| 						totalCount: 0, | ||||
| 						totalSize: 0, | ||||
| 						diffCount: 0, | ||||
| 						diffSize: 0 | ||||
| 						incCount: 0, | ||||
| 						incSize: 0, | ||||
| 						decCount: 0, | ||||
| 						decSize: 0 | ||||
| 					}, | ||||
| 					remote: { | ||||
| 						totalCount: 0, | ||||
| 						totalSize: 0, | ||||
| 						diffCount: 0, | ||||
| 						diffSize: 0 | ||||
| 						incCount: 0, | ||||
| 						incSize: 0, | ||||
| 						decCount: 0, | ||||
| 						decSize: 0 | ||||
| 					} | ||||
| 				} | ||||
| 			}; | ||||
| @@ -174,46 +190,56 @@ function update(inc: any) { | ||||
| } | ||||
|  | ||||
| export async function updateUserStats(user: IUser, isAdditional: boolean) { | ||||
| 	const amount = isAdditional ? 1 : -1; | ||||
| 	const origin = isLocalUser(user) ? 'local' : 'remote'; | ||||
|  | ||||
| 	const inc = {} as any; | ||||
| 	inc[`users.${origin}.total`] = amount; | ||||
| 	inc[`users.${origin}.diff`] = amount; | ||||
| 	inc[`users.${origin}.total`] = isAdditional ? 1 : -1; | ||||
| 	if (isAdditional) { | ||||
| 		inc[`users.${origin}.inc`] = 1; | ||||
| 	} else { | ||||
| 		inc[`users.${origin}.dec`] = 1; | ||||
| 	} | ||||
|  | ||||
| 	await update(inc); | ||||
| } | ||||
|  | ||||
| export async function updateNoteStats(note: INote, isAdditional: boolean) { | ||||
| 	const amount = isAdditional ? 1 : -1; | ||||
| 	const origin = isLocalUser(note._user) ? 'local' : 'remote'; | ||||
|  | ||||
| 	const inc = {} as any; | ||||
|  | ||||
| 	inc[`notes.${origin}.total`] = amount; | ||||
| 	inc[`notes.${origin}.diff`] = amount; | ||||
| 	inc[`notes.${origin}.total`] = isAdditional ? 1 : -1; | ||||
|  | ||||
| 	if (isAdditional) { | ||||
| 		inc[`notes.${origin}.inc`] = 1; | ||||
| 	} else { | ||||
| 		inc[`notes.${origin}.dec`] = 1; | ||||
| 	} | ||||
|  | ||||
| 	if (note.replyId != null) { | ||||
| 		inc[`notes.${origin}.diffs.reply`] = amount; | ||||
| 		inc[`notes.${origin}.diffs.reply`] = isAdditional ? 1 : -1; | ||||
| 	} else if (note.renoteId != null) { | ||||
| 		inc[`notes.${origin}.diffs.renote`] = amount; | ||||
| 		inc[`notes.${origin}.diffs.renote`] = isAdditional ? 1 : -1; | ||||
| 	} else { | ||||
| 		inc[`notes.${origin}.diffs.normal`] = amount; | ||||
| 		inc[`notes.${origin}.diffs.normal`] = isAdditional ? 1 : -1; | ||||
| 	} | ||||
|  | ||||
| 	await update(inc); | ||||
| } | ||||
|  | ||||
| export async function updateDriveStats(file: IDriveFile, isAdditional: boolean) { | ||||
| 	const amount = isAdditional ? 1 : -1; | ||||
| 	const size = isAdditional ? file.length : -file.length; | ||||
| 	const origin = isLocalUser(file.metadata._user) ? 'local' : 'remote'; | ||||
|  | ||||
| 	const inc = {} as any; | ||||
| 	inc[`drive.${origin}.totalCount`] = amount; | ||||
| 	inc[`drive.${origin}.diffCount`] = amount; | ||||
| 	inc[`drive.${origin}.totalSize`] = size; | ||||
| 	inc[`drive.${origin}.diffSize`] = size; | ||||
| 	inc[`drive.${origin}.totalCount`] = isAdditional ? 1 : -1; | ||||
| 	inc[`drive.${origin}.totalSize`] = isAdditional ? file.length : -file.length; | ||||
| 	if (isAdditional) { | ||||
| 		inc[`drive.${origin}.incCount`] = 1; | ||||
| 		inc[`drive.${origin}.incSize`] = file.length; | ||||
| 	} else { | ||||
| 		inc[`drive.${origin}.decCount`] = 1; | ||||
| 		inc[`drive.${origin}.decSize`] = file.length; | ||||
| 	} | ||||
|  | ||||
| 	await update(inc); | ||||
| } | ||||
|   | ||||
| @@ -8,7 +8,7 @@ export const replacement = (ctx: any, _: any, key: string) => { | ||||
| 	const client = '/src/client/app/'; | ||||
| 	let name = null; | ||||
|  | ||||
| 	if (key[0] == '@') { | ||||
| 	if (key.startsWith('@')) { | ||||
| 		name = ctx.src.substr(ctx.src.indexOf(client) + client.length); | ||||
| 		key = key.substr(1); | ||||
| 	} | ||||
|   | ||||
| @@ -7,7 +7,7 @@ function trim(text, g) { | ||||
| export default function(src) { | ||||
| 	const fn = options => { | ||||
| 		const search = options.search; | ||||
| 		const g = search[search.length - 1] == 'g'; | ||||
| 		const g = search.endsWith('g'); | ||||
| 		const file = this.resourcePath.replace(/\\/g, '/'); | ||||
| 		const replace = options.i18n ? global[options.replace].bind(null, { | ||||
| 			src: file, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user