Compare commits

..

206 Commits

Author SHA1 Message Date
syuilo
f24d202024 10.30.3 2018-10-23 14:52:25 +09:00
syuilo
d3e0b8574b 🎨 2018-10-23 14:44:26 +09:00
syuilo
f4482cc34a 🎨 2018-10-23 14:33:00 +09:00
syuilo
3ff226cd6b 🎨 2018-10-23 14:28:15 +09:00
syuilo
5c0d37d021 10.30.2 2018-10-23 12:59:17 +09:00
syuilo
b958959cca Merge pull request #2990 from syuilo/l10n_develop
New Crowdin translations
2018-10-23 12:58:10 +09:00
syuilo
762418d0fa New translations ja-JP.yml (English) 2018-10-23 12:51:51 +09:00
syuilo
6831f0c192 New translations ja-JP.yml (Norwegian) 2018-10-23 12:42:47 +09:00
syuilo
64635fff2d New translations ja-JP.yml (Dutch) 2018-10-23 12:42:42 +09:00
syuilo
e7e861fb5c New translations ja-JP.yml (Japanese, Kansai) 2018-10-23 12:42:37 +09:00
syuilo
08523ce271 New translations ja-JP.yml (Spanish) 2018-10-23 12:42:31 +09:00
syuilo
833f63c1a9 New translations ja-JP.yml (Russian) 2018-10-23 12:42:26 +09:00
syuilo
1c05825bc8 New translations ja-JP.yml (Portuguese) 2018-10-23 12:42:21 +09:00
syuilo
26bb088a3d New translations ja-JP.yml (Polish) 2018-10-23 12:42:17 +09:00
syuilo
5c361cef23 New translations ja-JP.yml (Korean) 2018-10-23 12:42:12 +09:00
syuilo
04bef96aee New translations ja-JP.yml (Italian) 2018-10-23 12:42:06 +09:00
syuilo
a791981da9 New translations ja-JP.yml (German) 2018-10-23 12:42:01 +09:00
syuilo
264c47e07a New translations ja-JP.yml (French) 2018-10-23 12:41:55 +09:00
syuilo
863c44d15c New translations ja-JP.yml (English) 2018-10-23 12:41:51 +09:00
syuilo
cdec6f202e New translations ja-JP.yml (Chinese Simplified) 2018-10-23 12:41:46 +09:00
syuilo
bdf6c739a9 New translations ja-JP.yml (Catalan) 2018-10-23 12:41:40 +09:00
syuilo
843dd5fb58 Improve user column 2018-10-23 12:32:24 +09:00
syuilo
c05853289a 10.30.1 2018-10-23 10:01:22 +09:00
syuilo
11c5d257f2 ハッシュタグチャートでローカルとリモートを分離するように 2018-10-23 09:59:43 +09:00
syuilo
cee1a27348 🎨 2018-10-23 09:41:28 +09:00
syuilo
690dc75e45 🎨 2018-10-23 09:39:27 +09:00
syuilo
8dc82b7a6e 10.30.0 2018-10-23 07:46:50 +09:00
syuilo
a396b519bb Merge pull request #2988 from syuilo/l10n_develop
New Crowdin translations
2018-10-23 07:17:31 +09:00
syuilo
d5f9ce0893 New translations ja-JP.yml (Norwegian) 2018-10-23 07:14:38 +09:00
syuilo
c1d7ae99ab New translations ja-JP.yml (Dutch) 2018-10-23 07:14:34 +09:00
syuilo
d8aee7c310 New translations ja-JP.yml (Japanese, Kansai) 2018-10-23 07:14:30 +09:00
syuilo
3e43d847ca New translations ja-JP.yml (Spanish) 2018-10-23 07:14:24 +09:00
syuilo
70273931b2 New translations ja-JP.yml (Russian) 2018-10-23 07:14:18 +09:00
syuilo
cc94d2acc5 New translations ja-JP.yml (Portuguese) 2018-10-23 07:14:13 +09:00
syuilo
327d9702ca New translations ja-JP.yml (Polish) 2018-10-23 07:14:08 +09:00
syuilo
1cdb285fe6 New translations ja-JP.yml (Korean) 2018-10-23 07:14:03 +09:00
syuilo
e9e61e3034 New translations ja-JP.yml (Italian) 2018-10-23 07:13:56 +09:00
syuilo
b613a51035 New translations ja-JP.yml (German) 2018-10-23 07:13:52 +09:00
syuilo
63e62ecb02 New translations ja-JP.yml (French) 2018-10-23 07:13:47 +09:00
syuilo
d11122af3f New translations ja-JP.yml (English) 2018-10-23 07:13:43 +09:00
syuilo
e8ddb7f6ee New translations ja-JP.yml (Chinese Simplified) 2018-10-23 07:13:39 +09:00
syuilo
5ad0a158bc New translations ja-JP.yml (Catalan) 2018-10-23 07:13:34 +09:00
syuilo
e3ea29a8b6 fix(package): update systeminformation to version 3.45.9 (#2987)
Closes #2986
2018-10-23 07:12:27 +09:00
nico
ead201ac3d More missing i18n stuff (#2981)
* More missing i18n stuff

Not tested, please check before merege

* Add missing colons

* Revert some changes
2018-10-23 07:11:56 +09:00
syuilo
19af2d7a7b Implement #2983 2018-10-23 07:04:00 +09:00
syuilo
8ba87443ca Use camelCase instead of snake_case 2018-10-23 07:01:43 +09:00
syuilo
162ace2fd6 Improve some API definitions 2018-10-23 06:59:52 +09:00
syuilo
f51fdc0dbf Update src/client/app/desktop/views/pages/deck/deck.user-column.vue 2018-10-23 06:49:23 +09:00
syuilo
d3d612a89b Resolve #2978 2018-10-23 06:47:06 +09:00
syuilo
7c7f32d9a6 Refactoring 2018-10-23 05:36:35 +09:00
greenkeeper[bot]
c8b6b6e44f fix(package): update file-type to version 10.1.0 (#2984) 2018-10-23 04:51:47 +09:00
かひわし4(バージョン1)
12daa80071 Fix build error for Docker (#2982) 2018-10-23 04:39:00 +09:00
MeiMei
2f8cc36d4b Complement file extension from MIME (#2979) 2018-10-23 04:37:37 +09:00
syuilo
1af4f94338 Implement #2961 2018-10-22 22:00:54 +09:00
syuilo
172a0a85aa Show chart in user column 2018-10-22 22:00:32 +09:00
syuilo
d37c06884d 🎨 2018-10-22 20:06:55 +09:00
syuilo
80e52c57e1 Fix #2958 2018-10-22 18:23:20 +09:00
syuilo
213a7f137e 🎨 2018-10-22 18:19:25 +09:00
syuilo
4848b71ca0 Update src/docs/stream.ja-JP.md 2018-10-22 18:08:26 +09:00
syuilo
13bad106cc Implement some chart APIs 2018-10-22 17:37:55 +09:00
syuilo
3bebf82501 Implement #2980 2018-10-22 17:36:36 +09:00
syuilo
e9a8090d7e Refactor 2018-10-22 17:13:06 +09:00
syuilo
e2a79abbe0 Doc: Better parameter description 2018-10-22 17:06:53 +09:00
syuilo
d7f57a4415 Improve usability 2018-10-22 16:58:22 +09:00
syuilo
9dd5ed7f1a Refactor 2018-10-22 16:51:45 +09:00
syuilo
432e18a0c0 Update doc 2018-10-22 11:59:15 +09:00
syuilo
9a2d435cb1 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-10-22 10:23:20 +09:00
syuilo
b02274c178 Use router-link instead of a to improve usability 2018-10-22 10:22:07 +09:00
syuilo
91408bceb1 Merge pull request #2960 from syuilo/l10n_develop
New Crowdin translations
2018-10-22 09:29:53 +09:00
syuilo
e1fd7e3f0c New translations ja-JP.yml (English) 2018-10-22 06:01:16 +09:00
syuilo
d18498cb6b New translations ja-JP.yml (German) 2018-10-22 05:51:47 +09:00
syuilo
b3986b8963 New translations ja-JP.yml (English) 2018-10-22 05:51:43 +09:00
syuilo
75e3d6f7fb New translations ja-JP.yml (English) 2018-10-22 05:31:54 +09:00
syuilo
ded78aa294 New translations ja-JP.yml (Norwegian) 2018-10-22 05:23:02 +09:00
syuilo
58e8938364 New translations ja-JP.yml (Dutch) 2018-10-22 05:22:57 +09:00
syuilo
6e8e6c7352 New translations ja-JP.yml (Japanese, Kansai) 2018-10-22 05:22:53 +09:00
syuilo
270de03646 New translations ja-JP.yml (Spanish) 2018-10-22 05:22:48 +09:00
syuilo
b6c7ff109b New translations ja-JP.yml (Russian) 2018-10-22 05:22:43 +09:00
syuilo
9b72a5a46d New translations ja-JP.yml (Portuguese) 2018-10-22 05:22:39 +09:00
syuilo
626e06c5fd New translations ja-JP.yml (Polish) 2018-10-22 05:22:35 +09:00
syuilo
b09d10ac52 New translations ja-JP.yml (Korean) 2018-10-22 05:22:30 +09:00
syuilo
d1568cda19 New translations ja-JP.yml (Italian) 2018-10-22 05:22:25 +09:00
syuilo
3400b4fa0d New translations ja-JP.yml (German) 2018-10-22 05:22:20 +09:00
syuilo
4455f110b1 New translations ja-JP.yml (French) 2018-10-22 05:22:16 +09:00
syuilo
25fc37449b New translations ja-JP.yml (English) 2018-10-22 05:22:11 +09:00
syuilo
e5ffc7c492 New translations ja-JP.yml (Chinese Simplified) 2018-10-22 05:22:08 +09:00
syuilo
5c118e6d8a New translations ja-JP.yml (Catalan) 2018-10-22 05:22:02 +09:00
syuilo
b49c70e67e Update locales/ja-JP.yml 2018-10-22 05:18:05 +09:00
syuilo
3760fdeed0 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-10-22 05:16:42 +09:00
syuilo
3aece449e4 Improve API definitions 2018-10-22 05:16:27 +09:00
syuilo
dcd2d8be77 New translations ja-JP.yml (Norwegian) 2018-10-22 05:12:54 +09:00
syuilo
b90e6f9abb New translations ja-JP.yml (Dutch) 2018-10-22 05:12:49 +09:00
syuilo
d984652aa1 New translations ja-JP.yml (Japanese, Kansai) 2018-10-22 05:12:45 +09:00
syuilo
f176de6d2e New translations ja-JP.yml (Spanish) 2018-10-22 05:12:39 +09:00
syuilo
ef31efabb2 New translations ja-JP.yml (Russian) 2018-10-22 05:12:35 +09:00
syuilo
53763acb76 New translations ja-JP.yml (Portuguese) 2018-10-22 05:12:30 +09:00
syuilo
6f39010133 New translations ja-JP.yml (Polish) 2018-10-22 05:12:24 +09:00
syuilo
04b5fe6af4 New translations ja-JP.yml (Korean) 2018-10-22 05:12:19 +09:00
syuilo
626f43f424 New translations ja-JP.yml (Italian) 2018-10-22 05:12:14 +09:00
syuilo
bebcc72deb New translations ja-JP.yml (German) 2018-10-22 05:12:08 +09:00
syuilo
9f285779ec New translations ja-JP.yml (French) 2018-10-22 05:12:04 +09:00
syuilo
57d3e9fc32 New translations ja-JP.yml (English) 2018-10-22 05:12:00 +09:00
syuilo
84cf09c1d0 New translations ja-JP.yml (Chinese Simplified) 2018-10-22 05:11:56 +09:00
syuilo
0848bad960 New translations ja-JP.yml (Catalan) 2018-10-22 05:11:52 +09:00
nico
c1b13c3b5b #2939 part 4 (#2977) 2018-10-22 05:04:33 +09:00
syuilo
8abc4ed65a New translations ja-JP.yml (Norwegian) 2018-10-22 05:03:14 +09:00
syuilo
0eebe620cb New translations ja-JP.yml (Dutch) 2018-10-22 05:03:10 +09:00
syuilo
62a0d87795 New translations ja-JP.yml (Japanese, Kansai) 2018-10-22 05:03:06 +09:00
syuilo
8318633749 New translations ja-JP.yml (Spanish) 2018-10-22 05:03:01 +09:00
syuilo
a453f8aa2e New translations ja-JP.yml (Russian) 2018-10-22 05:02:57 +09:00
syuilo
54d2b90c25 New translations ja-JP.yml (Portuguese) 2018-10-22 05:02:53 +09:00
syuilo
7e1865984d New translations ja-JP.yml (Polish) 2018-10-22 05:02:48 +09:00
syuilo
a2c56cc112 New translations ja-JP.yml (Korean) 2018-10-22 05:02:43 +09:00
syuilo
5c0ee8ca48 New translations ja-JP.yml (Italian) 2018-10-22 05:02:38 +09:00
syuilo
7397b2b82b New translations ja-JP.yml (German) 2018-10-22 05:02:33 +09:00
syuilo
ddcbe21ce6 New translations ja-JP.yml (French) 2018-10-22 05:02:27 +09:00
syuilo
8fc7d1377d New translations ja-JP.yml (English) 2018-10-22 05:02:22 +09:00
syuilo
092403f362 New translations ja-JP.yml (Chinese Simplified) 2018-10-22 05:02:18 +09:00
syuilo
bb179922b9 New translations ja-JP.yml (Catalan) 2018-10-22 05:02:13 +09:00
nico
c29f912461 #2939 part 3 (#2976) 2018-10-22 04:59:27 +09:00
nico
83d3e1cfe6 #2939 part 2 (#2975)
I hope it's correct
2018-10-22 04:58:18 +09:00
nico
2914f0f65d #2939 (#2974) 2018-10-22 04:47:12 +09:00
syuilo
99aa588ae7 Implement per user drive stats 2018-10-22 04:44:10 +09:00
syuilo
0085e1f3ab Fix bug 2018-10-22 04:36:57 +09:00
syuilo
53a9eb13f8 Fix bug 2018-10-22 04:31:45 +09:00
syuilo
b8c56c4dda Implemet per user notes stats 2018-10-22 04:30:27 +09:00
syuilo
59266b3190 Fix bug 2018-10-22 03:41:50 +09:00
syuilo
0dc94547f5 Fix bug 2018-10-22 03:34:56 +09:00
syuilo
29fc6de330 Refactor 2018-10-22 03:30:45 +09:00
greenkeeper[bot]
e24d0c40cd fix(package): update webpack to version 4.22.0 (#2969) 2018-10-22 02:28:08 +09:00
MeiMei
e95845777a Fix og:image on user page (#2972) 2018-10-22 02:27:45 +09:00
MeiMei
167648f61c Use thumbnail instead of original in photo-stream (#2971) 2018-10-22 02:27:23 +09:00
syuilo
9e6d6ff0dd New translations ja-JP.yml (English) 2018-10-21 22:01:18 +09:00
syuilo
e659cc3d58 New translations ja-JP.yml (Japanese, Kansai) 2018-10-21 21:41:24 +09:00
syuilo
ff6d45571a New translations ja-JP.yml (English) 2018-10-21 21:41:20 +09:00
syuilo
6cc9a2c945 New translations ja-JP.yml (Japanese, Kansai) 2018-10-21 21:31:16 +09:00
syuilo
a873401bd7 New translations ja-JP.yml (English) 2018-10-21 21:31:11 +09:00
syuilo
6b19745241 New translations ja-JP.yml (English) 2018-10-21 20:02:04 +09:00
syuilo
982fae80aa New translations ja-JP.yml (English) 2018-10-21 19:21:37 +09:00
syuilo
77b15a3535 Update stats.ts 2018-10-21 18:43:45 +09:00
MeiMei
72754ede4e Fix several file processings (#2968)
* Ignore image error in person

* Fix hang while processing empty file
2018-10-21 18:35:36 +09:00
syuilo
b8ed8336e0 Improve readability 2018-10-21 17:58:02 +09:00
syuilo
13f82856f9 Implement following stats 2018-10-21 17:51:35 +09:00
syuilo
a62013f54d Refactor 2018-10-21 17:28:27 +09:00
syuilo
4c180869c6 Imprement hashtag stats 2018-10-21 16:54:07 +09:00
syuilo
7bbf022978 Refactor 2018-10-21 16:18:02 +09:00
syuilo
6b0d48423d Refactor 2018-10-21 15:08:07 +09:00
syuilo
a617b8dbed Refactor 2018-10-21 14:53:04 +09:00
syuilo
c57f472caf Improve readability 2018-10-21 14:47:44 +09:00
syuilo
e1ba19fd7e Improve readability 2018-10-21 14:44:37 +09:00
syuilo
1bf8cbeb29 Clean up 2018-10-21 14:15:02 +09:00
syuilo
f13faf2243 Refactoring & Better stats aggregation 2018-10-21 14:08:05 +09:00
syuilo
6cccd9d288 Implement unique incremebt 2018-10-21 12:37:00 +09:00
syuilo
be2cde106b Update stats.ts 2018-10-21 10:35:37 +09:00
syuilo
17263fb459 ✌️ 2018-10-21 10:24:56 +09:00
syuilo
fed04ef5ae Reduce network traffic of API response 2018-10-21 10:05:15 +09:00
syuilo
969b6dbcad Resolve #2963 2018-10-21 09:20:11 +09:00
syuilo
aa50d0ee11 🎨 2018-10-21 08:47:34 +09:00
zwebmedia
f09999ad5a Update README.md (#2965)
Improve instructions documentation.
2018-10-21 07:45:35 +09:00
zwebmedia
35814faf8a Update user.header.vue (#2964)
Use new birthday translations in user.header.vue
2018-10-21 07:34:50 +09:00
syuilo
8447a7fafa New translations ja-JP.yml (Norwegian) 2018-10-21 07:32:08 +09:00
syuilo
c6e6c5e3ce New translations ja-JP.yml (Dutch) 2018-10-21 07:32:02 +09:00
syuilo
85cbd8dd47 New translations ja-JP.yml (Japanese, Kansai) 2018-10-21 07:31:58 +09:00
syuilo
bebc9003a3 New translations ja-JP.yml (Spanish) 2018-10-21 07:31:53 +09:00
syuilo
3c081fbd65 New translations ja-JP.yml (Russian) 2018-10-21 07:31:49 +09:00
syuilo
fdcf874306 New translations ja-JP.yml (Portuguese) 2018-10-21 07:31:44 +09:00
syuilo
6cbb741fa1 New translations ja-JP.yml (Polish) 2018-10-21 07:31:41 +09:00
syuilo
24129c1cb9 New translations ja-JP.yml (Korean) 2018-10-21 07:31:37 +09:00
syuilo
f0938c36f5 New translations ja-JP.yml (Italian) 2018-10-21 07:31:33 +09:00
syuilo
484a6eda2e New translations ja-JP.yml (German) 2018-10-21 07:31:27 +09:00
syuilo
3f2ebffbe7 New translations ja-JP.yml (French) 2018-10-21 07:31:23 +09:00
syuilo
ff278a7d8f New translations ja-JP.yml (English) 2018-10-21 07:31:19 +09:00
syuilo
844a3c3aff New translations ja-JP.yml (Chinese Simplified) 2018-10-21 07:31:16 +09:00
syuilo
0db48993e9 New translations ja-JP.yml (Catalan) 2018-10-21 07:31:10 +09:00
zwebmedia
81e21c4314 Birthday translations (#2962)
Add language terms for birthday years, months, days, years old.
2018-10-21 07:24:28 +09:00
syuilo
ba0e57396d Refactoring 2018-10-21 07:10:35 +09:00
syuilo
6a728d160a New translations ja-JP.yml (Norwegian) 2018-10-21 04:12:12 +09:00
syuilo
180e507bc8 New translations ja-JP.yml (Dutch) 2018-10-21 04:12:09 +09:00
syuilo
f3b7611ded New translations ja-JP.yml (Japanese, Kansai) 2018-10-21 04:12:04 +09:00
syuilo
c344de5546 New translations ja-JP.yml (Spanish) 2018-10-21 04:11:59 +09:00
syuilo
0bd0aa2bf7 New translations ja-JP.yml (Russian) 2018-10-21 04:11:53 +09:00
syuilo
c786cbb3a1 New translations ja-JP.yml (Portuguese) 2018-10-21 04:11:48 +09:00
syuilo
cd856f653d New translations ja-JP.yml (Polish) 2018-10-21 04:11:44 +09:00
syuilo
d528c09da6 New translations ja-JP.yml (Korean) 2018-10-21 04:11:40 +09:00
syuilo
76b7ad006d New translations ja-JP.yml (Italian) 2018-10-21 04:11:36 +09:00
syuilo
ff33e405a3 New translations ja-JP.yml (German) 2018-10-21 04:11:33 +09:00
syuilo
f74de26d63 New translations ja-JP.yml (French) 2018-10-21 04:11:29 +09:00
syuilo
2c823798d8 New translations ja-JP.yml (English) 2018-10-21 04:11:24 +09:00
syuilo
381e261bbb New translations ja-JP.yml (Chinese Simplified) 2018-10-21 04:11:20 +09:00
syuilo
ba9bb5db6c New translations ja-JP.yml (Catalan) 2018-10-21 04:11:15 +09:00
syuilo
cd12bb33a5 Fix: Remove duplicated key 2018-10-21 04:01:35 +09:00
syuilo
e333aee232 New translations ja-JP.yml (Norwegian) 2018-10-21 03:02:48 +09:00
syuilo
54571f60c3 New translations ja-JP.yml (Dutch) 2018-10-21 03:02:45 +09:00
syuilo
dd743aaeac New translations ja-JP.yml (Japanese, Kansai) 2018-10-21 03:02:41 +09:00
syuilo
22c76dc9f8 New translations ja-JP.yml (Spanish) 2018-10-21 03:02:35 +09:00
syuilo
7c7e09cf64 New translations ja-JP.yml (Russian) 2018-10-21 03:02:31 +09:00
syuilo
e5e3d69371 New translations ja-JP.yml (Portuguese) 2018-10-21 03:02:27 +09:00
syuilo
82a700b24e New translations ja-JP.yml (Polish) 2018-10-21 03:02:24 +09:00
syuilo
0579425a4f New translations ja-JP.yml (Korean) 2018-10-21 03:02:18 +09:00
syuilo
218e74569d New translations ja-JP.yml (Italian) 2018-10-21 03:02:14 +09:00
syuilo
448f54cf84 New translations ja-JP.yml (German) 2018-10-21 03:02:09 +09:00
syuilo
c139e13049 New translations ja-JP.yml (French) 2018-10-21 03:02:05 +09:00
syuilo
65116fef32 New translations ja-JP.yml (English) 2018-10-21 03:02:00 +09:00
syuilo
a0a35b7dca New translations ja-JP.yml (Chinese Simplified) 2018-10-21 03:01:56 +09:00
syuilo
11fb8a24b7 New translations ja-JP.yml (Catalan) 2018-10-21 03:01:50 +09:00
Dr. Gutfuck LLC
512336685c Localized dev/views/new-app.vue (#2959)
* Localized read-all message

* Fixed some weirdness:   src/client/app/init.ts

* Unfucked server api stuff (sorry lol):   src/server/api/endpoints/i/read_all_unread_notes.ts

* Clean up

* Added localization lines to:   locales/ja-JP.yml
Localized:   src/client/app/dev/views/apps.vue

* Fix potential error:   src/client/app/dev/views/apps.vue

* Added relevant localization lines:   locales/ja-JP.yml
Localized:   src/client/app/dev/views/new-app.vue
2018-10-21 03:01:09 +09:00
112 changed files with 3226 additions and 1497 deletions

View File

@@ -1,4 +1,4 @@
FROM alpine:latest AS base
FROM alpine:edge AS base
ENV NODE_ENV=production

View File

@@ -1,3 +1,6 @@
# **DO NOT edit locale files** except `ja-JP.yml`.
When you add text to the ja-JP file (of syuilo/misskey), it will automatically be applied to other language files.
Translations added in ja-JP file should contain the original Japanese strings.
Please see [Contribution guide](../CONTRIBUTING.md) for more information.

View File

@@ -184,6 +184,7 @@ common:
rename: "名前を変更"
stack-left: "左に重ねる"
pop-right: "右に出す"
dev: "アプリの作成に失敗しました。再度お試しください。"
auth/views/form.vue:
share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
permission-ask: "このアプリは次の権限を要求しています:"
@@ -938,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
@@ -945,6 +948,9 @@ desktop/views/pages/admin/admin.dashboard.vue:
all-notes: "全ての投稿"
original-notes: "このインスタンスの投稿"
invite: "招待"
banner-url: "Banner URL"
disableRegistration: "Disable new user registration"
disableLocalTimeline: "Disable the local timeline"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@@ -961,12 +967,23 @@ desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.announcements.vue:
announcements: "お知らせ"
desktop/views/pages/admin/admin.hashtags.vue:
hided-tags: "Hidden Tags"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
desktop/views/pages/deck/deck.user-column.vue:
posts: "投稿"
following: "フォロー"
followers: "フォロワー"
images: "画像"
activity: "アクティビティ"
timeline: "タイムライン"
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1019,7 +1036,6 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よく話すユーザーはいません"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "フォト"
@@ -1040,6 +1056,10 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー"
followers: "フォロワー"
is-bot: "このアカウントはBotです"
years-old: "歳"
year: "年"
month: "月"
day: "日"
desktop/views/pages/user/user.timeline.vue:
default: "投稿"
with-replies: "投稿と返信"
@@ -1296,6 +1316,7 @@ mobile/views/pages/settings.vue:
signout: "サインアウト"
sound: "サウンド"
enable-sounds: "サウンドを有効にする"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "フォローされています"
following: "フォロー"
@@ -1350,3 +1371,29 @@ docs:
description: "説明"
dev/views/index.vue:
manage-apps: "アプリの管理"
dev/views/apps.vue:
manage-apps: "アプリを管理"
create-app: "アプリ作成"
app-missing: "アプリなし"
dev/views/new-app.vue:
create-app: "アプリケーションの作成"
app-name: "アプリケーション名"
app-name-desc: "あなたのアプリの名称。"
app-name-ex: "ex) Misskey for iOS"
app-overview: "アプリの概要"
app-desc: "あなたのアプリの簡単な説明や紹介。"
app-desc-ex: "ex) Misskey iOSクライアント。"
callback-url: "コールバックURL (オプション)"
callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。"
authority: "権限"
authority-desc: "ここで要求した機能だけがAPIからアクセスできます。"
authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。"
account-read: "アカウントの情報を見る。"
account-write: "アカウントの情報を操作する。"
note-write: "投稿する。"
reaction-write: "リアクションしたりリアクションをキャンセルする。"
following-write: "フォローしたりフォロー解除する。"
drive-read: "ドライブを見る。"
drive-write: "ドライブを操作する。"
notification-read: "通知を見る。"
notification-write: "通知を操作する。"

View File

@@ -20,19 +20,19 @@ common:
drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんかもしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんかMisskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
adblock:
detected: "Bitte deaktivieren Sie den Werbeblocker."
detected: "Bitte deaktiviere den Werbeblocker."
warning: "<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。"
application-authorization: "Autorisierte Anwendungen"
close: "Schließen"
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
BSoD:
fatal-error: ":( 致命的な問題が発生しました。"
fatal-error: "Ein schwerwiegender Fehler ist aufgetreten :("
update-browser-os: "お使いのブラウザ(またはOS)のバージョンを更新すると解決する可能性があります。"
error-code: "エラーコード"
browser-version: "ブラウザ バージョン"
client-version: "クライアント バージョン"
error-code: "Fehlercode"
browser-version: "Browserversion"
client-version: "Clientversion"
email-support: "問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。"
thanks: "Thank you for using Misskey."
thanks: "Vielen Dank dass du Misskey verwendest."
got-it: "Verstanden!"
customization-tips:
title: "Anpassung-Tipps"
@@ -128,7 +128,7 @@ common:
view-on-remote: "正確な情報を見る"
error:
title: '問題が発生しました'
retry: 'やり直す'
retry: 'Erneut versuchen'
reversi:
drawn: "Unentschieden"
my-turn: "Du bist am Zug"
@@ -168,7 +168,7 @@ common:
widgets: "Widget hinzufügen:"
home: "Startseite"
local: "Lokal"
hybrid: "ソーシャル"
hybrid: "Sozial"
hashtag: "Hashtag"
global: "Global"
mentions: "Erwähnungen"
@@ -183,7 +183,8 @@ common:
add-column: "Eine Spalte hinzufügen"
rename: "Umbenennen"
stack-left: "Nach links schichten"
pop-right: "右に出す"
pop-right: "Rechts andocken"
dev: "Fehler beim Erstellen der Applikation. Bitte versuche es erneut."
auth/views/form.vue:
share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
permission-ask: "このアプリは次の権限を要求しています:"
@@ -195,7 +196,7 @@ auth/views/form.vue:
drive-read: "ドライブを見る。"
drive-write: "ドライブを操作する。"
notification-read: "通知を見る。"
notification-write: "通知を操作する。"
notification-write: "Benachrichtigungen verwalten."
cancel: "Abbrechen"
accept: "Zugriff erlauben."
auth/views/index.vue:
@@ -205,7 +206,7 @@ auth/views/index.vue:
already-authorized: "Diese Anwendung ist bereits autorisiert."
allowed: "Autorisierung der Anwendung wurde erlaubt."
callback-url: "アプリケーションに戻っています"
please-go-back: "Bitte gehen Sie zurück zur Anwendung."
please-go-back: "Bitte gehe zurück zur Anwendung."
error: "Sitzung ist nicht vorhanden."
sign-in: "Bitte melde dich an."
common/views/components/games/reversi/reversi.vue:
@@ -348,8 +349,8 @@ common/views/components/nav.vue:
common/views/components/note-menu.vue:
detail: "詳細"
copy-link: "リンクをコピー"
favorite: "Diese Anmerkung favorisieren"
unfavorite: "Entfavorisieren"
favorite: "Diese Notiz favorisieren"
unfavorite: "Aus Favoriten entfernen"
pin: "An die Profilseite pinnen"
unpin: "ピン留め解除"
delete: "Löschen"
@@ -591,7 +592,7 @@ desktop/views/components/drive.file.vue:
open-in-app: "In der App öffnen"
add-app: "App hinzufügen"
rename-file: "Datei umbennen"
input-new-file-name: "Geben Sie den neuen Dateinamen an"
input-new-file-name: "Gib den neuen Dateinamen an"
copied: "Kopieren erfolgreich"
copied-url-to-clipboard: "URL wurde in die Zwischenablage kopiert"
desktop/views/components/drive.folder.vue:
@@ -938,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
@@ -945,6 +948,9 @@ desktop/views/pages/admin/admin.dashboard.vue:
all-notes: "全ての投稿"
original-notes: "このインスタンスの投稿"
invite: "招待"
banner-url: "Banner URL"
disableRegistration: "Disable new user registration"
disableLocalTimeline: "Disable the local timeline"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@@ -961,12 +967,23 @@ desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.announcements.vue:
announcements: "お知らせ"
desktop/views/pages/admin/admin.hashtags.vue:
hided-tags: "Hidden Tags"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
desktop/views/pages/deck/deck.user-column.vue:
posts: "投稿"
following: "フォロー"
followers: "フォロワー"
images: "画像"
activity: "アクティビティ"
timeline: "タイムライン"
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1019,7 +1036,6 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よく話すユーザーはいません"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "フォト"
@@ -1040,6 +1056,10 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー"
followers: "フォロワー"
is-bot: "このアカウントはBotです"
years-old: "歳"
year: "年"
month: "月"
day: "日"
desktop/views/pages/user/user.timeline.vue:
default: "投稿"
with-replies: "投稿と返信"
@@ -1296,6 +1316,7 @@ mobile/views/pages/settings.vue:
signout: "サインアウト"
sound: "サウンド"
enable-sounds: "サウンドを有効にする"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "フォローされています"
following: "フォロー"
@@ -1350,3 +1371,29 @@ docs:
description: "説明"
dev/views/index.vue:
manage-apps: "アプリの管理"
dev/views/apps.vue:
manage-apps: "アプリを管理"
create-app: "アプリ作成"
app-missing: "アプリなし"
dev/views/new-app.vue:
create-app: "アプリケーションの作成"
app-name: "アプリケーション名"
app-name-desc: "あなたのアプリの名称。"
app-name-ex: "ex) Misskey for iOS"
app-overview: "アプリの概要"
app-desc: "あなたのアプリの簡単な説明や紹介。"
app-desc-ex: "ex) Misskey iOSクライアント。"
callback-url: "コールバックURL (オプション)"
callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。"
authority: "権限"
authority-desc: "ここで要求した機能だけがAPIからアクセスできます。"
authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。"
account-read: "アカウントの情報を見る。"
account-write: "アカウントの情報を操作する。"
note-write: "投稿する。"
reaction-write: "リアクションしたりリアクションをキャンセルする。"
following-write: "フォローしたりフォロー解除する。"
drive-read: "ドライブを見る。"
drive-write: "ドライブを操作する。"
notification-read: "通知を見る。"
notification-write: "通知を操作する。"

View File

@@ -184,6 +184,7 @@ common:
rename: "Rename"
stack-left: "Stack to the left"
pop-right: "Dock on the right"
dev: "Failed to create the application. Please try again."
auth/views/form.vue:
share-access: "Would you <b>allow</b> <i>{{ app.name }}</i> to access your account?"
permission-ask: "This application requires the following permissions:"
@@ -759,8 +760,8 @@ desktop/views/components/settings.vue:
advanced: "Advanced settings"
api-via-stream: "API request via stream"
api-via-stream-desc: "API request is performed via the WebSocket connection instead of native fetch API (for better performance). This setting is stored in the browser."
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
deck-nav: "Transitionless deck navigation"
deck-nav-desc: "You get a temporary column without page transitions during navigation when using the deck."
deck-default: "Use Deck as default UI"
display: "Design and display"
customize: "Customize home layout"
@@ -778,7 +779,7 @@ desktop/views/components/settings.vue:
show-reply-target: "Display reply target"
timeline: "Timeline"
show-my-renotes: "Show my renotes in the timeline"
show-renoted-my-notes: "Show renoted my posts in timelines"
show-renoted-my-notes: "Show renoted posts of mine in timelines"
show-local-renotes: "Show renoted local posts in timelines"
show-maps: "Display a map to show the location"
deck-column-align: "Deck column alignment"
@@ -938,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "Drive"
users: "Users"
update: "Updates"
announcements: "Announcements"
hashtags: "Hashtags"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "Dashboard"
all-users: "All Users"
@@ -945,6 +948,9 @@ desktop/views/pages/admin/admin.dashboard.vue:
all-notes: "All the posts"
original-notes: "Posts on this instance"
invite: "Invite"
banner-url: "Banner URL"
disableRegistration: "Disable new user registration"
disableLocalTimeline: "Disable the local timeline"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "Suspend a user"
suspend: "Suspend"
@@ -961,12 +967,23 @@ 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.announcements.vue:
announcements: "Announcements"
desktop/views/pages/admin/admin.hashtags.vue:
hided-tags: "Hidden Tags"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "Only media posts"
is-media-view: "Media view"
edit: "Options"
desktop/views/pages/deck/deck.user-column.vue:
posts: "Posts"
following: "Following"
followers: "Followers"
images: "Images"
activity: "Activity"
timeline: "Timeline"
pinned-notes: "Pinned posts"
push-to-a-list: "Add to list"
desktop/views/pages/stats/stats.vue:
all-users: "All Users"
original-users: "Users on this instance"
@@ -1019,8 +1036,7 @@ desktop/views/pages/user/user.friends.vue:
no-users: "No frequent mentions"
desktop/views/pages/user/user.vue:
is-suspended: "This account has been suspended."
desktop/views/pages/user/user.home.vue:
last-used-at: "Last active:"
last-used-at: "Last active"
desktop/views/pages/user/user.photos.vue:
title: "Photos"
loading: "Loading"
@@ -1040,6 +1056,10 @@ desktop/views/pages/user/user.header.vue:
following: "Following"
followers: "Followers"
is-bot: "This account is a Bot"
years-old: " years old"
year: "/"
month: "/"
day: "-"
desktop/views/pages/user/user.timeline.vue:
default: "Posts"
with-replies: "Posts and replies"
@@ -1188,7 +1208,7 @@ mobile/views/components/user-timeline.vue:
load-more: "More"
mobile/views/components/users-list.vue:
all: "All"
known: "You know"
known: "In common"
load-more: "More"
mobile/views/pages/favorites.vue:
title: "Favorites"
@@ -1262,7 +1282,7 @@ mobile/views/pages/settings.vue:
timeline: "Timeline"
show-reply-target: "Show reply target"
show-my-renotes: "Show my reposts"
show-renoted-my-notes: "Show renoted my posts"
show-renoted-my-notes: "Show renoted posts of mine"
show-local-renotes: "Show renoted local posts"
post-style: "Post design"
post-style-standard: "Standard"
@@ -1296,6 +1316,7 @@ mobile/views/pages/settings.vue:
signout: "Sign out"
sound: "Sounds"
enable-sounds: "Enable sounds"
mark-as-read-all-unread-notes: "Mark all posts as read"
mobile/views/pages/user.vue:
follows-you: "Follows you"
following: "Following"
@@ -1350,3 +1371,29 @@ docs:
description: "Description"
dev/views/index.vue:
manage-apps: "Manage apps"
dev/views/apps.vue:
manage-apps: "Manage apps"
create-app: "Create app"
app-missing: "No apps"
dev/views/new-app.vue:
create-app: "Creating application"
app-name: "Application name"
app-name-desc: "The name of your app"
app-name-ex: "ex) Misskey for iOS"
app-overview: "Application summary"
app-desc: "A brief description or introduction of your app."
app-desc-ex: "ex) Misskey iOS client."
callback-url: "The callback URL (optional)"
callback-url-desc: "The URL to redirect to after the user is authenticated via the authentication form."
authority: "Permissions"
authority-desc: "Only the functions requested here can be accessed via the API."
authority-warning: "You can change it even after creating the application, but if you give different permissions, all user keys associated at that time will be invalidated."
account-read: "View account information."
account-write: "Modify account information."
note-write: "Post."
reaction-write: "Add or remove reactions."
following-write: "Follow and unfollow."
drive-read: "Read the drive."
drive-write: "Upload/delete files in the drive."
notification-read: "Read your notifications."
notification-write: "Manage your notifications."

View File

@@ -184,6 +184,7 @@ common:
rename: "Renombrar"
stack-left: "A la izqda."
pop-right: "A la dcha."
dev: "アプリの作成に失敗しました。再度お試しください。"
auth/views/form.vue:
share-access: "¿Deseas <b>permitir</b> a <i>{{ app.name }}</i> acceder a tu cuenta?"
permission-ask: "La aplicación requiere los siguientes permisos:"
@@ -938,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
@@ -945,6 +948,9 @@ desktop/views/pages/admin/admin.dashboard.vue:
all-notes: "全ての投稿"
original-notes: "このインスタンスの投稿"
invite: "招待"
banner-url: "Banner URL"
disableRegistration: "Disable new user registration"
disableLocalTimeline: "Disable the local timeline"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@@ -961,12 +967,23 @@ desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.announcements.vue:
announcements: "お知らせ"
desktop/views/pages/admin/admin.hashtags.vue:
hided-tags: "Hidden Tags"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
desktop/views/pages/deck/deck.user-column.vue:
posts: "投稿"
following: "フォロー"
followers: "フォロワー"
images: "画像"
activity: "アクティビティ"
timeline: "タイムライン"
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1019,7 +1036,6 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よく話すユーザーはいません"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "フォト"
@@ -1040,6 +1056,10 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー"
followers: "フォロワー"
is-bot: "このアカウントはBotです"
years-old: "歳"
year: "年"
month: "月"
day: "日"
desktop/views/pages/user/user.timeline.vue:
default: "投稿"
with-replies: "投稿と返信"
@@ -1296,6 +1316,7 @@ mobile/views/pages/settings.vue:
signout: "サインアウト"
sound: "サウンド"
enable-sounds: "サウンドを有効にする"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "フォローされています"
following: "フォロー"
@@ -1350,3 +1371,29 @@ docs:
description: "説明"
dev/views/index.vue:
manage-apps: "アプリの管理"
dev/views/apps.vue:
manage-apps: "アプリを管理"
create-app: "アプリ作成"
app-missing: "アプリなし"
dev/views/new-app.vue:
create-app: "アプリケーションの作成"
app-name: "アプリケーション名"
app-name-desc: "あなたのアプリの名称。"
app-name-ex: "ex) Misskey for iOS"
app-overview: "アプリの概要"
app-desc: "あなたのアプリの簡単な説明や紹介。"
app-desc-ex: "ex) Misskey iOSクライアント。"
callback-url: "コールバックURL (オプション)"
callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。"
authority: "権限"
authority-desc: "ここで要求した機能だけがAPIからアクセスできます。"
authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。"
account-read: "アカウントの情報を見る。"
account-write: "アカウントの情報を操作する。"
note-write: "投稿する。"
reaction-write: "リアクションしたりリアクションをキャンセルする。"
following-write: "フォローしたりフォロー解除する。"
drive-read: "ドライブを見る。"
drive-write: "ドライブを操作する。"
notification-read: "通知を見る。"
notification-write: "通知を操作する。"

View File

@@ -184,6 +184,7 @@ common:
rename: "Renommer"
stack-left: "Vers la gauche"
pop-right: "Vers la droite"
dev: "アプリの作成に失敗しました。再度お試しください。"
auth/views/form.vue:
share-access: "Désirez-vous <b>autoriser</b> <i>{{ app.name }}</i> à avoir accès à votre compte ?"
permission-ask: "Cette application nécessite les autorisations suivantes :"
@@ -938,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "Drive"
users: "Utilisateur·rice·s"
update: "Mises à jour"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "Tableau de bord"
all-users: "Toutes les utilisateurrices"
@@ -945,6 +948,9 @@ desktop/views/pages/admin/admin.dashboard.vue:
all-notes: "Toutes les publications"
original-notes: "Publications sur cette instance"
invite: "Invitation"
banner-url: "Banner URL"
disableRegistration: "Disable new user registration"
disableLocalTimeline: "Disable the local timeline"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "Suspendre un·e utilisateur·rice"
suspend: "Suspendre"
@@ -961,12 +967,23 @@ desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "Ôter la vérification du compte"
unverified: "Ce compte n'est pas vérifié"
desktop/views/pages/admin/admin.announcements.vue:
announcements: "お知らせ"
desktop/views/pages/admin/admin.hashtags.vue:
hided-tags: "Hidden Tags"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "Les publications médias uniquement"
is-media-view: "Vue média"
edit: "Options"
desktop/views/pages/deck/deck.user-column.vue:
posts: "投稿"
following: "フォロー"
followers: "フォロワー"
images: "画像"
activity: "アクティビティ"
timeline: "タイムライン"
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "Toutes les utilisateurrices"
original-users: "Utilisateur·rice·s sur cette instance"
@@ -1019,8 +1036,7 @@ desktop/views/pages/user/user.friends.vue:
no-users: "Pas d'utilisateurs"
desktop/views/pages/user/user.vue:
is-suspended: "Ce compte a été suspendu."
desktop/views/pages/user/user.home.vue:
last-used-at: "Last used at"
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "Photos"
loading: "Chargement en cours"
@@ -1040,6 +1056,10 @@ desktop/views/pages/user/user.header.vue:
following: "Suit"
followers: "Abonné·e·s"
is-bot: "Ce compte est un Bot"
years-old: "歳"
year: "年"
month: "月"
day: "日"
desktop/views/pages/user/user.timeline.vue:
default: "Publications"
with-replies: "Publications et réponses"
@@ -1296,6 +1316,7 @@ mobile/views/pages/settings.vue:
signout: "Déconnexion"
sound: "Sons"
enable-sounds: "Activer les sons"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "Vous suit"
following: "Abonnements"
@@ -1350,3 +1371,29 @@ docs:
description: "Description"
dev/views/index.vue:
manage-apps: "Gestion des applications"
dev/views/apps.vue:
manage-apps: "アプリを管理"
create-app: "アプリ作成"
app-missing: "アプリなし"
dev/views/new-app.vue:
create-app: "アプリケーションの作成"
app-name: "アプリケーション名"
app-name-desc: "あなたのアプリの名称。"
app-name-ex: "ex) Misskey for iOS"
app-overview: "アプリの概要"
app-desc: "あなたのアプリの簡単な説明や紹介。"
app-desc-ex: "ex) Misskey iOSクライアント。"
callback-url: "コールバックURL (オプション)"
callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。"
authority: "権限"
authority-desc: "ここで要求した機能だけがAPIからアクセスできます。"
authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。"
account-read: "アカウントの情報を見る。"
account-write: "アカウントの情報を操作する。"
note-write: "投稿する。"
reaction-write: "リアクションしたりリアクションをキャンセルする。"
following-write: "フォローしたりフォロー解除する。"
drive-read: "ドライブを見る。"
drive-write: "ドライブを操作する。"
notification-read: "通知を見る。"
notification-write: "通知を操作する。"

View File

@@ -184,6 +184,7 @@ common:
rename: "名前を変更"
stack-left: "左に重ねる"
pop-right: "右に出す"
dev: "アプリの作成に失敗しました。再度お試しください。"
auth/views/form.vue:
share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
permission-ask: "このアプリは次の権限を要求しています:"
@@ -938,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
@@ -945,6 +948,9 @@ desktop/views/pages/admin/admin.dashboard.vue:
all-notes: "全ての投稿"
original-notes: "このインスタンスの投稿"
invite: "招待"
banner-url: "Banner URL"
disableRegistration: "Disable new user registration"
disableLocalTimeline: "Disable the local timeline"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@@ -961,12 +967,23 @@ desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.announcements.vue:
announcements: "お知らせ"
desktop/views/pages/admin/admin.hashtags.vue:
hided-tags: "Hidden Tags"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
desktop/views/pages/deck/deck.user-column.vue:
posts: "投稿"
following: "フォロー"
followers: "フォロワー"
images: "画像"
activity: "アクティビティ"
timeline: "タイムライン"
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1019,7 +1036,6 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よく話すユーザーはいません"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "フォト"
@@ -1040,6 +1056,10 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー"
followers: "フォロワー"
is-bot: "このアカウントはBotです"
years-old: "歳"
year: "年"
month: "月"
day: "日"
desktop/views/pages/user/user.timeline.vue:
default: "投稿"
with-replies: "投稿と返信"
@@ -1296,6 +1316,7 @@ mobile/views/pages/settings.vue:
signout: "サインアウト"
sound: "サウンド"
enable-sounds: "サウンドを有効にする"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "フォローされています"
following: "フォロー"
@@ -1350,3 +1371,29 @@ docs:
description: "説明"
dev/views/index.vue:
manage-apps: "アプリの管理"
dev/views/apps.vue:
manage-apps: "アプリを管理"
create-app: "アプリ作成"
app-missing: "アプリなし"
dev/views/new-app.vue:
create-app: "アプリケーションの作成"
app-name: "アプリケーション名"
app-name-desc: "あなたのアプリの名称。"
app-name-ex: "ex) Misskey for iOS"
app-overview: "アプリの概要"
app-desc: "あなたのアプリの簡単な説明や紹介。"
app-desc-ex: "ex) Misskey iOSクライアント。"
callback-url: "コールバックURL (オプション)"
callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。"
authority: "権限"
authority-desc: "ここで要求した機能だけがAPIからアクセスできます。"
authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。"
account-read: "アカウントの情報を見る。"
account-write: "アカウントの情報を操作する。"
note-write: "投稿する。"
reaction-write: "リアクションしたりリアクションをキャンセルする。"
following-write: "フォローしたりフォロー解除する。"
drive-read: "ドライブを見る。"
drive-write: "ドライブを操作する。"
notification-read: "通知を見る。"
notification-write: "通知を操作する。"

View File

@@ -199,6 +199,8 @@ common:
stack-left: "左に重ねる"
pop-right: "右に出す"
dev: "アプリの作成に失敗しました。再度お試しください。"
auth/views/form.vue:
share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
permission-ask: "このアプリは次の権限を要求しています:"
@@ -1061,6 +1063,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
@@ -1069,6 +1073,9 @@ desktop/views/pages/admin/admin.dashboard.vue:
all-notes: "全ての投稿"
original-notes: "このインスタンスの投稿"
invite: "招待"
banner-url: "Banner URL"
disableRegistration: "Disable new user registration"
disableLocalTimeline: "Disable the local timeline"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
@@ -1090,13 +1097,26 @@ desktop/views/pages/admin/admin.unverify-user.vue:
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.announcements.vue:
announcements: "お知らせ"
desktop/views/pages/admin/admin.hashtags.vue:
hided-tags: "Hidden Tags"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
desktop/views/pages/deck/deck.user-column.vue:
posts: "投稿"
following: "フォロー"
followers: "フォロワー"
images: "画像"
activity: "アクティビティ"
timeline: "タイムライン"
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
@@ -1163,8 +1183,6 @@ desktop/views/pages/user/user.friends.vue:
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
@@ -1188,6 +1206,10 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー"
followers: "フォロワー"
is-bot: "このアカウントはBotです"
years-old: "歳"
year: "年"
month: "月"
day: "日"
desktop/views/pages/user/user.timeline.vue:
default: "投稿"
@@ -1496,6 +1518,7 @@ mobile/views/pages/settings.vue:
signout: "サインアウト"
sound: "サウンド"
enable-sounds: "サウンドを有効にする"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "フォローされています"
@@ -1559,3 +1582,31 @@ docs:
dev/views/index.vue:
manage-apps: "アプリの管理"
dev/views/apps.vue:
manage-apps: "アプリを管理"
create-app: "アプリ作成"
app-missing: "アプリなし"
dev/views/new-app.vue:
create-app: "アプリケーションの作成"
app-name: "アプリケーション名"
app-name-desc: "あなたのアプリの名称。"
app-name-ex: "ex) Misskey for iOS"
app-overview: "アプリの概要"
app-desc: "あなたのアプリの簡単な説明や紹介。"
app-desc-ex: "ex) Misskey iOSクライアント。"
callback-url: "コールバックURL (オプション)"
callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。"
authority: "権限"
authority-desc: "ここで要求した機能だけがAPIからアクセスできます。"
authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。"
account-read: "アカウントの情報を見る。"
account-write: "アカウントの情報を操作する。"
note-write: "投稿する。"
reaction-write: "リアクションしたりリアクションをキャンセルする。"
following-write: "フォローしたりフォロー解除する。"
drive-read: "ドライブを見る。"
drive-write: "ドライブを操作する。"
notification-read: "通知を見る。"
notification-write: "通知を操作する。"

View File

@@ -26,13 +26,13 @@ common:
close: "さいなら"
do-not-copy-paste: "ここにコードを入力したり張り付けたりせんといてください。アカウントが不正利用されるかも分からん。知らんけど。"
BSoD:
fatal-error: ":( 致命的な問題が発生しました。"
update-browser-os: "お使いのブラウザ(またはOS)のバージョン更新すると解決する可能性があります。"
fatal-error: "あかん、やってもうたわ… (致命的なエラー"
update-browser-os: "ブラウザ(またはOS)のバージョン更新してくれへん?なおるかもしれんわ。"
error-code: "エラーコード"
browser-version: "ブラウザ バージョン"
client-version: "クライアント バージョン"
email-support: "問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。"
thanks: "Thank you for using Misskey."
email-support: "それでもあかん?せやったら syuilotan@yahoo.co.jp に連絡してや!"
thanks: "Thank you おおきに。Misskey"
got-it: "ほい"
customization-tips:
title: "カスタマイズのヒント"
@@ -125,10 +125,10 @@ common:
do-not-use-in-production: '開発ビルドや。本番環境で使わんといて!知らんで!'
is-remote-user: "このユーザー情報はコピーです。"
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
view-on-remote: "ちゃんとした情報見せてや!"
error:
title: '問題が発生しました'
retry: 'やり直す'
title: '問題が起こったわ'
retry: 'もっぺん'
reversi:
drawn: "おあいこ"
my-turn: "あんさんのターンや"
@@ -184,6 +184,7 @@ common:
rename: "名前を変更や!"
stack-left: "左に重ねんで!"
pop-right: "右に出すで!"
dev: "アプリの作成あかんかったわ。もっぺんやってみて。"
auth/views/form.vue:
share-access: "<i>{{ app.name }}</i>があんさんのアカウントにアクセスすんのを<b>許可</b>してもええか?"
permission-ask: "このアプリは次の権限を要求してんで:"
@@ -439,16 +440,16 @@ common/views/components/profile-editor.vue:
birthday: "誕生日"
avatar: "アイコン"
banner: "バナー"
is-cat: "このアカウントはCatで"
is-bot: "このアカウントはBotで"
is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
is-cat: "このアカウントはCatで"
is-bot: "このアカウントはBotで"
is-locked: "他人のフォローは許可してからや!"
careful-bot: "Botからのフォローだけは許可制や"
advanced: "その他"
privacy: "プライバシー"
privacy: "プライバシーってなんや?オカンの年齢か?"
save: "保存"
saved: "プロフィールを保存しました"
uploading: "アップロード"
upload-failed: "アップロードに失敗しました"
saved: "プロフィールを保存した"
uploading: "アップロードしとります"
upload-failed: "これアップロードでけへんわ"
common/views/widgets/broadcast.vue:
fetching: "見てみるわ…"
no-broadcasts: "お知らせはあらへんで"
@@ -675,12 +676,12 @@ desktop/views/components/note-detail.vue:
add-reaction: "リアクション"
desktop/views/components/note.vue:
reposted-by: "{}がRenote"
reply: "返"
reply: "返"
renote: "Renote"
add-reaction: "リアクション"
detail: "詳細"
private: "この投稿は非公開です"
deleted: "この投稿は削除されました"
detail: "もっと"
private: "この投稿は見せられへんわ"
deleted: "この投稿なんか無くなってもうたわ"
desktop/views/components/notes.vue:
error: "あかん、読み込めへんわ"
retry: "もっぺん"
@@ -760,7 +761,7 @@ desktop/views/components/settings.vue:
api-via-stream: "ストリームを経由したAPIリクエスト"
api-via-stream-desc: "この設定をオンにすると、WebSocket接続を経由してAPIリクエストが行われんで(パフォーマンス向上するかも、知らんけど)。オフにすると、ネイティブの fetch API が利用されるで。この設定はこのデバイスのみ有効やで。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生するにページ遷移を行わずに一時的なカラムで受けるようにします。"
deck-nav-desc: "デッキを使うとるとき、ナビゲーションが発生するときにページ移動せんで、一時的なカラムで受けるようにするで"
deck-default: "デッキをデフォルトのUIにする"
display: "見た感じ"
customize: "ホームをカスタマイズ"
@@ -782,7 +783,7 @@ desktop/views/components/settings.vue:
show-local-renotes: "ローカル投稿のRenoteも見たいんや"
show-maps: "地図勝手にバァーって開いてくれ"
deck-column-align: "デッキのカラムの位置"
deck-column-align-center: "中"
deck-column-align-center: "真ん中"
deck-column-align-left: "左"
sound: "サウンド"
enable-sounds: "サウンド鳴らす"
@@ -938,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "知り合い全員や"
@@ -945,6 +948,9 @@ desktop/views/pages/admin/admin.dashboard.vue:
all-notes: "全ての投稿"
original-notes: "このインスタンスの投稿"
invite: "来てや"
banner-url: "Banner URL"
disableRegistration: "Disable new user registration"
disableLocalTimeline: "Disable the local timeline"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@@ -961,12 +967,23 @@ desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウントにせーへん"
unverify: "公式アカウントにはさせへんで"
unverified: "公式アカウントを解除したで"
desktop/views/pages/admin/admin.announcements.vue:
announcements: "お知らせ"
desktop/views/pages/admin/admin.hashtags.vue:
hided-tags: "Hidden Tags"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿だけや"
is-media-view: "メディアビュー"
edit: "オプション"
desktop/views/pages/deck/deck.user-column.vue:
posts: "投稿"
following: "フォロー"
followers: "フォロワー"
images: "画像"
activity: "アクティビティ"
timeline: "タイムライン"
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "ここの人らだけ"
@@ -1019,8 +1036,7 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よう話すツレは居らん"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーはあかんわ。凍結されとる。"
desktop/views/pages/user/user.home.vue:
last-used-at: "最後いつ来た?"
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "写真"
loading: "読み込んどります"
@@ -1040,6 +1056,10 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー"
followers: "フォロワー"
is-bot: "このアカウントはBotや"
years-old: "歳"
year: "年"
month: "月"
day: "日"
desktop/views/pages/user/user.timeline.vue:
default: "投稿"
with-replies: "投稿と返信"
@@ -1296,6 +1316,7 @@ mobile/views/pages/settings.vue:
signout: "さいなら"
sound: "サウンド"
enable-sounds: "サウンド鳴らす"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "フォローされとるで"
following: "フォロー"
@@ -1350,3 +1371,29 @@ docs:
description: "説明"
dev/views/index.vue:
manage-apps: "アプリの管理"
dev/views/apps.vue:
manage-apps: "アプリを管理"
create-app: "アプリ作成"
app-missing: "アプリなし"
dev/views/new-app.vue:
create-app: "アプリケーションの作成"
app-name: "アプリケーション名"
app-name-desc: "あなたのアプリの名称。"
app-name-ex: "ex) Misskey for iOS"
app-overview: "アプリの概要"
app-desc: "あなたのアプリの簡単な説明や紹介。"
app-desc-ex: "ex) Misskey iOSクライアント。"
callback-url: "コールバックURL (オプション)"
callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。"
authority: "権限"
authority-desc: "ここで要求した機能だけがAPIからアクセスできます。"
authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。"
account-read: "アカウントの情報を見る。"
account-write: "アカウントの情報を操作する。"
note-write: "投稿する。"
reaction-write: "リアクションしたりリアクションをキャンセルする。"
following-write: "フォローしたりフォロー解除する。"
drive-read: "ドライブを見る。"
drive-write: "ドライブを操作する。"
notification-read: "通知を見る。"
notification-write: "通知を操作する。"

View File

@@ -184,6 +184,7 @@ common:
rename: "이름 변경"
stack-left: "左に重ねる"
pop-right: "右に出す"
dev: "アプリの作成に失敗しました。再度お試しください。"
auth/views/form.vue:
share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
permission-ask: "このアプリは次の権限を要求しています:"
@@ -938,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
@@ -945,6 +948,9 @@ desktop/views/pages/admin/admin.dashboard.vue:
all-notes: "全ての投稿"
original-notes: "このインスタンスの投稿"
invite: "招待"
banner-url: "Banner URL"
disableRegistration: "Disable new user registration"
disableLocalTimeline: "Disable the local timeline"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@@ -961,12 +967,23 @@ desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.announcements.vue:
announcements: "お知らせ"
desktop/views/pages/admin/admin.hashtags.vue:
hided-tags: "Hidden Tags"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
desktop/views/pages/deck/deck.user-column.vue:
posts: "投稿"
following: "フォロー"
followers: "フォロワー"
images: "画像"
activity: "アクティビティ"
timeline: "タイムライン"
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1019,7 +1036,6 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よく話すユーザーはいません"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "フォト"
@@ -1040,6 +1056,10 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー"
followers: "フォロワー"
is-bot: "このアカウントはBotです"
years-old: "歳"
year: "年"
month: "月"
day: "日"
desktop/views/pages/user/user.timeline.vue:
default: "投稿"
with-replies: "投稿と返信"
@@ -1296,6 +1316,7 @@ mobile/views/pages/settings.vue:
signout: "サインアウト"
sound: "サウンド"
enable-sounds: "サウンドを有効にする"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "フォローされています"
following: "フォロー"
@@ -1350,3 +1371,29 @@ docs:
description: "説明"
dev/views/index.vue:
manage-apps: "アプリの管理"
dev/views/apps.vue:
manage-apps: "アプリを管理"
create-app: "アプリ作成"
app-missing: "アプリなし"
dev/views/new-app.vue:
create-app: "アプリケーションの作成"
app-name: "アプリケーション名"
app-name-desc: "あなたのアプリの名称。"
app-name-ex: "ex) Misskey for iOS"
app-overview: "アプリの概要"
app-desc: "あなたのアプリの簡単な説明や紹介。"
app-desc-ex: "ex) Misskey iOSクライアント。"
callback-url: "コールバックURL (オプション)"
callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。"
authority: "権限"
authority-desc: "ここで要求した機能だけがAPIからアクセスできます。"
authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。"
account-read: "アカウントの情報を見る。"
account-write: "アカウントの情報を操作する。"
note-write: "投稿する。"
reaction-write: "リアクションしたりリアクションをキャンセルする。"
following-write: "フォローしたりフォロー解除する。"
drive-read: "ドライブを見る。"
drive-write: "ドライブを操作する。"
notification-read: "通知を見る。"
notification-write: "通知を操作する。"

View File

@@ -184,6 +184,7 @@ common:
rename: "名前を変更"
stack-left: "左に重ねる"
pop-right: "右に出す"
dev: "アプリの作成に失敗しました。再度お試しください。"
auth/views/form.vue:
share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
permission-ask: "このアプリは次の権限を要求しています:"
@@ -938,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
@@ -945,6 +948,9 @@ desktop/views/pages/admin/admin.dashboard.vue:
all-notes: "全ての投稿"
original-notes: "このインスタンスの投稿"
invite: "招待"
banner-url: "Banner URL"
disableRegistration: "Disable new user registration"
disableLocalTimeline: "Disable the local timeline"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@@ -961,12 +967,23 @@ desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.announcements.vue:
announcements: "お知らせ"
desktop/views/pages/admin/admin.hashtags.vue:
hided-tags: "Hidden Tags"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
desktop/views/pages/deck/deck.user-column.vue:
posts: "投稿"
following: "フォロー"
followers: "フォロワー"
images: "画像"
activity: "アクティビティ"
timeline: "タイムライン"
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1019,8 +1036,7 @@ desktop/views/pages/user/user.friends.vue:
no-users: "Geen gebruikers"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
desktop/views/pages/user/user.home.vue:
last-used-at: "Laatst actief: "
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "Foto's"
loading: "Bezig met laden"
@@ -1040,6 +1056,10 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー"
followers: "フォロワー"
is-bot: "このアカウントはBotです"
years-old: "歳"
year: "年"
month: "月"
day: "日"
desktop/views/pages/user/user.timeline.vue:
default: "Berichten"
with-replies: "Berichten en antwoorden"
@@ -1296,6 +1316,7 @@ mobile/views/pages/settings.vue:
signout: "Uitloggen"
sound: "サウンド"
enable-sounds: "サウンドを有効にする"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "Volgt jou"
following: "Volgend"
@@ -1350,3 +1371,29 @@ docs:
description: "Omschrijving"
dev/views/index.vue:
manage-apps: "アプリの管理"
dev/views/apps.vue:
manage-apps: "アプリを管理"
create-app: "アプリ作成"
app-missing: "アプリなし"
dev/views/new-app.vue:
create-app: "アプリケーションの作成"
app-name: "アプリケーション名"
app-name-desc: "あなたのアプリの名称。"
app-name-ex: "ex) Misskey for iOS"
app-overview: "アプリの概要"
app-desc: "あなたのアプリの簡単な説明や紹介。"
app-desc-ex: "ex) Misskey iOSクライアント。"
callback-url: "コールバックURL (オプション)"
callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。"
authority: "権限"
authority-desc: "ここで要求した機能だけがAPIからアクセスできます。"
authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。"
account-read: "アカウントの情報を見る。"
account-write: "アカウントの情報を操作する。"
note-write: "投稿する。"
reaction-write: "リアクションしたりリアクションをキャンセルする。"
following-write: "フォローしたりフォロー解除する。"
drive-read: "ドライブを見る。"
drive-write: "ドライブを操作する。"
notification-read: "通知を見る。"
notification-write: "通知を操作する。"

View File

@@ -184,6 +184,7 @@ common:
rename: "Endre navn"
stack-left: "左に重ねる"
pop-right: "Til høyre"
dev: "アプリの作成に失敗しました。再度お試しください。"
auth/views/form.vue:
share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
permission-ask: "このアプリは次の権限を要求しています:"
@@ -938,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "Disk"
users: "Brukere"
update: "Oppdater"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
@@ -945,6 +948,9 @@ desktop/views/pages/admin/admin.dashboard.vue:
all-notes: "全ての投稿"
original-notes: "このインスタンスの投稿"
invite: "Inviter"
banner-url: "Banner URL"
disableRegistration: "Disable new user registration"
disableLocalTimeline: "Disable the local timeline"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "Suspender"
@@ -961,12 +967,23 @@ desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.announcements.vue:
announcements: "お知らせ"
desktop/views/pages/admin/admin.hashtags.vue:
hided-tags: "Hidden Tags"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
desktop/views/pages/deck/deck.user-column.vue:
posts: "投稿"
following: "フォロー"
followers: "フォロワー"
images: "画像"
activity: "アクティビティ"
timeline: "タイムライン"
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1019,7 +1036,6 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よく話すユーザーはいません"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "Bilder"
@@ -1040,6 +1056,10 @@ desktop/views/pages/user/user.header.vue:
following: "Følger"
followers: "フォロワー"
is-bot: "このアカウントはBotです"
years-old: "歳"
year: "年"
month: "月"
day: "日"
desktop/views/pages/user/user.timeline.vue:
default: "Innlegg"
with-replies: "Innlegg og svar"
@@ -1296,6 +1316,7 @@ mobile/views/pages/settings.vue:
signout: "サインアウト"
sound: "Lyder"
enable-sounds: "サウンドを有効にする"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "フォローされています"
following: "Følger"
@@ -1350,3 +1371,29 @@ docs:
description: "Beskrivelse"
dev/views/index.vue:
manage-apps: "アプリの管理"
dev/views/apps.vue:
manage-apps: "アプリを管理"
create-app: "アプリ作成"
app-missing: "アプリなし"
dev/views/new-app.vue:
create-app: "アプリケーションの作成"
app-name: "アプリケーション名"
app-name-desc: "あなたのアプリの名称。"
app-name-ex: "ex) Misskey for iOS"
app-overview: "アプリの概要"
app-desc: "あなたのアプリの簡単な説明や紹介。"
app-desc-ex: "ex) Misskey iOSクライアント。"
callback-url: "コールバックURL (オプション)"
callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。"
authority: "権限"
authority-desc: "ここで要求した機能だけがAPIからアクセスできます。"
authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。"
account-read: "アカウントの情報を見る。"
account-write: "アカウントの情報を操作する。"
note-write: "投稿する。"
reaction-write: "リアクションしたりリアクションをキャンセルする。"
following-write: "フォローしたりフォロー解除する。"
drive-read: "ドライブを見る。"
drive-write: "ドライブを操作する。"
notification-read: "通知を見る。"
notification-write: "通知を操作する。"

View File

@@ -184,6 +184,7 @@ common:
rename: "Zmień nazwę"
stack-left: "Przypnij do lewej"
pop-right: "Odepnij w prawo"
dev: "アプリの作成に失敗しました。再度お試しください。"
auth/views/form.vue:
share-access: "Czy chcesz <b>zezwolić</b> <i>{{ app.name }}</i> na dostęp do Twojego konta?"
permission-ask: "Ta aplikacja wymaga następujących uprawnień:"
@@ -938,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
@@ -945,6 +948,9 @@ desktop/views/pages/admin/admin.dashboard.vue:
all-notes: "全ての投稿"
original-notes: "このインスタンスの投稿"
invite: "招待"
banner-url: "Banner URL"
disableRegistration: "Disable new user registration"
disableLocalTimeline: "Disable the local timeline"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@@ -961,12 +967,23 @@ desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.announcements.vue:
announcements: "お知らせ"
desktop/views/pages/admin/admin.hashtags.vue:
hided-tags: "Hidden Tags"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "Tylko wpisy z zawartością multimedialną"
is-media-view: "Widok multimediów"
edit: "Opcje"
desktop/views/pages/deck/deck.user-column.vue:
posts: "投稿"
following: "フォロー"
followers: "フォロワー"
images: "画像"
activity: "アクティビティ"
timeline: "タイムライン"
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1019,8 +1036,7 @@ desktop/views/pages/user/user.friends.vue:
no-users: "Brak użytkowników"
desktop/views/pages/user/user.vue:
is-suspended: "To konto zostało zawieszone."
desktop/views/pages/user/user.home.vue:
last-used-at: "Ostatnio aktywny"
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "Zdjęcia"
loading: "Ładowanie"
@@ -1040,6 +1056,10 @@ desktop/views/pages/user/user.header.vue:
following: "Śledzeni"
followers: "Śledzący"
is-bot: "To konto jest botem"
years-old: "歳"
year: "年"
month: "月"
day: "日"
desktop/views/pages/user/user.timeline.vue:
default: "Wpisy"
with-replies: "Wpisy i odpowiedzi"
@@ -1296,6 +1316,7 @@ mobile/views/pages/settings.vue:
signout: "Wyloguj"
sound: "サウンド"
enable-sounds: "サウンドを有効にする"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "Śledzi Cię"
following: "Śledzeni"
@@ -1350,3 +1371,29 @@ docs:
description: "Opis"
dev/views/index.vue:
manage-apps: "Zarządzaj aplikacjami"
dev/views/apps.vue:
manage-apps: "アプリを管理"
create-app: "アプリ作成"
app-missing: "アプリなし"
dev/views/new-app.vue:
create-app: "アプリケーションの作成"
app-name: "アプリケーション名"
app-name-desc: "あなたのアプリの名称。"
app-name-ex: "ex) Misskey for iOS"
app-overview: "アプリの概要"
app-desc: "あなたのアプリの簡単な説明や紹介。"
app-desc-ex: "ex) Misskey iOSクライアント。"
callback-url: "コールバックURL (オプション)"
callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。"
authority: "権限"
authority-desc: "ここで要求した機能だけがAPIからアクセスできます。"
authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。"
account-read: "アカウントの情報を見る。"
account-write: "アカウントの情報を操作する。"
note-write: "投稿する。"
reaction-write: "リアクションしたりリアクションをキャンセルする。"
following-write: "フォローしたりフォロー解除する。"
drive-read: "ドライブを見る。"
drive-write: "ドライブを操作する。"
notification-read: "通知を見る。"
notification-write: "通知を操作する。"

View File

@@ -184,6 +184,7 @@ common:
rename: "Renomear"
stack-left: "左に重ねる"
pop-right: "Acoplar à direita"
dev: "アプリの作成に失敗しました。再度お試しください。"
auth/views/form.vue:
share-access: "Você <b>permite</b> que <i>{{ app.name }}</i> acesse sua conta?"
permission-ask: "Este aplicativo precisa das seguintes permissões:"
@@ -938,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "Usuários"
update: "Actualizações"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "Todos os usuários"
@@ -945,6 +948,9 @@ desktop/views/pages/admin/admin.dashboard.vue:
all-notes: "全ての投稿"
original-notes: "このインスタンスの投稿"
invite: "招待"
banner-url: "Banner URL"
disableRegistration: "Disable new user registration"
disableLocalTimeline: "Disable the local timeline"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@@ -961,12 +967,23 @@ desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.announcements.vue:
announcements: "お知らせ"
desktop/views/pages/admin/admin.hashtags.vue:
hided-tags: "Hidden Tags"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
desktop/views/pages/deck/deck.user-column.vue:
posts: "投稿"
following: "フォロー"
followers: "フォロワー"
images: "画像"
activity: "アクティビティ"
timeline: "タイムライン"
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1019,7 +1036,6 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よく話すユーザーはいません"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "フォト"
@@ -1040,6 +1056,10 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー"
followers: "フォロワー"
is-bot: "このアカウントはBotです"
years-old: "歳"
year: "年"
month: "月"
day: "日"
desktop/views/pages/user/user.timeline.vue:
default: "投稿"
with-replies: "投稿と返信"
@@ -1296,6 +1316,7 @@ mobile/views/pages/settings.vue:
signout: "Sair"
sound: "Sons"
enable-sounds: "Ativar sons"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "Te segue"
following: "Seguindo"
@@ -1350,3 +1371,29 @@ docs:
description: "Descrição"
dev/views/index.vue:
manage-apps: "Gerenciar aplicativos"
dev/views/apps.vue:
manage-apps: "アプリを管理"
create-app: "アプリ作成"
app-missing: "アプリなし"
dev/views/new-app.vue:
create-app: "アプリケーションの作成"
app-name: "アプリケーション名"
app-name-desc: "あなたのアプリの名称。"
app-name-ex: "ex) Misskey for iOS"
app-overview: "アプリの概要"
app-desc: "あなたのアプリの簡単な説明や紹介。"
app-desc-ex: "ex) Misskey iOSクライアント。"
callback-url: "コールバックURL (オプション)"
callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。"
authority: "権限"
authority-desc: "ここで要求した機能だけがAPIからアクセスできます。"
authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。"
account-read: "アカウントの情報を見る。"
account-write: "アカウントの情報を操作する。"
note-write: "投稿する。"
reaction-write: "リアクションしたりリアクションをキャンセルする。"
following-write: "フォローしたりフォロー解除する。"
drive-read: "ドライブを見る。"
drive-write: "ドライブを操作する。"
notification-read: "通知を見る。"
notification-write: "通知を操作する。"

View File

@@ -184,6 +184,7 @@ common:
rename: "名前を変更"
stack-left: "左に重ねる"
pop-right: "右に出す"
dev: "アプリの作成に失敗しました。再度お試しください。"
auth/views/form.vue:
share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
permission-ask: "このアプリは次の権限を要求しています:"
@@ -938,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
@@ -945,6 +948,9 @@ desktop/views/pages/admin/admin.dashboard.vue:
all-notes: "全ての投稿"
original-notes: "このインスタンスの投稿"
invite: "招待"
banner-url: "Banner URL"
disableRegistration: "Disable new user registration"
disableLocalTimeline: "Disable the local timeline"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@@ -961,12 +967,23 @@ desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.announcements.vue:
announcements: "お知らせ"
desktop/views/pages/admin/admin.hashtags.vue:
hided-tags: "Hidden Tags"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
desktop/views/pages/deck/deck.user-column.vue:
posts: "投稿"
following: "フォロー"
followers: "フォロワー"
images: "画像"
activity: "アクティビティ"
timeline: "タイムライン"
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1019,7 +1036,6 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よく話すユーザーはいません"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "フォト"
@@ -1040,6 +1056,10 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー"
followers: "フォロワー"
is-bot: "このアカウントはBotです"
years-old: "歳"
year: "年"
month: "月"
day: "日"
desktop/views/pages/user/user.timeline.vue:
default: "投稿"
with-replies: "投稿と返信"
@@ -1296,6 +1316,7 @@ mobile/views/pages/settings.vue:
signout: "サインアウト"
sound: "サウンド"
enable-sounds: "サウンドを有効にする"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "フォローされています"
following: "フォロー"
@@ -1350,3 +1371,29 @@ docs:
description: "説明"
dev/views/index.vue:
manage-apps: "アプリの管理"
dev/views/apps.vue:
manage-apps: "アプリを管理"
create-app: "アプリ作成"
app-missing: "アプリなし"
dev/views/new-app.vue:
create-app: "アプリケーションの作成"
app-name: "アプリケーション名"
app-name-desc: "あなたのアプリの名称。"
app-name-ex: "ex) Misskey for iOS"
app-overview: "アプリの概要"
app-desc: "あなたのアプリの簡単な説明や紹介。"
app-desc-ex: "ex) Misskey iOSクライアント。"
callback-url: "コールバックURL (オプション)"
callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。"
authority: "権限"
authority-desc: "ここで要求した機能だけがAPIからアクセスできます。"
authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。"
account-read: "アカウントの情報を見る。"
account-write: "アカウントの情報を操作する。"
note-write: "投稿する。"
reaction-write: "リアクションしたりリアクションをキャンセルする。"
following-write: "フォローしたりフォロー解除する。"
drive-read: "ドライブを見る。"
drive-write: "ドライブを操作する。"
notification-read: "通知を見る。"
notification-write: "通知を操作する。"

View File

@@ -184,6 +184,7 @@ common:
rename: "名前を変更"
stack-left: "左に重ねる"
pop-right: "右に出す"
dev: "アプリの作成に失敗しました。再度お試しください。"
auth/views/form.vue:
share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
permission-ask: "このアプリは次の権限を要求しています:"
@@ -938,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
@@ -945,6 +948,9 @@ desktop/views/pages/admin/admin.dashboard.vue:
all-notes: "全ての投稿"
original-notes: "このインスタンスの投稿"
invite: "招待"
banner-url: "Banner URL"
disableRegistration: "Disable new user registration"
disableLocalTimeline: "Disable the local timeline"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@@ -961,12 +967,23 @@ desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.announcements.vue:
announcements: "お知らせ"
desktop/views/pages/admin/admin.hashtags.vue:
hided-tags: "Hidden Tags"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
desktop/views/pages/deck/deck.user-column.vue:
posts: "投稿"
following: "フォロー"
followers: "フォロワー"
images: "画像"
activity: "アクティビティ"
timeline: "タイムライン"
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1019,7 +1036,6 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よく話すユーザーはいません"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "フォト"
@@ -1040,6 +1056,10 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー"
followers: "フォロワー"
is-bot: "このアカウントはBotです"
years-old: "歳"
year: "年"
month: "月"
day: "日"
desktop/views/pages/user/user.timeline.vue:
default: "投稿"
with-replies: "投稿と返信"
@@ -1296,6 +1316,7 @@ mobile/views/pages/settings.vue:
signout: "サインアウト"
sound: "サウンド"
enable-sounds: "サウンドを有効にする"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "フォローされています"
following: "フォロー"
@@ -1350,3 +1371,29 @@ docs:
description: "説明"
dev/views/index.vue:
manage-apps: "アプリの管理"
dev/views/apps.vue:
manage-apps: "アプリを管理"
create-app: "アプリ作成"
app-missing: "アプリなし"
dev/views/new-app.vue:
create-app: "アプリケーションの作成"
app-name: "アプリケーション名"
app-name-desc: "あなたのアプリの名称。"
app-name-ex: "ex) Misskey for iOS"
app-overview: "アプリの概要"
app-desc: "あなたのアプリの簡単な説明や紹介。"
app-desc-ex: "ex) Misskey iOSクライアント。"
callback-url: "コールバックURL (オプション)"
callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。"
authority: "権限"
authority-desc: "ここで要求した機能だけがAPIからアクセスできます。"
authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。"
account-read: "アカウントの情報を見る。"
account-write: "アカウントの情報を操作する。"
note-write: "投稿する。"
reaction-write: "リアクションしたりリアクションをキャンセルする。"
following-write: "フォローしたりフォロー解除する。"
drive-read: "ドライブを見る。"
drive-write: "ドライブを操作する。"
notification-read: "通知を見る。"
notification-write: "通知を操作する。"

View File

@@ -1,8 +1,8 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "10.29.1",
"clientVersion": "1.0.10839",
"version": "10.30.3",
"clientVersion": "1.0.11045",
"codename": "nighthike",
"main": "./built/index.js",
"private": true,
@@ -84,6 +84,7 @@
"@types/websocket": "0.0.40",
"@types/ws": "6.0.1",
"animejs": "2.2.0",
"apexcharts": "2.1.5",
"autobind-decorator": "2.1.0",
"autosize": "4.0.2",
"autwh": "0.1.0",
@@ -113,7 +114,7 @@
"eventemitter3": "3.1.0",
"exif-js": "2.3.0",
"file-loader": "2.0.0",
"file-type": "10.0.0",
"file-type": "10.1.0",
"fuckadblock": "3.2.1",
"gulp": "3.9.1",
"gulp-cssnano": "2.1.3",
@@ -199,7 +200,7 @@
"stylus": "0.54.5",
"stylus-loader": "3.0.2",
"summaly": "2.2.0",
"systeminformation": "3.45.7",
"systeminformation": "3.45.9",
"syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0",
"tinycolor2": "1.4.1",
@@ -232,7 +233,7 @@
"vuex-persistedstate": "2.5.4",
"web-push": "3.3.3",
"webfinger.js": "2.6.6",
"webpack": "4.21.0",
"webpack": "4.22.0",
"webpack-cli": "3.1.2",
"websocket": "1.0.28",
"ws": "6.1.0",

122
src/chart/drive.ts Normal file
View File

@@ -0,0 +1,122 @@
import autobind from 'autobind-decorator';
import Chart, { Obj } from './';
import DriveFile, { IDriveFile } from '../models/drive-file';
import { isLocalUser } from '../models/user';
/**
* ドライブに関するチャート
*/
type DriveLog = {
local: {
/**
* 集計期間時点での、全ドライブファイル数
*/
totalCount: number;
/**
* 集計期間時点での、全ドライブファイルの合計サイズ
*/
totalSize: number;
/**
* 増加したドライブファイル数
*/
incCount: number;
/**
* 増加したドライブ使用量
*/
incSize: number;
/**
* 減少したドライブファイル数
*/
decCount: number;
/**
* 減少したドライブ使用量
*/
decSize: number;
};
remote: DriveLog['local'];
};
class DriveChart extends Chart<DriveLog> {
constructor() {
super('drive');
}
@autobind
protected async getTemplate(init: boolean, latest?: DriveLog): Promise<DriveLog> {
const calcSize = (local: boolean) => DriveFile
.aggregate([{
$match: {
'metadata._user.host': local ? null : { $ne: null },
'metadata.deletedAt': { $exists: false }
}
}, {
$project: {
length: true
}
}, {
$group: {
_id: null,
usage: { $sum: '$length' }
}
}])
.then(res => res.length > 0 ? res[0].usage : 0);
const [localCount, remoteCount, localSize, remoteSize] = init ? await Promise.all([
DriveFile.count({ 'metadata._user.host': null }),
DriveFile.count({ 'metadata._user.host': { $ne: null } }),
calcSize(true),
calcSize(false)
]) : [
latest ? latest.local.totalCount : 0,
latest ? latest.remote.totalCount : 0,
latest ? latest.local.totalSize : 0,
latest ? latest.remote.totalSize : 0
];
return {
local: {
totalCount: localCount,
totalSize: localSize,
incCount: 0,
incSize: 0,
decCount: 0,
decSize: 0
},
remote: {
totalCount: remoteCount,
totalSize: remoteSize,
incCount: 0,
incSize: 0,
decCount: 0,
decSize: 0
}
};
}
@autobind
public async update(file: IDriveFile, isAdditional: boolean) {
const update: Obj = {};
update.totalCount = isAdditional ? 1 : -1;
update.totalSize = isAdditional ? file.length : -file.length;
if (isAdditional) {
update.incCount = 1;
update.incSize = file.length;
} else {
update.decCount = 1;
update.decSize = file.length;
}
await this.inc({
[isLocalUser(file.metadata._user) ? 'local' : 'remote']: update
});
}
}
export default new DriveChart();

56
src/chart/hashtag.ts Normal file
View File

@@ -0,0 +1,56 @@
import autobind from 'autobind-decorator';
import Chart, { Obj } from './';
import { IUser, isLocalUser } from '../models/user';
import db from '../db/mongodb';
/**
* ハッシュタグに関するチャート
*/
type HashtagLog = {
local: {
/**
* 投稿された数
*/
count: number;
};
remote: HashtagLog['local'];
};
class HashtagChart extends Chart<HashtagLog> {
constructor() {
super('hashtag', true);
// 後方互換性のため
db.get('chart.hashtag').findOne().then(doc => {
if (doc != null && doc.data.local == null) {
db.get('chart.hashtag').drop();
}
});
}
@autobind
protected async getTemplate(init: boolean, latest?: HashtagLog): Promise<HashtagLog> {
return {
local: {
count: 0
},
remote: {
count: 0
}
};
}
@autobind
public async update(hashtag: string, user: IUser) {
const update: Obj = {
count: 1
};
await this.incIfUnique({
[isLocalUser(user) ? 'local' : 'remote']: update
}, 'users', user._id.toHexString(), hashtag);
}
}
export default new HashtagChart();

285
src/chart/index.ts Normal file
View File

@@ -0,0 +1,285 @@
/**
* チャートエンジン
*/
const nestedProperty = require('nested-property');
import autobind from 'autobind-decorator';
import * as mongo from 'mongodb';
import db from '../db/mongodb';
import { ICollection } from 'monk';
export type Obj = { [key: string]: any };
export type Partial<T> = {
[P in keyof T]?: Partial<T[P]>;
};
type ArrayValue<T> = {
[P in keyof T]: T[P] extends number ? Array<T[P]> : ArrayValue<T[P]>;
};
type Span = 'day' | 'hour';
//#region Chart Core
type Log<T extends Obj> = {
_id: mongo.ObjectID;
/**
* 集計のグループ
*/
group?: any;
/**
* 集計日時
*/
date: Date;
/**
* 集計期間
*/
span: Span;
/**
* データ
*/
data: T;
/**
* ユニークインクリメント用
*/
unique?: Obj;
};
/**
* 様々なチャートの管理を司るクラス
*/
export default abstract class Chart<T> {
protected collection: ICollection<Log<T>>;
protected abstract async getTemplate(init: boolean, latest?: T, group?: any): Promise<T>;
constructor(name: string, grouped = false) {
this.collection = db.get<Log<T>>(`chart.${name}`);
if (grouped) {
this.collection.createIndex({ span: -1, date: -1, group: -1 }, { unique: true });
} else {
this.collection.createIndex({ span: -1, date: -1 }, { unique: true });
}
}
@autobind
private convertQuery(x: Obj, path: string): Obj {
const query: Obj = {};
const dive = (x: Obj, path: string) => {
Object.entries(x).forEach(([k, v]) => {
const p = path ? `${path}.${k}` : k;
if (typeof v === 'number') {
query[p] = v;
} else {
dive(v, p);
}
});
};
dive(x, path);
return query;
}
@autobind
private async getCurrentLog(span: Span, group?: any): Promise<Log<T>> {
const now = new Date();
const y = now.getFullYear();
const m = now.getMonth();
const d = now.getDate();
const h = now.getHours();
const current =
span == 'day' ? new Date(y, m, d) :
span == 'hour' ? new Date(y, m, d, h) :
null;
// 現在(今日または今のHour)のログ
const currentLog = await this.collection.findOne({
group: group,
span: span,
date: current
});
if (currentLog) {
return currentLog;
}
// 集計期間が変わってから、初めてのチャート更新なら
// 最も最近のログを持ってくる
// * 例えば集計期間が「日」である場合で考えると、
// * 昨日何もチャートを更新するような出来事がなかった場合は、
// * ログがそもそも作られずドキュメントが存在しないということがあり得るため、
// * 「昨日の」と決め打ちせずに「もっとも最近の」とします
const latest = await this.collection.findOne({
group: group,
span: span
}, {
sort: {
date: -1
}
});
if (latest) {
// 現在のログを初期挿入
const data = await this.getTemplate(false, latest.data);
const log = await this.collection.insert({
group: group,
span: span,
date: current,
data: data
});
return log;
} else {
// ログが存在しなかったら
// * Misskeyインスタンスを建てて初めてのチャート更新時など
// 空のログを作成
const data = await this.getTemplate(true, null, group);
const log = await this.collection.insert({
group: group,
span: span,
date: current,
data: data
});
return log;
}
}
@autobind
protected commit(query: Obj, group?: any, uniqueKey?: string, uniqueValue?: string): void {
const update = (log: Log<T>) => {
// ユニークインクリメントの場合、指定のキーに指定の値が既に存在していたら弾く
if (
uniqueKey &&
log.unique &&
log.unique[uniqueKey] &&
log.unique[uniqueKey].includes(uniqueValue)
) return;
// ユニークインクリメントの指定のキーに値を追加
if (uniqueKey) {
query['$push'] = {
[`unique.${uniqueKey}`]: uniqueValue
};
}
this.collection.update({
_id: log._id
}, query);
};
this.getCurrentLog('day', group).then(log => update(log));
this.getCurrentLog('hour', group).then(log => update(log));
}
@autobind
protected inc(inc: Partial<T>, group?: any): void {
this.commit({
$inc: this.convertQuery(inc, 'data')
}, group);
}
@autobind
protected incIfUnique(inc: Partial<T>, key: string, value: string, group?: any): void {
this.commit({
$inc: this.convertQuery(inc, 'data')
}, group, key, value);
}
@autobind
public async getChart(span: Span, range: number, group?: any): Promise<ArrayValue<T>> {
const promisedChart: Promise<T>[] = [];
const now = new Date();
const y = now.getFullYear();
const m = now.getMonth();
const d = now.getDate();
const h = now.getHours();
const gt =
span == 'day' ? new Date(y, m, d - range) :
span == 'hour' ? new Date(y, m, d, h - range) : null;
const logs = await this.collection.find({
group: group,
span: span,
date: {
$gt: gt
}
}, {
sort: {
date: -1
},
fields: {
_id: 0
}
});
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 log = logs.find(l => l.date.getTime() == current.getTime());
if (log) {
promisedChart.unshift(Promise.resolve(log.data));
} else { // 隙間埋め
const latest = logs.find(l => l.date.getTime() < current.getTime());
promisedChart.unshift(this.getTemplate(false, latest ? latest.data : null));
}
}
const chart = await Promise.all(promisedChart);
const res: ArrayValue<T> = {} as any;
/**
* [{
* xxxxx: 1,
* yyyyy: 5
* }, {
* xxxxx: 2,
* yyyyy: 6
* }, {
* xxxxx: 3,
* yyyyy: 7
* }]
*
* を
*
* {
* xxxxx: [1, 2, 3],
* yyyyy: [5, 6, 7]
* }
*
* にする
*/
const dive = (x: Obj, path?: string) => {
Object.entries(x).forEach(([k, v]) => {
const p = path ? `${path}.${k}` : k;
if (typeof v == 'object') {
dive(v, p);
} else {
nestedProperty.set(res, p, chart.map(s => nestedProperty.get(s, p)));
}
});
};
dive(chart[0]);
return res;
}
}
//#endregion

64
src/chart/network.ts Normal file
View File

@@ -0,0 +1,64 @@
import autobind from 'autobind-decorator';
import Chart, { Partial } from './';
/**
* ネットワークに関するチャート
*/
type NetworkLog = {
/**
* 受信したリクエスト数
*/
incomingRequests: number;
/**
* 送信したリクエスト数
*/
outgoingRequests: number;
/**
* 応答時間の合計
* TIP: (totalTime / incomingRequests) でひとつのリクエストに平均でどれくらいの時間がかかったか知れる
*/
totalTime: number;
/**
* 合計受信データ量
*/
incomingBytes: number;
/**
* 合計送信データ量
*/
outgoingBytes: number;
};
class NetworkChart extends Chart<NetworkLog> {
constructor() {
super('network');
}
@autobind
protected async getTemplate(init: boolean, latest?: NetworkLog): Promise<NetworkLog> {
return {
incomingRequests: 0,
outgoingRequests: 0,
totalTime: 0,
incomingBytes: 0,
outgoingBytes: 0
};
}
@autobind
public async update(incomingRequests: number, time: number, incomingBytes: number, outgoingBytes: number) {
const inc: Partial<NetworkLog> = {
incomingRequests: incomingRequests,
totalTime: time,
incomingBytes: incomingBytes,
outgoingBytes: outgoingBytes
};
await this.inc(inc);
}
}
export default new NetworkChart();

114
src/chart/notes.ts Normal file
View File

@@ -0,0 +1,114 @@
import autobind from 'autobind-decorator';
import Chart, { Obj } from '.';
import Note, { INote } from '../models/note';
import { isLocalUser } from '../models/user';
/**
* 投稿に関するチャート
*/
type NotesLog = {
local: {
/**
* 集計期間時点での、全投稿数
*/
total: number;
/**
* 増加した投稿数
*/
inc: number;
/**
* 減少した投稿数
*/
dec: number;
diffs: {
/**
* 通常の投稿数の差分
*/
normal: number;
/**
* リプライの投稿数の差分
*/
reply: number;
/**
* Renoteの投稿数の差分
*/
renote: number;
};
};
remote: NotesLog['local'];
};
class NotesChart extends Chart<NotesLog> {
constructor() {
super('notes');
}
@autobind
protected async getTemplate(init: boolean, latest?: NotesLog): Promise<NotesLog> {
const [localCount, remoteCount] = init ? await Promise.all([
Note.count({ '_user.host': null }),
Note.count({ '_user.host': { $ne: null } })
]) : [
latest ? latest.local.total : 0,
latest ? latest.remote.total : 0
];
return {
local: {
total: localCount,
inc: 0,
dec: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
}
},
remote: {
total: remoteCount,
inc: 0,
dec: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
}
}
};
}
@autobind
public async update(note: INote, isAdditional: boolean) {
const update: Obj = {
diffs: {}
};
update.total = isAdditional ? 1 : -1;
if (isAdditional) {
update.inc = 1;
} else {
update.dec = 1;
}
if (note.replyId != null) {
update.diffs.reply = isAdditional ? 1 : -1;
} else if (note.renoteId != null) {
update.diffs.renote = isAdditional ? 1 : -1;
} else {
update.diffs.normal = isAdditional ? 1 : -1;
}
await this.inc({
[isLocalUser(note._user) ? 'local' : 'remote']: update
});
}
}
export default new NotesChart();

101
src/chart/per-user-drive.ts Normal file
View File

@@ -0,0 +1,101 @@
import autobind from 'autobind-decorator';
import Chart, { Obj } from './';
import DriveFile, { IDriveFile } from '../models/drive-file';
/**
* ユーザーごとのドライブに関するチャート
*/
type PerUserDriveLog = {
/**
* 集計期間時点での、全ドライブファイル数
*/
totalCount: number;
/**
* 集計期間時点での、全ドライブファイルの合計サイズ
*/
totalSize: number;
/**
* 増加したドライブファイル数
*/
incCount: number;
/**
* 増加したドライブ使用量
*/
incSize: number;
/**
* 減少したドライブファイル数
*/
decCount: number;
/**
* 減少したドライブ使用量
*/
decSize: number;
};
class PerUserDriveChart extends Chart<PerUserDriveLog> {
constructor() {
super('perUserDrive', true);
}
@autobind
protected async getTemplate(init: boolean, latest?: PerUserDriveLog, group?: any): Promise<PerUserDriveLog> {
const calcSize = () => DriveFile
.aggregate([{
$match: {
'metadata.userId': group,
'metadata.deletedAt': { $exists: false }
}
}, {
$project: {
length: true
}
}, {
$group: {
_id: null,
usage: { $sum: '$length' }
}
}])
.then(res => res.length > 0 ? res[0].usage : 0);
const [count, size] = init ? await Promise.all([
DriveFile.count({ 'metadata.userId': group }),
calcSize()
]) : [
latest ? latest.totalCount : 0,
latest ? latest.totalSize : 0
];
return {
totalCount: count,
totalSize: size,
incCount: 0,
incSize: 0,
decCount: 0,
decSize: 0
};
}
@autobind
public async update(file: IDriveFile, isAdditional: boolean) {
const update: Obj = {};
update.totalCount = isAdditional ? 1 : -1;
update.totalSize = isAdditional ? file.length : -file.length;
if (isAdditional) {
update.incCount = 1;
update.incSize = file.length;
} else {
update.decCount = 1;
update.decSize = file.length;
}
await this.inc(update, file.metadata.userId);
}
}
export default new PerUserDriveChart();

View File

@@ -0,0 +1,128 @@
import autobind from 'autobind-decorator';
import Chart, { Obj } from './';
import Following from '../models/following';
import { IUser, isLocalUser } from '../models/user';
/**
* ユーザーごとのフォローに関するチャート
*/
type PerUserFollowingLog = {
local: {
/**
* フォローしている
*/
followings: {
/**
* 合計
*/
total: number;
/**
* フォローした数
*/
inc: number;
/**
* フォロー解除した数
*/
dec: number;
};
/**
* フォローされている
*/
followers: {
/**
* 合計
*/
total: number;
/**
* フォローされた数
*/
inc: number;
/**
* フォロー解除された数
*/
dec: number;
};
};
remote: PerUserFollowingLog['local'];
};
class PerUserFollowingChart extends Chart<PerUserFollowingLog> {
constructor() {
super('perUserFollowing', true);
}
@autobind
protected async getTemplate(init: boolean, latest?: PerUserFollowingLog, group?: any): Promise<PerUserFollowingLog> {
const [
localFollowingsCount,
localFollowersCount,
remoteFollowingsCount,
remoteFollowersCount
] = init ? await Promise.all([
Following.count({ followerId: group, '_followee.host': null }),
Following.count({ followeeId: group, '_follower.host': null }),
Following.count({ followerId: group, '_followee.host': { $ne: null } }),
Following.count({ followeeId: group, '_follower.host': { $ne: null } })
]) : [
latest ? latest.local.followings.total : 0,
latest ? latest.local.followers.total : 0,
latest ? latest.remote.followings.total : 0,
latest ? latest.remote.followers.total : 0
];
return {
local: {
followings: {
total: localFollowingsCount,
inc: 0,
dec: 0
},
followers: {
total: localFollowersCount,
inc: 0,
dec: 0
}
},
remote: {
followings: {
total: remoteFollowingsCount,
inc: 0,
dec: 0
},
followers: {
total: remoteFollowersCount,
inc: 0,
dec: 0
}
}
};
}
@autobind
public async update(follower: IUser, followee: IUser, isFollow: boolean) {
const update: Obj = {};
update.total = isFollow ? 1 : -1;
if (isFollow) {
update.inc = 1;
} else {
update.dec = 1;
}
this.inc({
[isLocalUser(follower) ? 'local' : 'remote']: { followings: update }
}, follower._id);
this.inc({
[isLocalUser(followee) ? 'local' : 'remote']: { followers: update }
}, followee._id);
}
}
export default new PerUserFollowingChart();

View File

@@ -0,0 +1,94 @@
import autobind from 'autobind-decorator';
import Chart, { Obj } from './';
import Note, { INote } from '../models/note';
import { IUser } from '../models/user';
/**
* ユーザーごとの投稿に関するチャート
*/
type PerUserNotesLog = {
/**
* 集計期間時点での、全投稿数
*/
total: number;
/**
* 増加した投稿数
*/
inc: number;
/**
* 減少した投稿数
*/
dec: number;
diffs: {
/**
* 通常の投稿数の差分
*/
normal: number;
/**
* リプライの投稿数の差分
*/
reply: number;
/**
* Renoteの投稿数の差分
*/
renote: number;
};
};
class PerUserNotesChart extends Chart<PerUserNotesLog> {
constructor() {
super('perUserNotes', true);
}
@autobind
protected async getTemplate(init: boolean, latest?: PerUserNotesLog, group?: any): Promise<PerUserNotesLog> {
const [count] = init ? await Promise.all([
Note.count({ userId: group, deletedAt: null }),
]) : [
latest ? latest.total : 0
];
return {
total: count,
inc: 0,
dec: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
}
};
}
@autobind
public async update(user: IUser, note: INote, isAdditional: boolean) {
const update: Obj = {
diffs: {}
};
update.total = isAdditional ? 1 : -1;
if (isAdditional) {
update.inc = 1;
} else {
update.dec = 1;
}
if (note.replyId != null) {
update.diffs.reply = isAdditional ? 1 : -1;
} else if (note.renoteId != null) {
update.diffs.renote = isAdditional ? 1 : -1;
} else {
update.diffs.normal = isAdditional ? 1 : -1;
}
await this.inc(update, user._id);
}
}
export default new PerUserNotesChart();

View File

@@ -0,0 +1,45 @@
import autobind from 'autobind-decorator';
import Chart from './';
import { IUser, isLocalUser } from '../models/user';
import { INote } from '../models/note';
/**
* ユーザーごとのリアクションに関するチャート
*/
type PerUserReactionsLog = {
local: {
/**
* リアクションされた数
*/
count: number;
};
remote: PerUserReactionsLog['local'];
};
class PerUserReactionsChart extends Chart<PerUserReactionsLog> {
constructor() {
super('perUserReaction', true);
}
@autobind
protected async getTemplate(init: boolean, latest?: PerUserReactionsLog, group?: any): Promise<PerUserReactionsLog> {
return {
local: {
count: 0
},
remote: {
count: 0
}
};
}
@autobind
public async update(user: IUser, note: INote) {
this.inc({
[isLocalUser(user) ? 'local' : 'remote']: { count: 1 }
}, note.userId);
}
}
export default new PerUserReactionsChart();

75
src/chart/users.ts Normal file
View File

@@ -0,0 +1,75 @@
import autobind from 'autobind-decorator';
import Chart, { Obj } from './';
import User, { IUser, isLocalUser } from '../models/user';
/**
* ユーザーに関するチャート
*/
type UsersLog = {
local: {
/**
* 集計期間時点での、全ユーザー数
*/
total: number;
/**
* 増加したユーザー数
*/
inc: number;
/**
* 減少したユーザー数
*/
dec: number;
};
remote: UsersLog['local'];
};
class UsersChart extends Chart<UsersLog> {
constructor() {
super('users');
}
@autobind
protected async getTemplate(init: boolean, latest?: UsersLog): Promise<UsersLog> {
const [localCount, remoteCount] = init ? await Promise.all([
User.count({ host: null }),
User.count({ host: { $ne: null } })
]) : [
latest ? latest.local.total : 0,
latest ? latest.remote.total : 0
];
return {
local: {
total: localCount,
inc: 0,
dec: 0
},
remote: {
total: remoteCount,
inc: 0,
dec: 0
}
};
}
@autobind
public async update(user: IUser, isAdditional: boolean) {
const update: Obj = {};
update.total = isAdditional ? 1 : -1;
if (isAdditional) {
update.inc = 1;
} else {
update.dec = 1;
}
await this.inc({
[isLocalUser(user) ? 'local' : 'remote']: update
});
}
}
export default new UsersChart();

View File

@@ -37,10 +37,6 @@ export default (opts: Opts = {}) => ({
'ctrl+q': this.renoteDirectly,
'up|k|shift+tab': this.focusBefore,
'down|j|tab': this.focusAfter,
'shift+up': () => this.$emit('parentFocus', 'up'),
'shift+down': () => this.$emit('parentFocus', 'down'),
'shift+left': () => this.$emit('parentFocus', 'left'),
'shift+right': () => this.$emit('parentFocus', 'right'),
'esc': this.blur,
'm|o': () => this.menu(true),
's': this.toggleShowContent,

View File

@@ -5,7 +5,7 @@
<p :class="$style.fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
<div :class="$style.stream" v-if="!fetching && images.length > 0">
<div v-for="image in images" :class="$style.img" :style="`background-image: url(${image.url})`"></div>
<div v-for="image in images" :class="$style.img" :style="`background-image: url(${image.thumbnailUrl || image.url})`"></div>
</div>
<p :class="$style.empty" v-if="!fetching && images.length == 0">%i18n:@no-photos%</p>
</mk-widget-container>

View File

@@ -33,7 +33,7 @@ export default Vue.extend({
},
tooltips: {
intersect: false,
mode: 'x',
mode: 'index',
position: 'nearest'
}
}, this.opts || {}));

View File

@@ -56,6 +56,11 @@ const rgba = (color: string): string => {
return color.replace('rgb', 'rgba').replace(')', ', 0.1)');
};
const limit = 35;
const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
const negate = arr => arr.map(x => -x);
export default Vue.extend({
components: {
XChart
@@ -63,6 +68,7 @@ export default Vue.extend({
data() {
return {
now: null,
chart: null,
chartType: 'notes',
span: 'hour'
@@ -90,32 +96,67 @@ export default Vue.extend({
},
stats(): any[] {
return (
const stats =
this.span == 'day' ? this.chart.perDay :
this.span == 'hour' ? this.chart.perHour :
null
);
null;
return stats;
}
},
created() {
(this as any).api('chart', {
limit: 35
}).then(chart => {
this.chart = chart;
});
async created() {
this.now = new Date();
const [perHour, perDay] = await Promise.all([Promise.all([
(this as any).api('charts/users', { limit: limit, span: 'hour' }),
(this as any).api('charts/notes', { limit: limit, span: 'hour' }),
(this as any).api('charts/drive', { limit: limit, span: 'hour' }),
(this as any).api('charts/network', { limit: limit, span: 'hour' })
]), Promise.all([
(this as any).api('charts/users', { limit: limit, span: 'day' }),
(this as any).api('charts/notes', { limit: limit, span: 'day' }),
(this as any).api('charts/drive', { limit: limit, span: 'day' }),
(this as any).api('charts/network', { limit: limit, span: 'day' })
])]);
const chart = {
perHour: {
users: perHour[0],
notes: perHour[1],
drive: perHour[2],
network: perHour[3]
},
perDay: {
users: perDay[0],
notes: perDay[1],
drive: perDay[2],
network: perDay[3]
}
};
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)
}));
getDate(i: number) {
const y = this.now.getFullYear();
const m = this.now.getMonth();
const d = this.now.getDate();
const h = this.now.getHours();
return (
this.span == 'day' ? new Date(y, m, d - i) :
this.span == 'hour' ? new Date(y, m, d, h - i) :
null
);
},
format(arr) {
return arr.map((v, i) => ({ t: this.getDate(i).getTime(), y: v }));
},
notesChart(type: string): any {
return [{
datasets: [{
label: 'All',
@@ -125,7 +166,10 @@ export default Vue.extend({
borderDash: [4, 4],
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.all }))
data: this.format(type == 'combined'
? sum(this.stats.notes.local.inc, negate(this.stats.notes.local.dec), this.stats.notes.remote.inc, negate(this.stats.notes.remote.dec))
: sum(this.stats.notes[type].inc, negate(this.stats.notes[type].dec))
)
}, {
label: 'Renotes',
fill: true,
@@ -134,7 +178,10 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.renote }))
data: this.format(type == 'combined'
? sum(this.stats.notes.local.diffs.renote, this.stats.notes.remote.diffs.renote)
: this.stats.notes[type].diffs.renote
)
}, {
label: 'Replies',
fill: true,
@@ -143,7 +190,10 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.reply }))
data: this.format(type == 'combined'
? sum(this.stats.notes.local.diffs.reply, this.stats.notes.remote.diffs.reply)
: this.stats.notes[type].diffs.reply
)
}, {
label: 'Normal',
fill: true,
@@ -152,7 +202,10 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.normal }))
data: this.format(type == 'combined'
? sum(this.stats.notes.local.diffs.normal, this.stats.notes.remote.diffs.normal)
: this.stats.notes[type].diffs.normal
)
}]
}, {
scales: {
@@ -176,12 +229,6 @@ export default Vue.extend({
},
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',
@@ -191,7 +238,7 @@ export default Vue.extend({
borderDash: [4, 4],
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.remoteCount + x.localCount }))
data: this.format(sum(this.stats.notes.local.total, this.stats.notes.remote.total))
}, {
label: 'Local',
fill: true,
@@ -200,7 +247,7 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.localCount }))
data: this.format(this.stats.notes.local.total)
}, {
label: 'Remote',
fill: true,
@@ -209,7 +256,7 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.remoteCount }))
data: this.format(this.stats.notes.remote.total)
}]
}, {
scales: {
@@ -233,12 +280,6 @@ export default Vue.extend({
},
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',
@@ -248,7 +289,10 @@ export default Vue.extend({
borderDash: [4, 4],
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.remoteCount + x.localCount }))
data: this.format(total
? sum(this.stats.users.local.total, this.stats.users.remote.total)
: sum(this.stats.users.local.inc, negate(this.stats.users.local.dec), this.stats.users.remote.inc, negate(this.stats.users.remote.dec))
)
}, {
label: 'Local',
fill: true,
@@ -257,7 +301,10 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.localCount }))
data: this.format(total
? this.stats.users.local.total
: sum(this.stats.users.local.inc, negate(this.stats.users.local.dec))
)
}, {
label: 'Remote',
fill: true,
@@ -266,7 +313,10 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.remoteCount }))
data: this.format(total
? this.stats.users.remote.total
: sum(this.stats.users.remote.inc, negate(this.stats.users.remote.dec))
)
}]
}, {
scales: {
@@ -290,14 +340,6 @@ export default Vue.extend({
},
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',
@@ -307,7 +349,7 @@ export default Vue.extend({
borderDash: [4, 4],
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.localInc + x.localDec + x.remoteInc + x.remoteDec }))
data: this.format(sum(this.stats.drive.local.incSize, negate(this.stats.drive.local.decSize), this.stats.drive.remote.incSize, negate(this.stats.drive.remote.decSize)))
}, {
label: 'Local +',
fill: true,
@@ -316,7 +358,7 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.localInc }))
data: this.format(this.stats.drive.local.incSize)
}, {
label: 'Local -',
fill: true,
@@ -325,7 +367,7 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.localDec }))
data: this.format(negate(this.stats.drive.local.decSize))
}, {
label: 'Remote +',
fill: true,
@@ -334,7 +376,7 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.remoteInc }))
data: this.format(this.stats.drive.remote.incSize)
}, {
label: 'Remote -',
fill: true,
@@ -343,7 +385,7 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.remoteDec }))
data: this.format(negate(this.stats.drive.remote.decSize))
}]
}, {
scales: {
@@ -367,12 +409,6 @@ export default Vue.extend({
},
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',
@@ -382,7 +418,7 @@ export default Vue.extend({
borderDash: [4, 4],
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.remoteSize + x.localSize }))
data: this.format(sum(this.stats.drive.local.totalSize, this.stats.drive.remote.totalSize))
}, {
label: 'Local',
fill: true,
@@ -391,7 +427,7 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.localSize }))
data: this.format(this.stats.drive.local.totalSize)
}, {
label: 'Remote',
fill: true,
@@ -400,7 +436,7 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.remoteSize }))
data: this.format(this.stats.drive.remote.totalSize)
}]
}, {
scales: {
@@ -424,14 +460,6 @@ export default Vue.extend({
},
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',
@@ -441,7 +469,7 @@ export default Vue.extend({
borderDash: [4, 4],
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.localInc + x.localDec + x.remoteInc + x.remoteDec }))
data: this.format(sum(this.stats.drive.local.incCount, negate(this.stats.drive.local.decCount), this.stats.drive.remote.incCount, negate(this.stats.drive.remote.decCount)))
}, {
label: 'Local +',
fill: true,
@@ -450,7 +478,7 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.localInc }))
data: this.format(this.stats.drive.local.incCount)
}, {
label: 'Local -',
fill: true,
@@ -459,7 +487,7 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.localDec }))
data: this.format(negate(this.stats.drive.local.decCount))
}, {
label: 'Remote +',
fill: true,
@@ -468,7 +496,7 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.remoteInc }))
data: this.format(this.stats.drive.remote.incCount)
}, {
label: 'Remote -',
fill: true,
@@ -477,7 +505,7 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.remoteDec }))
data: this.format(negate(this.stats.drive.remote.decCount))
}]
}, {
scales: {
@@ -501,12 +529,6 @@ export default Vue.extend({
},
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',
@@ -516,7 +538,7 @@ export default Vue.extend({
borderDash: [4, 4],
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.localCount + x.remoteCount }))
data: this.format(sum(this.stats.drive.local.totalCount, this.stats.drive.remote.totalCount))
}, {
label: 'Local',
fill: true,
@@ -525,7 +547,7 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.localCount }))
data: this.format(this.stats.drive.local.totalCount)
}, {
label: 'Remote',
fill: true,
@@ -534,7 +556,7 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.remoteCount }))
data: this.format(this.stats.drive.remote.totalCount)
}]
}, {
scales: {
@@ -558,30 +580,26 @@ export default Vue.extend({
},
networkRequestsChart(): any {
const data = this.stats.slice().reverse().map(x => ({
date: new Date(x.date),
requests: x.network.requests
}));
return [{
datasets: [{
label: 'Requests',
label: 'Incoming',
fill: true,
backgroundColor: rgba(colors.localPlus),
borderColor: colors.localPlus,
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.requests }))
data: this.format(this.stats.network.incomingRequests)
}]
}];
},
networkTimeChart(): any {
const data = this.stats.slice().reverse().map(x => ({
date: new Date(x.date),
time: x.network.requests != 0 ? (x.network.totalTime / x.network.requests) : 0,
}));
const data = [];
for (let i = 0; i < limit; i++) {
data.push(this.stats.network.incomingRequests[i] != 0 ? (this.stats.network.totalTime[i] / this.stats.network.incomingRequests[i]) : 0);
}
return [{
datasets: [{
@@ -592,18 +610,12 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.time }))
data: this.format(data)
}]
}];
},
networkUsageChart(): any {
const data = this.stats.slice().reverse().map(x => ({
date: new Date(x.date),
incoming: x.network.incomingBytes,
outgoing: x.network.outgoingBytes
}));
return [{
datasets: [{
label: 'Incoming',
@@ -613,7 +625,7 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.incoming }))
data: this.format(this.stats.network.incomingBytes)
}, {
label: 'Outgoing',
fill: true,
@@ -622,7 +634,7 @@ export default Vue.extend({
borderWidth: 2,
pointBackgroundColor: '#fff',
lineTension: 0,
data: data.map(x => ({ t: x.date, y: x.outgoing }))
data: this.format(this.stats.network.outgoingBytes)
}]
}, {
scales: {
@@ -649,8 +661,6 @@ export default Vue.extend({
</script>
<style lang="stylus" scoped>
.gkgckalzgidaygcxnugepioremxvxvpt
padding 32px
background #fff

View File

@@ -117,11 +117,11 @@ export default Vue.extend({
mounted() {
this.connection = (this as any).os.stream.useSharedConnection('drive');
this.connection.on('file_created', this.onStreamDriveFileCreated);
this.connection.on('file_updated', this.onStreamDriveFileUpdated);
this.connection.on('file_deleted', this.onStreamDriveFileDeleted);
this.connection.on('folder_created', this.onStreamDriveFolderCreated);
this.connection.on('folder_updated', this.onStreamDriveFolderUpdated);
this.connection.on('fileCreated', this.onStreamDriveFileCreated);
this.connection.on('fileUpdated', this.onStreamDriveFileUpdated);
this.connection.on('fileDeleted', this.onStreamDriveFileDeleted);
this.connection.on('folderCreated', this.onStreamDriveFolderCreated);
this.connection.on('folderUpdated', this.onStreamDriveFolderUpdated);
if (this.initFolder) {
this.move(this.initFolder);

View File

@@ -17,7 +17,7 @@
<mk-avatar class="avatar" :user="note.user"/>
%fa:retweet%
<span>{{ '%i18n:@reposted-by%'.substr(0, '%i18n:@reposted-by%'.indexOf('{')) }}</span>
<a class="name" :href="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</a>
<router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link>
<span>{{ '%i18n:@reposted-by%'.substr('%i18n:@reposted-by%'.indexOf('}') + 1) }}</span>
<mk-time :time="note.createdAt"/>
</div>

View File

@@ -218,10 +218,13 @@ export default Vue.extend({
> .error
max-width 300px
margin 0 auto
padding 16px
padding 32px
text-align center
color var(--text)
> p
margin 0 0 8px 0
> .placeholder
padding 32px
opacity 0.3

View File

@@ -12,7 +12,7 @@
</div>
<div class="hashtags" v-if="recentHashtags.length > 0 && $store.state.settings.suggestRecentHashtags">
<b>%i18n:@recent-tags%:</b>
<a v-for="tag in recentHashtags.slice(0, 5)" @click="addTag(tag)" title="%@click-to-tagging%">#{{ tag }}</a>
<a v-for="tag in recentHashtags.slice(0, 5)" @click="addTag(tag)" title="%i18n:@click-to-tagging%">#{{ tag }}</a>
</div>
<input v-show="useCw" v-model="cw" placeholder="%i18n:@annotations%">
<textarea :class="{ with: (files.length != 0 || poll) }"

View File

@@ -1,7 +1,7 @@
<template>
<div class="root">
<template v-if="!fetching">
<p><b>{{ capacity | bytes }}</b>%i18n:max%<b>{{ usage | bytes }}</b>%i18n:in-use%</p>
<p><b>{{ capacity | bytes }}</b>%i18n:@max%<b>{{ usage | bytes }}</b>%i18n:@in-use%</p>
</template>
</div>
</template>

View File

@@ -1,14 +1,14 @@
<template>
<x-widgets-column v-if="column.type == 'widgets'" :column="column" :is-stacked="isStacked"/>
<x-notifications-column v-else-if="column.type == 'notifications'" :column="column" :is-stacked="isStacked"/>
<x-tl-column v-else-if="column.type == 'home'" :column="column" :is-stacked="isStacked" @parentFocus="parentFocus"/>
<x-tl-column v-else-if="column.type == 'local'" :column="column" :is-stacked="isStacked" @parentFocus="parentFocus"/>
<x-tl-column v-else-if="column.type == 'hybrid'" :column="column" :is-stacked="isStacked" @parentFocus="parentFocus"/>
<x-tl-column v-else-if="column.type == 'global'" :column="column" :is-stacked="isStacked" @parentFocus="parentFocus"/>
<x-tl-column v-else-if="column.type == 'list'" :column="column" :is-stacked="isStacked" @parentFocus="parentFocus"/>
<x-tl-column v-else-if="column.type == 'hashtag'" :column="column" :is-stacked="isStacked" @parentFocus="parentFocus"/>
<x-mentions-column v-else-if="column.type == 'mentions'" :column="column" :is-stacked="isStacked" @parentFocus="parentFocus"/>
<x-direct-column v-else-if="column.type == 'direct'" :column="column" :is-stacked="isStacked" @parentFocus="parentFocus"/>
<x-widgets-column v-if="column.type == 'widgets'" :column="column" :is-stacked="isStacked" v-on="$listeners"/>
<x-notifications-column v-else-if="column.type == 'notifications'" :column="column" :is-stacked="isStacked" v-on="$listeners"/>
<x-tl-column v-else-if="column.type == 'home'" :column="column" :is-stacked="isStacked" v-on="$listeners"/>
<x-tl-column v-else-if="column.type == 'local'" :column="column" :is-stacked="isStacked" v-on="$listeners"/>
<x-tl-column v-else-if="column.type == 'hybrid'" :column="column" :is-stacked="isStacked" v-on="$listeners"/>
<x-tl-column v-else-if="column.type == 'global'" :column="column" :is-stacked="isStacked" v-on="$listeners"/>
<x-tl-column v-else-if="column.type == 'list'" :column="column" :is-stacked="isStacked" v-on="$listeners"/>
<x-tl-column v-else-if="column.type == 'hashtag'" :column="column" :is-stacked="isStacked" v-on="$listeners"/>
<x-mentions-column v-else-if="column.type == 'mentions'" :column="column" :is-stacked="isStacked" v-on="$listeners"/>
<x-direct-column v-else-if="column.type == 'direct'" :column="column" :is-stacked="isStacked" v-on="$listeners"/>
</template>
<script lang="ts">
@@ -43,11 +43,7 @@ export default Vue.extend({
methods: {
focus() {
this.$children[0].focus();
},
parentFocus(direction) {
this.$emit('parentFocus', direction);
},
}
}
});
</script>

View File

@@ -2,7 +2,8 @@
<div class="dnpfarvgbnfmyzbdquhhzyxcmstpdqzs" :class="{ naked, narrow, active, isStacked, draghover, dragging, dropready }"
@dragover.prevent.stop="onDragover"
@dragleave="onDragleave"
@drop.prevent.stop="onDrop">
@drop.prevent.stop="onDrop"
v-hotkey="keymap">
<header :class="{ indicate: count > 0 }"
draggable="true"
@click="goTop"
@@ -66,6 +67,15 @@ export default Vue.extend({
computed: {
isTemporaryColumn(): boolean {
return this.column == null;
},
keymap(): any {
return {
'shift+up': () => this.$parent.$emit('parentFocus', 'up'),
'shift+down': () => this.$parent.$emit('parentFocus', 'down'),
'shift+left': () => this.$parent.$emit('parentFocus', 'left'),
'shift+right': () => this.$parent.$emit('parentFocus', 'right'),
};
}
},

View File

@@ -2,7 +2,7 @@
<x-column :name="name" :column="column" :is-stacked="isStacked">
<span slot="header">%fa:envelope R%{{ name }}</span>
<x-direct @parentFocus="parentFocus"/>
<x-direct/>
</x-column>
</template>
@@ -38,11 +38,7 @@ export default Vue.extend({
methods: {
focus() {
this.$refs.tl.focus();
},
parentFocus(direction) {
this.$emit('parentFocus', direction);
},
}
}
});
</script>

View File

@@ -1,5 +1,5 @@
<template>
<x-notes ref="timeline" :more="existMore ? more : null" @parentFocus="parentFocus"/>
<x-notes ref="timeline" :more="existMore ? more : null"/>
</template>
<script lang="ts">
@@ -93,11 +93,7 @@ export default Vue.extend({
focus() {
this.$refs.timeline.focus();
},
parentFocus(direction) {
this.$emit('parentFocus', direction);
},
}
}
});
</script>

View File

@@ -4,7 +4,10 @@
%fa:hashtag%<span>{{ tag }}</span>
</span>
<x-hashtag-tl :tag-tl="tagTl"/>
<div class="xroyrflcmhhtmlwmyiwpfqiirqokfueb">
<div ref="chart" class="chart"></div>
<x-hashtag-tl :tag-tl="tagTl" class="tl"/>
</div>
</x-column>
</template>
@@ -12,6 +15,7 @@
import Vue from 'vue';
import XColumn from './deck.column.vue';
import XHashtagTl from './deck.hashtag-tl.vue';
import * as ApexCharts from 'apexcharts';
export default Vue.extend({
components: {
@@ -32,6 +36,77 @@ export default Vue.extend({
query: [[this.tag]]
};
}
},
mounted() {
(this as any).api('charts/hashtag', {
tag: this.tag,
span: 'hour',
limit: 24
}).then(stats => {
const local = [];
const remote = [];
const now = new Date();
const y = now.getFullYear();
const m = now.getMonth();
const d = now.getDate();
const h = now.getHours();
for (let i = 0; i < 24; i++) {
const x = new Date(y, m, d, h - i);
local.push([x, stats.local.count[i]]);
remote.push([x, stats.remote.count[i]]);
}
const chart = new ApexCharts(this.$refs.chart, {
chart: {
type: 'area',
height: 70,
sparkline: {
enabled: true
},
},
grid: {
clipMarkers: false,
padding: {
top: 16,
right: 16,
bottom: 16,
left: 16
}
},
stroke: {
curve: 'straight',
width: 2
},
series: [{
name: 'Local',
data: local
}, {
name: 'Remote',
data: remote
}],
xaxis: {
type: 'datetime',
}
});
chart.render();
});
}
});
</script>
<style lang="stylus" scoped>
.xroyrflcmhhtmlwmyiwpfqiirqokfueb
background var(--deckColumnBg)
> .chart
margin-bottom 16px
background var(--face)
> .tl
background var(--face)
</style>

View File

@@ -1,5 +1,5 @@
<template>
<x-notes ref="timeline" :more="existMore ? more : null" :media-view="mediaView" @parentFocus="parentFocus"/>
<x-notes ref="timeline" :more="existMore ? more : null" :media-view="mediaView"/>
</template>
<script lang="ts">
@@ -118,11 +118,7 @@ export default Vue.extend({
focus() {
this.$refs.timeline.focus();
},
parentFocus(direction) {
this.$emit('parentFocus', direction);
},
}
}
});
</script>

View File

@@ -1,5 +1,5 @@
<template>
<x-notes ref="timeline" :more="existMore ? more : null" :media-view="mediaView" @parentFocus="parentFocus"/>
<x-notes ref="timeline" :more="existMore ? more : null" :media-view="mediaView"/>
</template>
<script lang="ts">
@@ -128,11 +128,7 @@ export default Vue.extend({
focus() {
this.$refs.timeline.focus();
},
parentFocus(direction) {
this.$emit('parentFocus', direction);
},
}
}
});
</script>

View File

@@ -2,7 +2,7 @@
<x-column :name="name" :column="column" :is-stacked="isStacked">
<span slot="header">%fa:at%{{ name }}</span>
<x-mentions ref="tl" @parentFocus="parentFocus"/>
<x-mentions ref="tl"/>
</x-column>
</template>
@@ -38,11 +38,7 @@ export default Vue.extend({
methods: {
focus() {
this.$refs.tl.focus();
},
parentFocus(direction) {
this.$emit('parentFocus', direction);
},
}
}
});
</script>

View File

@@ -1,5 +1,5 @@
<template>
<x-notes ref="timeline" :more="existMore ? more : null" @parentFocus="parentFocus"/>
<x-notes ref="timeline" :more="existMore ? more : null"/>
</template>
<script lang="ts">
@@ -89,11 +89,7 @@ export default Vue.extend({
focus() {
this.$refs.timeline.focus();
},
parentFocus(direction) {
this.$emit('parentFocus', direction);
},
}
}
});
</script>

View File

@@ -22,8 +22,7 @@
:key="note.id"
@update:note="onNoteUpdated(i, $event)"
:media-view="mediaView"
:mini="true"
@parentFocus="parentFocus"/>
:mini="true"/>
<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
<span>%fa:angle-up%{{ note._datetext }}</span>
<span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span>
@@ -111,10 +110,6 @@ export default Vue.extend({
(this.$refs.notes as any).children[0].focus ? (this.$refs.notes as any).children[0].focus() : (this.$refs.notes as any).$el.children[0].focus();
},
parentFocus(direction) {
this.$emit('parentFocus', direction);
},
onNoteUpdated(i, note) {
Vue.set((this as any).notes, i, note);
},
@@ -241,7 +236,7 @@ export default Vue.extend({
> .date
display block
margin 0
line-height 32px
line-height 28px
font-size 12px
text-align center
color var(--dateDividerFg)

View File

@@ -178,9 +178,9 @@ export default Vue.extend({
> .date
display block
margin 0
line-height 32px
line-height 28px
text-align center
font-size 0.8em
font-size 12px
color var(--dateDividerFg)
background var(--dateDividerBg)
border-bottom solid 1px var(--faceDivider)

View File

@@ -20,21 +20,18 @@
:media-only="column.isMediaOnly"
:media-view="column.isMediaView"
ref="tl"
@parentFocus="parentFocus"
/>
<x-hashtag-tl v-else-if="column.type == 'hashtag'"
:tag-tl="$store.state.settings.tagTimelines.find(x => x.id == column.tagTlId)"
:media-only="column.isMediaOnly"
:media-view="column.isMediaView"
ref="tl"
@parentFocus="parentFocus"
/>
<x-tl v-else
:src="column.type"
:media-only="column.isMediaOnly"
:media-view="column.isMediaView"
ref="tl"
@parentFocus="parentFocus"
/>
</x-column>
</template>
@@ -100,11 +97,7 @@ export default Vue.extend({
focus() {
this.$refs.tl.focus();
},
parentFocus(direction) {
this.$emit('parentFocus', direction);
},
}
}
});
</script>

View File

@@ -1,5 +1,5 @@
<template>
<x-notes ref="timeline" :more="existMore ? more : null" :media-view="mediaView" @parentFocus="parentFocus"/>
<x-notes ref="timeline" :more="existMore ? more : null" :media-view="mediaView"/>
</template>
<script lang="ts">
@@ -143,11 +143,7 @@ export default Vue.extend({
focus() {
(this.$refs.timeline as any).focus();
},
parentFocus(direction) {
this.$emit('parentFocus', direction);
},
}
}
});
</script>

View File

@@ -24,23 +24,55 @@
<div class="description">
<misskey-flavored-markdown v-if="user.description" :text="user.description" :i="$store.state.i"/>
</div>
<div class="counts">
<div>
<b>{{ user.notesCount | number }}</b>
<span>%i18n:@posts%</span>
</div>
<div>
<b>{{ user.followingCount | number }}</b>
<span>%i18n:@following%</span>
</div>
<div>
<b>{{ user.followersCount | number }}</b>
<span>%i18n:@followers%</span>
</div>
</div>
</div>
<div class="pinned" v-if="user.pinnedNotes && user.pinnedNotes.length > 0">
<p>%fa:thumbtack% %i18n:@pinned-notes%</p>
<div class="notes">
<p class="caption" @click="toggleShowPinned">%fa:thumbtack% %i18n:@pinned-notes%</p>
<span class="angle" v-if="showPinned">%fa:angle-up%</span>
<span class="angle" v-else>%fa:angle-down%</span>
<div class="notes" v-show="showPinned">
<x-note v-for="n in user.pinnedNotes" :key="n.id" :note="n" :mini="true"/>
</div>
</div>
<div class="images" v-if="images.length > 0">
<router-link v-for="image in images"
:style="`background-image: url(${image.thumbnailUrl})`"
:key="`${image.id}:${image._note.id}`"
:to="image._note | notePage"
:title="`${image.name}\n${(new Date(image.createdAt)).toLocaleString()}`"
></router-link>
<p class="caption" @click="toggleShowImages">%fa:images R% %i18n:@images%</p>
<span class="angle" v-if="showImages">%fa:angle-up%</span>
<span class="angle" v-else>%fa:angle-down%</span>
<div v-show="showImages">
<router-link v-for="image in images"
:style="`background-image: url(${image.thumbnailUrl})`"
:key="`${image.id}:${image._note.id}`"
:to="image._note | notePage"
:title="`${image.name}\n${(new Date(image.createdAt)).toLocaleString()}`"
></router-link>
</div>
</div>
<div class="activity">
<p class="caption" @click="toggleShowActivity">%fa:chart-bar R% %i18n:@activity%</p>
<span class="angle" v-if="showActivity">%fa:angle-up%</span>
<span class="angle" v-else>%fa:angle-down%</span>
<div v-show="showActivity">
<div ref="chart"></div>
</div>
</div>
<div class="tl">
<x-notes ref="timeline" :more="existMore ? fetchMoreNotes : null"/>
<p class="caption">%fa:comment-alt R% %i18n:@timeline%</p>
<div>
<x-notes ref="timeline" :more="existMore ? fetchMoreNotes : null"/>
</div>
</div>
</div>
</x-column>
@@ -56,6 +88,7 @@ import Menu from '../../../../common/views/components/menu.vue';
import MkUserListsWindow from '../../components/user-lists-window.vue';
import Ok from '../../../../common/views/components/ok.vue';
import { concat } from '../../../../../../prelude/array';
import * as ApexCharts from 'apexcharts';
const fetchLimit = 10;
@@ -80,7 +113,10 @@ export default Vue.extend({
existMore: false,
moreFetching: false,
withFiles: false,
images: []
images: [],
showPinned: true,
showImages: true,
showActivity: true
};
},
@@ -127,6 +163,86 @@ export default Vue.extend({
const files = concat(notes.map((n: any): any[] => n.files));
this.images = files.filter(f => image.includes(f.type)).slice(0, 9);
});
(this as any).api('charts/user/notes', {
userId: this.user.id,
span: 'day',
limit: 21
}).then(stats => {
const normal = [];
const reply = [];
const renote = [];
const now = new Date();
const y = now.getFullYear();
const m = now.getMonth();
const d = now.getDate();
for (let i = 0; i < 21; i++) {
const x = new Date(y, m, d - i);
normal.push([
x,
stats.diffs.normal[i]
]);
reply.push([
x,
stats.diffs.reply[i]
]);
renote.push([
x,
stats.diffs.renote[i]
]);
}
const chart = new ApexCharts(this.$refs.chart, {
chart: {
type: 'bar',
stacked: true,
height: 100,
sparkline: {
enabled: true
},
},
plotOptions: {
bar: {
columnWidth: '90%',
endingShape: 'rounded'
}
},
grid: {
clipMarkers: false,
padding: {
top: 16,
right: 16,
bottom: 16,
left: 16
}
},
tooltip: {
shared: true,
intersect: false
},
series: [{
name: 'Normal',
data: normal
}, {
name: 'Reply',
data: reply
}, {
name: 'Renote',
data: renote
}],
xaxis: {
type: 'datetime',
crosshairs: {
width: 1,
opacity: 1
}
}
});
chart.render();
});
});
},
@@ -198,6 +314,18 @@ export default Vue.extend({
compact: false,
items: menu
});
},
toggleShowPinned() {
this.showPinned = !this.showPinned;
},
toggleShowImages() {
this.showImages = !this.showImages;
},
toggleShowActivity() {
this.showActivity = !this.showActivity;
}
}
});
@@ -205,7 +333,7 @@ export default Vue.extend({
<style lang="stylus" scoped>
.zubukjlciycdsyynicqrnlsmdwmymzqu
background var(--deckUserColumnBg)
background var(--deckColumnBg)
> .is-remote
padding 8px 16px
@@ -265,7 +393,6 @@ export default Vue.extend({
color var(--text)
text-align center
background var(--face)
border-bottom solid 1px var(--faceDivider)
&:before
content ""
@@ -281,35 +408,65 @@ export default Vue.extend({
border-right solid 16px transparent
border-bottom solid 16px var(--face)
> .pinned
padding-bottom 16px
background var(--deckUserColumnBg)
> .counts
display grid
grid-template-columns 1fr 1fr 1fr
margin-top 8px
border-top solid 1px var(--faceDivider)
> p
> div
padding 8px 8px 0 8px
text-align center
> b
display block
font-size 110%
> span
display block
font-size 80%
opacity 0.7
> *
> p.caption
margin 0
padding 8px 16px
font-size 12px
color var(--text)
& + .angle
position absolute
top 0
right 8px
padding 6px
font-size 14px
color var(--text)
> .pinned
> .notes
background var(--face)
> .images
display grid
grid-template-rows 1fr 1fr 1fr
grid-template-columns 1fr 1fr 1fr
gap 4px
height 250px
padding 16px
margin-bottom 16px
background var(--face)
> div
display grid
grid-template-columns 1fr 1fr 1fr
gap 8px
padding 16px
background var(--face)
> *
background-position center center
background-size cover
background-clip content-box
> *
height 70px
background-position center center
background-size cover
background-clip content-box
border-radius 4px
> .activity
> div
background var(--face)
> .tl
background var(--face)
> div
background var(--face)
</style>

View File

@@ -18,7 +18,7 @@
</div>
<div class="info">
<span class="location" v-if="user.host === null && user.profile.location">%fa:map-marker% {{ user.profile.location }}</span>
<span class="birthday" v-if="user.host === null && user.profile.birthday">%fa:birthday-cake% {{ user.profile.birthday.replace('-', '').replace('-', '') + '' }} ({{ age }})</span>
<span class="birthday" v-if="user.host === null && user.profile.birthday">%fa:birthday-cake% {{ user.profile.birthday.replace('-', '%i18n:@year%').replace('-', '%i18n:@month%') + '%i18n:@day%' }} ({{ age }}%i18n:@years-old%)</span>
</div>
<div class="status">
<span class="notes-count"><b>{{ user.notesCount | number }}</b>%i18n:@posts%</span>

View File

@@ -1,12 +1,12 @@
<template>
<mk-ui>
<b-card header="アプリを管理">
<b-button to="/app/new" variant="primary">アプリ作成</b-button>
<b-card header="%i18n:@manage-apps%">
<b-button to="/app/new" variant="primary">%i18n:@create-app%</b-button>
<hr>
<div class="apps">
<p v-if="fetching">読み込み中</p>
<p v-if="fetching">%i18n:common.loading%</p>
<template v-if="!fetching">
<b-alert v-if="apps.length == 0">アプリなし</b-alert>
<b-alert v-if="apps.length == 0">%i18n:@app-missing%</b-alert>
<b-list-group v-else>
<b-list-group-item v-for="app in apps" :key="app.id" :to="`/app/${app.id}`">
{{ app.name }}

View File

@@ -1,34 +1,34 @@
<template>
<mk-ui>
<b-card header="アプリケーションの作成">
<b-card header="%i18n:@create-app%">
<b-form @submit.prevent="onSubmit" autocomplete="off">
<b-form-group label="アプリケーション名" description="あなたのアプリの名称。">
<b-form-input v-model="name" type="text" placeholder="ex) Misskey for iOS" autocomplete="off" required/>
<b-form-group label="%i18n:@app-name%" description="%i18n:@app-name-desc%">
<b-form-input v-model="name" type="text" placeholder="%i18n:@app-name-ex%" autocomplete="off" required/>
</b-form-group>
<b-form-group label="アプリの概要" description="あなたのアプリの簡単な説明や紹介。">
<b-textarea v-model="description" placeholder="ex) Misskey iOSクライアント。" autocomplete="off" required></b-textarea>
<b-form-group label="%i18n:@app-overview%" description="%i18n:@app-desc%">
<b-textarea v-model="description" placeholder="%i18n:@app-desc-ex%" autocomplete="off" required></b-textarea>
</b-form-group>
<b-form-group label="コールバックURL (オプション)" description="ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。">
<b-form-group label="%i18n:@callback-url%" description="%i18n:@callback-url-desc%">
<b-input v-model="cb" type="url" placeholder="ex) https://your.app.example.com/callback.php" autocomplete="off"/>
</b-form-group>
<b-card header="権限">
<b-form-group description="ここで要求した機能だけがAPIからアクセスできます。">
<b-alert show variant="warning">%fa:exclamation-triangle%アプリ作成後も変更できますが新たな権限を付与する場合その時点で関連付けられているユーザーキーはすべて無効になります</b-alert>
<b-card header="%i18n:@authority%">
<b-form-group description="%i18n:@authority-desc%">
<b-alert show variant="warning">%fa:exclamation-triangle% %i18n:@authority-warning%</b-alert>
<b-form-checkbox-group v-model="permission" stacked>
<b-form-checkbox value="account-read">アカウントの情報を見る</b-form-checkbox>
<b-form-checkbox value="account-write">アカウントの情報を操作する</b-form-checkbox>
<b-form-checkbox value="note-write">投稿する</b-form-checkbox>
<b-form-checkbox value="reaction-write">リアクションしたりリアクションをキャンセルする</b-form-checkbox>
<b-form-checkbox value="following-write">フォローしたりフォロー解除する</b-form-checkbox>
<b-form-checkbox value="drive-read">ドライブを見る</b-form-checkbox>
<b-form-checkbox value="drive-write">ドライブを操作する</b-form-checkbox>
<b-form-checkbox value="notification-read">通知を見る</b-form-checkbox>
<b-form-checkbox value="notification-write">通知を操作する</b-form-checkbox>
<b-form-checkbox value="account-read">%i18n:@account-read%</b-form-checkbox>
<b-form-checkbox value="account-write">%i18n:@account-write%</b-form-checkbox>
<b-form-checkbox value="note-write">%i18n:@note-write%</b-form-checkbox>
<b-form-checkbox value="reaction-write">%i18n:@reaction-write%</b-form-checkbox>
<b-form-checkbox value="following-write">%i18n:@following-write%</b-form-checkbox>
<b-form-checkbox value="drive-read">%i18n:@drive-read%</b-form-checkbox>
<b-form-checkbox value="drive-write">%i18n:@drive-write%</b-form-checkbox>
<b-form-checkbox value="notification-read">%i18n:@notification-read%</b-form-checkbox>
<b-form-checkbox value="notification-write">%i18n:@notification-write%</b-form-checkbox>
</b-form-checkbox-group>
</b-form-group>
</b-card>
<hr>
<b-button type="submit" variant="primary">アプリ作成</b-button>
<b-button type="submit" variant="primary">%i18n:@create-app%</b-button>
</b-form>
</b-card>
</mk-ui>
@@ -56,7 +56,7 @@ export default Vue.extend({
}).then(() => {
location.href = '/dev/apps';
}).catch(() => {
alert('アプリの作成に失敗しました。再度お試しください。');
alert('%i18n:common.dev.failed-to-create%');
});
}
}

View File

@@ -167,6 +167,12 @@ export default (callback: (launch: (router: VueRouter, api?: (os: MiOS) => API)
}
}, false);
window.addEventListener('scroll', () => {
if (window.scrollY <= 8) {
os.store.commit('clearBehindNotes');
}
}, { passive: true });
Vue.mixin({
data() {
return {

View File

@@ -103,11 +103,11 @@ export default Vue.extend({
mounted() {
this.connection = (this as any).os.stream.useSharedConnection('drive');
this.connection.on('file_created', this.onStreamDriveFileCreated);
this.connection.on('file_updated', this.onStreamDriveFileUpdated);
this.connection.on('file_deleted', this.onStreamDriveFileDeleted);
this.connection.on('folder_created', this.onStreamDriveFolderCreated);
this.connection.on('folder_updated', this.onStreamDriveFolderUpdated);
this.connection.on('fileCreated', this.onStreamDriveFileCreated);
this.connection.on('fileUpdated', this.onStreamDriveFileUpdated);
this.connection.on('fileDeleted', this.onStreamDriveFileDeleted);
this.connection.on('folderCreated', this.onStreamDriveFolderCreated);
this.connection.on('folderUpdated', this.onStreamDriveFolderUpdated);
if (this.initFolder) {
this.cd(this.initFolder, true);

View File

@@ -174,7 +174,7 @@
desktopSettingsNavItemHover: ':lighten<10<$text',
deckAcrylicColumnBg: 'rgba(0, 0, 0, 0.25)',
deckUserColumnBg: ':darken<3<@face',
deckColumnBg: ':darken<3<@face',
mobileHeaderBg: ':lighten<5<$secondary',
mobileHeaderFg: '$text',

View File

@@ -174,7 +174,7 @@
desktopSettingsNavItemHover: ':darken<10<$text',
deckAcrylicColumnBg: 'rgba(0, 0, 0, 0.1)',
deckUserColumnBg: ':darken<4<@face',
deckColumnBg: ':darken<4<@face',
mobileHeaderBg: ':lighten<5<$secondary',
mobileHeaderFg: '$text',

View File

@@ -21,3 +21,20 @@
> .host
opacity 0.7
#stability
padding 8px 12px
color #fff
border-radius 4px
&.deprecated
background #f42443
&.experimental
background #f2781a
&.stable
background #3dcc90
> b
margin-left 4px

View File

@@ -14,6 +14,11 @@ block main
| /
span.path= endpointUrl.path
- var stability = endpoint.stability || 'experimental';
p#stability(class=stability)
| Stability:
b= stability
if endpoint.desc
p#desc= endpoint.desc[lang] || endpoint.desc['ja-JP']

View File

@@ -1,19 +1,14 @@
# ストリーミングAPI
ストリーミングAPIを使うと、リアルタイムで様々な情報(例えばタイムラインに新しい投稿が流れてきた、メッセージが届いた、フォローされた、など)を受け取ったり、HTTPリクエストを発生させることなくAPIにアクセスしたりすることができます。
ストリーミングAPIは複数の種類がありますが、ここではメインとなる「ホームストリーム」について説明します。
ストリーミングAPIを使うと、リアルタイムで様々な情報(例えばタイムラインに新しい投稿が流れてきた、メッセージが届いた、フォローされた、など)を受け取ったり、様々な操作を行ったりすることができます。
## ストリームに接続する
以下のURLに**websocket**接続ます。
```
%URL%
```
ストリーミングAPIを利用するには、まずMisskeyサーバーに**websocket**接続する必要があります。
接続する際は`i`というパラメータ名で認証情報を含めます。例:
以下のURLに`i`というパラメータ名で認証情報を含めて、websocket接続してください。例:
```
%URL%/?i=xxxxxxxxxxxxxxx
%URL%/streaming?i=xxxxxxxxxxxxxxx
```
認証情報は、自分のAPIキーや、アプリケーションからストリームに接続する際はユーザーのアクセストークンのことを指します。
@@ -22,12 +17,116 @@
<p><i class="fas fa-info-circle"></i> 認証情報の取得については、<a href="./api">こちらのドキュメント</a>をご確認ください。</p>
</div>
---
認証情報は省略することもできますが、その場合非ログインでの利用ということになり、受信できる情報や可能な操作は限られます。例:
```
%URL%/streaming
```
---
ストリームに接続すると、後述するAPI操作や、投稿の購読を行ったりすることができます。
しかしまだこの段階では、例えばタイムラインへの新しい投稿を受信したりすることはできません。
それを行うには、ストリーム上で、後述する**チャンネル**に接続する必要があります。
**ストリームでのやり取りはすべてJSONです。**
## チャンネル
MisskeyのストリーミングAPIにはチャンネルという概念があります。これは、送受信する情報を分離するための仕組みです。
Misskeyのストリームに接続しただけでは、まだリアルタイムでタイムラインの投稿を受信したりはできません。
ストリーム上でチャンネルに接続することで、様々な情報を受け取ったり情報を送信したりすることができるようになります。
### チャンネルに接続する
チャンネルに接続するには、次のようなデータをJSONでストリームに送信します:
```json
{
type: 'connect',
body: {
channel: 'xxxxxxxx',
id: 'foobar',
params: {
...
}
}
}
```
ここで、
* `channel`には接続したいチャンネル名を設定します。チャンネルの種類については後述します。
* `id`にはそのチャンネルとやり取りするための任意のIDを設定します。ストリームでは様々なメッセージが流れるので、そのメッセージがどのチャンネルからのものなのか識別する必要があるからです。このIDは、UUIDや、乱数のようなもので構いません。
* `params`はチャンネルに接続する際のパラメータです。チャンネルによって接続時に必要とされるパラメータは異なります。パラメータ不要のチャンネルに接続する際は、このプロパティは省略可能です。
<div class="ui info">
<p><i class="fas fa-info-circle"></i> IDはチャンネルごとではなく「チャンネルの接続ごと」です。なぜなら、同じチャンネルに異なるパラメータで複数接続するケースもあるからです。</p>
</div>
### チャンネルからのメッセージを受け取る
例えばタイムラインのチャンネルなら、新しい投稿があった時にメッセージを発します。そのメッセージを受け取ることで、タイムラインに新しい投稿がされたことをリアルタイムで知ることができます。
チャンネルがメッセージを発すると、次のようなデータがJSONでストリームに流れてきます:
```json
{
type: 'channel',
body: {
id: 'foobar',
type: 'something',
body: {
some: 'thing'
}
}
}
```
ここで、
* `id`には前述したそのチャンネルに接続する際に設定したIDが設定されています。これで、このメッセージがどのチャンネルからのものなのか知ることができます。
* `type`にはメッセージの種類が設定されます。チャンネルによって、どのような種類のメッセージが流れてくるかは異なります。
* `body`にはメッセージの内容が設定されます。チャンネルによって、どのような内容のメッセージが流れてくるかは異なります。
### チャンネルに向けてメッセージを送信する
チャンネルによっては、メッセージを受け取るだけでなく、こちらから何かメッセージを送信し、何らかの操作を行える場合があります。
チャンネルにメッセージを送信するには、次のようなデータをJSONでストリームに送信します:
```json
{
type: 'channel',
body: {
id: 'foobar',
type: 'something',
body: {
some: 'thing'
}
}
}
```
ここで、
* `id`には前述したそのチャンネルに接続する際に設定したIDを設定します。これで、このメッセージがどのチャンネルに向けたものなのか識別させることができます。
* `type`にはメッセージの種類を設定します。チャンネルによって、どのような種類のメッセージを受け付けるかは異なります。
* `body`にはメッセージの内容を設定します。チャンネルによって、どのような内容のメッセージを受け付けるかは異なります。
### チャンネルから切断する
チャンネルから切断するには、次のようなデータをJSONでストリームに送信します:
```json
{
type: 'disconnect',
body: {
id: 'foobar'
}
}
```
ここで、
* `id`には前述したそのチャンネルに接続する際に設定したIDを設定します。
## ストリームを経由してAPIリクエストする
ストリームを経由してAPIリクエストすると、HTTPリクエストを発生させずにAPIを利用できます。そのため、コードを簡潔にできたり、パフォーマンスの向上を見込めるかもしれません。
ストリームを経由してAPIリクエストするには、次のようなメッセージをストリームに送信します:
ストリームを経由してAPIリクエストするには、次のようなデータをJSONでストリームに送信します:
```json
{
type: 'api',
@@ -39,11 +138,10 @@
}
```
`id`には、APIのレスポンスを識別するための、APIリクエストごとの一意なIDを設定する必要があります。UUIDや、簡単な乱数のようなもので構いません。
`endpoint`には、あなたがリクエストしたいAPIのエンドポイントを指定します。
`data`には、エンドポイントのパラメータを含めます。
ここで、
* `id`には、APIのレスポンスを識別するための、APIリクエストごとの一意なIDを設定する必要があります。UUIDや、簡単な乱数のようなもので構いません。
* `endpoint`には、あなたがリクエストしたいAPIのエンドポイントを指定します。
* `data`には、エンドポイントのパラメータを含めます。
<div class="ui info">
<p><i class="fas fa-info-circle"></i> APIのエンドポイントやパラメータについてはAPIリファレンスをご確認ください。</p>
@@ -62,9 +160,9 @@ APIへリクエストすると、レスポンスがストリームから次の
}
```
`xxxxxxxxxxxxxxxx`の部分には、リクエストの際に設定された`id`が含まれています。これにより、どのリクエストに対するレスポンスなのか判別することができます。
`body`には、レスポンスが含まれています。
ここで、
* `xxxxxxxxxxxxxxxx`の部分には、リクエストの際に設定された`id`が含まれています。これにより、どのリクエストに対するレスポンスなのか判別することができます。
* `body`には、レスポンスが含まれています。
## 投稿のキャプチャ
@@ -82,12 +180,15 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
```json
{
type: 'capture',
id: 'xxxxxxxxxxxxxxxx'
type: 'subNote',
body: {
id: 'xxxxxxxxxxxxxxxx'
}
}
```
`id`には、キャプチャしたい投稿の`id`を設定します。
ここで、
* `id`にキャプチャしたい投稿の`id`を設定します。
このメッセージを送信すると、Misskeyにキャプチャを要請したことになり、以後、その投稿に関するイベントが流れてくるようになります。
@@ -97,22 +198,83 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
{
type: 'noteUpdated',
body: {
note: {
...
id: 'xxxxxxxxxxxxxxxx',
type: 'reacted',
body: {
reaction: 'like',
userId: 'yyyyyyyyyyyyyyyy'
}
}
}
```
`body`内の`note`には、その投稿の最新の情報が含まれています。
ここで、
* `body`内の`id`に、イベントを発生させた投稿のIDが設定されます。
* `body`内の`type`に、イベントの種類が設定されます。
* `body`内の`body`に、イベントの詳細が設定されます。
---
#### イベントの種類
このように、投稿の情報が更新されると、`noteUpdated`イベントが流れてくるようになります。`noteUpdated`イベントが発生するのは、以下の場合です:
##### `reacted`
その投稿にリアクションがされた時に発生します。
- 投稿にリアクションが付いた
- 投稿に添付されたアンケートに投票がされた
- 投稿が削除された
* `reaction`に、リアクションの種類が設定されます。
* `userId`に、リアクションを行ったユーザーのIDが設定されます。
例:
```json
{
type: 'noteUpdated',
body: {
id: 'xxxxxxxxxxxxxxxx',
type: 'reacted',
body: {
reaction: 'like',
userId: 'yyyyyyyyyyyyyyyy'
}
}
}
```
##### `deleted`
その投稿が削除された時に発生します。
* `deletedAt`に、削除日時が設定されます。
例:
```json
{
type: 'noteUpdated',
body: {
id: 'xxxxxxxxxxxxxxxx',
type: 'deleted',
body: {
deletedAt: '2018-10-22T02:17:09.703Z'
}
}
}
```
##### `pollVoted`
その投稿に添付されたアンケートに投票された時に発生します。
* `choice`に、選択肢IDが設定されます。
* `userId`に、投票を行ったユーザーのIDが設定されます。
例:
```json
{
type: 'noteUpdated',
body: {
id: 'xxxxxxxxxxxxxxxx',
type: 'pollVoted',
body: {
choice: 2,
userId: 'yyyyyyyyyyyyyyyy'
}
}
}
```
### 投稿のキャプチャを解除する
@@ -122,62 +284,73 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
```json
{
type: 'decapture',
id: 'xxxxxxxxxxxxxxxx'
type: 'unsubNote',
body: {
id: 'xxxxxxxxxxxxxxxx'
}
}
```
`id`には、キャプチャを解除したい投稿の`id`を設定します。
ここで、
* `id`にキャプチャを解除したい投稿の`id`を設定します。
このメッセージを送信すると、以後、その投稿に関するイベントは流れてこないようになります。
## 流れてくるイベント一覧
# チャンネル一覧
## `main`
アカウントに関する基本的な情報が流れてきます。このチャンネルにパラメータはありません。
流れてくるすべてのメッセージはJSON形式で、必ず`type`というプロパティが含まれています。これにより、メッセージの種類(イベント)を判別することができます。
### `note`
タイムラインに新しい投稿が流れてきたときに発生するイベントです。
`body`プロパティの中に、投稿情報が含まれています。
### `renote`
### 流れてくるイベント一覧
#### `renote`
自分の投稿がRenoteされた時に発生するイベントです。自分自身の投稿をRenoteしたときは発生しません。
`body`プロパティの中に、Renoteされた投稿情報が含まれています。
### `mention`
#### `mention`
誰かからメンションされたときに発生するイベントです。
`body`プロパティの中に、投稿情報が含まれています。
### `readAllNotifications`
#### `readAllNotifications`
自分宛ての通知がすべて既読になったことを表すイベントです。このイベントを利用して、「通知があることを示すアイコン」のようなものをオフにしたりする等のケースが想定されます。
### `meUpdated`
#### `meUpdated`
自分の情報が更新されたことを表すイベントです。
`body`プロパティの中に、最新の自分のアカウントの情報が含まれています。
### `follow`
#### `follow`
自分が誰かをフォローしたときに発生するイベントです。
`body`プロパティの中に、フォローしたユーザーの情報が含まれています。
### `unfollow`
#### `unfollow`
自分が誰かのフォローを解除したときに発生するイベントです。
`body`プロパティの中に、フォロー解除したユーザーの情報が含まれています。
### `followed`
#### `followed`
自分が誰かにフォローされたときに発生するイベントです。
`body`プロパティの中に、フォローしてきたユーザーの情報が含まれています。
## `homeTimeline`
ホームタイムラインの投稿情報が流れてきます。このチャンネルにパラメータはありません。
### 流れてくるイベント一覧
#### `note`
タイムラインに新しい投稿が流れてきたときに発生するイベントです。
## `localTimeline`
ローカルタイムラインの投稿情報が流れてきます。このチャンネルにパラメータはありません。
### 流れてくるイベント一覧
#### `note`
ローカルタイムラインに新しい投稿が流れてきたときに発生するイベントです。
## `hybridTimeline`
ソーシャルタイムラインの投稿情報が流れてきます。このチャンネルにパラメータはありません。
### 流れてくるイベント一覧
#### `note`
ソーシャルタイムラインに新しい投稿が流れてきたときに発生するイベントです。
## `globalTimeline`
グローバルタイムラインの投稿情報が流れてきます。このチャンネルにパラメータはありません。
### 流れてくるイベント一覧
#### `note`
グローバルタイムラインに新しい投稿が流れてきたときに発生するイベントです。

View File

@@ -1,6 +1,9 @@
@import "../client/style"
@import "./ui"
html
--primary #fb4e4e
body
margin 0
color #34495e

View File

@@ -42,6 +42,11 @@ export type IMetadata = {
storageProps?: any;
isSensitive?: boolean;
/**
* このファイルが添付された投稿のID一覧
*/
attachedNoteIds?: mongo.ObjectID[];
/**
* 外部の(信頼されていない)URLへの直リンクか否か
*/

View File

@@ -1,40 +0,0 @@
import * as mongo from 'mongodb';
import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const FollowedLog = db.get<IFollowedLog>('followedLogs');
export default FollowedLog;
export type IFollowedLog = {
_id: mongo.ObjectID;
createdAt: Date;
userId: mongo.ObjectID;
count: number;
};
/**
* FollowedLogを物理削除します
*/
export async function deleteFollowedLog(followedLog: string | mongo.ObjectID | IFollowedLog) {
let f: IFollowedLog;
// Populate
if (isObjectId(followedLog)) {
f = await FollowedLog.findOne({
_id: followedLog
});
} else if (typeof followedLog === 'string') {
f = await FollowedLog.findOne({
_id: new mongo.ObjectID(followedLog)
});
} else {
f = followedLog as IFollowedLog;
}
if (f == null) return;
// このFollowedLogを削除
await FollowedLog.remove({
_id: f._id
});
}

View File

@@ -1,40 +0,0 @@
import * as mongo from 'mongodb';
import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const FollowingLog = db.get<IFollowingLog>('followingLogs');
export default FollowingLog;
export type IFollowingLog = {
_id: mongo.ObjectID;
createdAt: Date;
userId: mongo.ObjectID;
count: number;
};
/**
* FollowingLogを物理削除します
*/
export async function deleteFollowingLog(followingLog: string | mongo.ObjectID | IFollowingLog) {
let f: IFollowingLog;
// Populate
if (isObjectId(followingLog)) {
f = await FollowingLog.findOne({
_id: followingLog
});
} else if (typeof followingLog === 'string') {
f = await FollowingLog.findOne({
_id: new mongo.ObjectID(followingLog)
});
} else {
f = followingLog as IFollowingLog;
}
if (f == null) return;
// このFollowingLogを削除
await FollowingLog.remove({
_id: f._id
});
}

View File

@@ -1,228 +0,0 @@
import * as mongo from 'mongodb';
import db from '../db/mongodb';
const Stats = db.get<IStats>('stats');
Stats.createIndex({ span: -1, date: -1 }, { unique: true });
export default Stats;
export interface IStats {
_id: mongo.ObjectID;
/**
* 集計日時
*/
date: Date;
/**
* 集計期間
*/
span: 'day' | 'hour';
/**
* ユーザーに関する統計
*/
users: {
local: {
/**
* 集計期間時点での、全ユーザー数 (ローカル)
*/
total: number;
/**
* 増加したユーザー数 (ローカル)
*/
inc: number;
/**
* 減少したユーザー数 (ローカル)
*/
dec: number;
};
remote: {
/**
* 集計期間時点での、全ユーザー数 (リモート)
*/
total: number;
/**
* 増加したユーザー数 (リモート)
*/
inc: number;
/**
* 減少したユーザー数 (リモート)
*/
dec: number;
};
};
/**
* 投稿に関する統計
*/
notes: {
local: {
/**
* 集計期間時点での、全投稿数 (ローカル)
*/
total: number;
/**
* 増加した投稿数 (ローカル)
*/
inc: number;
/**
* 減少した投稿数 (ローカル)
*/
dec: number;
diffs: {
/**
* 通常の投稿数の差分 (ローカル)
*/
normal: number;
/**
* リプライの投稿数の差分 (ローカル)
*/
reply: number;
/**
* Renoteの投稿数の差分 (ローカル)
*/
renote: number;
};
};
remote: {
/**
* 集計期間時点での、全投稿数 (リモート)
*/
total: number;
/**
* 増加した投稿数 (リモート)
*/
inc: number;
/**
* 減少した投稿数 (リモート)
*/
dec: number;
diffs: {
/**
* 通常の投稿数の差分 (リモート)
*/
normal: number;
/**
* リプライの投稿数の差分 (リモート)
*/
reply: number;
/**
* Renoteの投稿数の差分 (リモート)
*/
renote: number;
};
};
};
/**
* ドライブ(のファイル)に関する統計
*/
drive: {
local: {
/**
* 集計期間時点での、全ドライブファイル数 (ローカル)
*/
totalCount: number;
/**
* 集計期間時点での、全ドライブファイルの合計サイズ (ローカル)
*/
totalSize: number;
/**
* 増加したドライブファイル数 (ローカル)
*/
incCount: number;
/**
* 増加したドライブ使用量 (ローカル)
*/
incSize: number;
/**
* 減少したドライブファイル数 (ローカル)
*/
decCount: number;
/**
* 減少したドライブ使用量 (ローカル)
*/
decSize: number;
};
remote: {
/**
* 集計期間時点での、全ドライブファイル数 (リモート)
*/
totalCount: number;
/**
* 集計期間時点での、全ドライブファイルの合計サイズ (リモート)
*/
totalSize: number;
/**
* 増加したドライブファイル数 (リモート)
*/
incCount: number;
/**
* 増加したドライブ使用量 (リモート)
*/
incSize: number;
/**
* 減少したドライブファイル数 (リモート)
*/
decCount: number;
/**
* 減少したドライブ使用量 (リモート)
*/
decSize: number;
};
};
/**
* ネットワークに関する統計
*/
network: {
/**
* サーバーへのリクエスト数
*/
requests: number;
/**
* 応答時間の合計
* TIP: (totalTime / requests) でひとつのリクエストに平均でどれくらいの時間がかかったか知れる
*/
totalTime: number;
/**
* 合計受信データ量
*/
incomingBytes: number;
/**
* 合計送信データ量
*/
outgoingBytes: number;
};
}

View File

@@ -18,8 +18,6 @@ import MessagingHistory, { deleteMessagingHistory } from './messaging-history';
import DriveFile, { deleteDriveFile } from './drive-file';
import DriveFolder, { deleteDriveFolder } from './drive-folder';
import PollVote, { deletePollVote } from './poll-vote';
import FollowingLog, { deleteFollowingLog } from './following-log';
import FollowedLog, { deleteFollowedLog } from './followed-log';
import SwSubscription, { deleteSwSubscription } from './sw-subscription';
import Notification, { deleteNotification } from './notification';
import UserList, { deleteUserList } from './user-list';
@@ -277,16 +275,6 @@ export async function deleteUser(user: string | mongo.ObjectID | IUser) {
await FollowRequest.find({ followeeId: u._id })
).map(x => deleteFollowRequest(x)));
// このユーザーのFollowingLogをすべて削除
await Promise.all((
await FollowingLog.find({ userId: u._id })
).map(x => deleteFollowingLog(x)));
// このユーザーのFollowedLogをすべて削除
await Promise.all((
await FollowedLog.find({ userId: u._id })
).map(x => deleteFollowedLog(x)));
// このユーザーのSwSubscriptionをすべて削除
await Promise.all((
await SwSubscription.find({ userId: u._id })

View File

@@ -10,7 +10,7 @@ import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type'
import { IDriveFile } from '../../../models/drive-file';
import Meta from '../../../models/meta';
import htmlToMFM from '../../../mfm/html-to-mfm';
import { updateUserStats } from '../../../services/update-chart';
import usersChart from '../../../chart/users';
import { URL } from 'url';
import { resolveNote } from './note';
@@ -180,7 +180,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
}
}, { upsert: true });
updateUserStats(user, true);
usersChart.update(user, true);
//#endregion
//#region アイコンとヘッダー画像をフェッチ
@@ -190,7 +190,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
].map(img =>
img == null
? Promise.resolve(null)
: resolveImage(user, img)
: resolveImage(user, img).catch(() => null)
)));
const avatarId = avatar ? avatar._id : null;
@@ -276,7 +276,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
].map(img =>
img == null
? Promise.resolve(null)
: resolveImage(exist, img)
: resolveImage(exist, img).catch(() => null)
)));
// Update user

View File

@@ -2,6 +2,8 @@ import * as path from 'path';
import * as glob from 'glob';
export interface IEndpointMeta {
stability?: 'deprecated' | 'experimental' | 'stable';
desc?: any;
params?: any;

View File

@@ -1,61 +0,0 @@
/**
* Module dependencies
*/
import $ from 'cafy'; import ID from '../../../../../misc/cafy-id';
import User from '../../../../../models/user';
import FollowedLog from '../../../../../models/followed-log';
/**
* Aggregate followers of a user
*/
export default (params: any) => new Promise(async (res, rej) => {
// Get 'userId' parameter
const [userId, userIdErr] = $.type(ID).get(params.userId);
if (userIdErr) return rej('invalid userId param');
// Lookup user
const user = await User.findOne({
_id: userId
}, {
fields: {
_id: true
}
});
if (user === null) {
return rej('user not found');
}
const today = new Date();
const graph = [];
today.setMinutes(0);
today.setSeconds(0);
today.setMilliseconds(0);
let cursorDate = new Date(today.getTime());
let cursorTime = cursorDate.setDate(new Date(today.getTime()).getDate() + 1);
for (let i = 0; i < 30; i++) {
graph.push(FollowedLog.findOne({
createdAt: { $lt: new Date(cursorTime / 1000) },
userId: user._id
}, {
sort: { createdAt: -1 },
}).then(log => {
cursorDate = new Date(today.getTime());
cursorTime = cursorDate.setDate(today.getDate() - i);
return {
date: {
year: cursorDate.getFullYear(),
month: cursorDate.getMonth() + 1, // In JavaScript, month is zero-based.
day: cursorDate.getDate()
},
count: log ? log.count : 0
};
}));
}
res(await Promise.all(graph));
});

View File

@@ -1,61 +0,0 @@
/**
* Module dependencies
*/
import $ from 'cafy'; import ID from '../../../../../misc/cafy-id';
import User from '../../../../../models/user';
import FollowingLog from '../../../../../models/following-log';
/**
* Aggregate following of a user
*/
export default (params: any) => new Promise(async (res, rej) => {
// Get 'userId' parameter
const [userId, userIdErr] = $.type(ID).get(params.userId);
if (userIdErr) return rej('invalid userId param');
// Lookup user
const user = await User.findOne({
_id: userId
}, {
fields: {
_id: true
}
});
if (user === null) {
return rej('user not found');
}
const today = new Date();
const graph = [];
today.setMinutes(0);
today.setSeconds(0);
today.setMilliseconds(0);
let cursorDate = new Date(today.getTime());
let cursorTime = cursorDate.setDate(new Date(today.getTime()).getDate() + 1);
for (let i = 0; i < 30; i++) {
graph.push(FollowingLog.findOne({
createdAt: { $lt: new Date(cursorTime / 1000) },
userId: user._id
}, {
sort: { createdAt: -1 },
}).then(log => {
cursorDate = new Date(today.getTime());
cursorTime = cursorDate.setDate(today.getDate() - i);
return {
date: {
year: cursorDate.getFullYear(),
month: cursorDate.getMonth() + 1, // In JavaScript, month is zero-based.
day: cursorDate.getDate()
},
count: log ? log.count : 0
};
}));
}
res(await Promise.all(graph));
});

View File

@@ -1,277 +0,0 @@
import $ from 'cafy';
import Stats, { IStats } from '../../../models/stats';
import getParams from '../get-params';
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
function migrateStats(stats: IStats[]) {
stats.forEach(stat => {
if (stat.network == null) {
stat.network = {
requests: 0,
totalTime: 0,
incomingBytes: 0,
outgoingBytes: 0
};
}
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 = {
desc: {
'ja-JP': 'インスタンスの統計を取得します。'
},
params: {
limit: $.num.optional.range(1, 100).note({
default: 30,
desc: {
'ja-JP': '最大数'
}
}),
}
};
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
const daysRange = ps.limit;
const hoursRange = ps.limit;
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
}
},
network: {
requests: 0,
totalTime: 0,
incomingBytes: 0,
outgoingBytes: 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
}
},
network: {
requests: 0,
totalTime: 0,
incomingBytes: 0,
outgoingBytes: 0
}
});
}
}
}
chart.forEach(x => {
delete (x as any).span;
});
return chart;
};
res({
perDay: format(statsPerDay, 'day'),
perHour: format(statsPerHour, 'hour')
});
});

View File

@@ -0,0 +1,33 @@
import $ from 'cafy';
import getParams from '../../get-params';
import driveChart from '../../../../chart/drive';
export const meta = {
desc: {
'ja-JP': 'ドライブのチャートを取得します。'
},
params: {
span: $.str.or(['day', 'hour']).note({
desc: {
'ja-JP': '集計のスパン (day または hour)'
}
}),
limit: $.num.optional.range(1, 100).note({
default: 30,
desc: {
'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
}
}),
}
};
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
const stats = await driveChart.getChart(ps.span as any, ps.limit);
res(stats);
});

View File

@@ -0,0 +1,39 @@
import $ from 'cafy';
import getParams from '../../get-params';
import hashtagChart from '../../../../chart/hashtag';
export const meta = {
desc: {
'ja-JP': 'ハッシュタグごとのチャートを取得します。'
},
params: {
span: $.str.or(['day', 'hour']).note({
desc: {
'ja-JP': '集計のスパン (day または hour)'
}
}),
limit: $.num.optional.range(1, 100).note({
default: 30,
desc: {
'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
}
}),
tag: $.str.note({
desc: {
'ja-JP': '対象のハッシュタグ'
}
}),
}
};
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
const stats = await hashtagChart.getChart(ps.span as any, ps.limit, ps.tag);
res(stats);
});

View File

@@ -0,0 +1,33 @@
import $ from 'cafy';
import getParams from '../../get-params';
import networkChart from '../../../../chart/network';
export const meta = {
desc: {
'ja-JP': 'ネットワークのチャートを取得します。'
},
params: {
span: $.str.or(['day', 'hour']).note({
desc: {
'ja-JP': '集計のスパン (day または hour)'
}
}),
limit: $.num.optional.range(1, 100).note({
default: 30,
desc: {
'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
}
}),
}
};
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
const stats = await networkChart.getChart(ps.span as any, ps.limit);
res(stats);
});

View File

@@ -0,0 +1,33 @@
import $ from 'cafy';
import getParams from '../../get-params';
import notesChart from '../../../../chart/notes';
export const meta = {
desc: {
'ja-JP': '投稿のチャートを取得します。'
},
params: {
span: $.str.or(['day', 'hour']).note({
desc: {
'ja-JP': '集計のスパン (day または hour)'
}
}),
limit: $.num.optional.range(1, 100).note({
default: 30,
desc: {
'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
}
}),
}
};
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
const stats = await notesChart.getChart(ps.span as any, ps.limit);
res(stats);
});

View File

@@ -0,0 +1,41 @@
import $ from 'cafy';
import getParams from '../../../get-params';
import perUserDriveChart from '../../../../../chart/per-user-drive';
import ID from '../../../../../misc/cafy-id';
export const meta = {
desc: {
'ja-JP': 'ユーザーごとのドライブのチャートを取得します。'
},
params: {
span: $.str.or(['day', 'hour']).note({
desc: {
'ja-JP': '集計のスパン (day または hour)'
}
}),
limit: $.num.optional.range(1, 100).note({
default: 30,
desc: {
'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
}
}),
userId: $.type(ID).note({
desc: {
'ja-JP': '対象のユーザーのID',
'en-US': 'Target user ID'
}
})
}
};
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
const stats = await perUserDriveChart.getChart(ps.span as any, ps.limit, ps.userId);
res(stats);
});

View File

@@ -0,0 +1,41 @@
import $ from 'cafy';
import getParams from '../../../get-params';
import perUserFollowingChart from '../../../../../chart/per-user-following';
import ID from '../../../../../misc/cafy-id';
export const meta = {
desc: {
'ja-JP': 'ユーザーごとのフォロー/フォロワーのチャートを取得します。'
},
params: {
span: $.str.or(['day', 'hour']).note({
desc: {
'ja-JP': '集計のスパン (day または hour)'
}
}),
limit: $.num.optional.range(1, 100).note({
default: 30,
desc: {
'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
}
}),
userId: $.type(ID).note({
desc: {
'ja-JP': '対象のユーザーのID',
'en-US': 'Target user ID'
}
})
}
};
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
const stats = await perUserFollowingChart.getChart(ps.span as any, ps.limit, ps.userId);
res(stats);
});

View File

@@ -0,0 +1,41 @@
import $ from 'cafy';
import getParams from '../../../get-params';
import perUserNotesChart from '../../../../../chart/per-user-notes';
import ID from '../../../../../misc/cafy-id';
export const meta = {
desc: {
'ja-JP': 'ユーザーごとの投稿のチャートを取得します。'
},
params: {
span: $.str.or(['day', 'hour']).note({
desc: {
'ja-JP': '集計のスパン (day または hour)'
}
}),
limit: $.num.optional.range(1, 100).note({
default: 30,
desc: {
'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
}
}),
userId: $.type(ID).note({
desc: {
'ja-JP': '対象のユーザーのID',
'en-US': 'Target user ID'
}
})
}
};
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
const stats = await perUserNotesChart.getChart(ps.span as any, ps.limit, ps.userId);
res(stats);
});

View File

@@ -0,0 +1,41 @@
import $ from 'cafy';
import getParams from '../../../get-params';
import perUserReactionsChart from '../../../../../chart/per-user-reactions';
import ID from '../../../../../misc/cafy-id';
export const meta = {
desc: {
'ja-JP': 'ユーザーごとの被リアクション数のチャートを取得します。'
},
params: {
span: $.str.or(['day', 'hour']).note({
desc: {
'ja-JP': '集計のスパン (day または hour)'
}
}),
limit: $.num.optional.range(1, 100).note({
default: 30,
desc: {
'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
}
}),
userId: $.type(ID).note({
desc: {
'ja-JP': '対象のユーザーのID',
'en-US': 'Target user ID'
}
})
}
};
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
const stats = await perUserReactionsChart.getChart(ps.span as any, ps.limit, ps.userId);
res(stats);
});

View File

@@ -0,0 +1,33 @@
import $ from 'cafy';
import getParams from '../../get-params';
import usersChart from '../../../../chart/users';
export const meta = {
desc: {
'ja-JP': 'ユーザーのチャートを取得します。'
},
params: {
span: $.str.or(['day', 'hour']).note({
desc: {
'ja-JP': '集計のスパン (day または hour)'
}
}),
limit: $.num.optional.range(1, 100).note({
default: 30,
desc: {
'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
}
}),
}
};
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
const stats = await usersChart.getChart(ps.span as any, ps.limit);
res(stats);
});

View File

@@ -0,0 +1,48 @@
import $ from 'cafy'; import ID from '../../../../../misc/cafy-id';
import DriveFile from '../../../../../models/drive-file';
import { ILocalUser } from '../../../../../models/user';
import getParams from '../../../get-params';
import { packMany } from '../../../../../models/note';
export const meta = {
stability: 'stable',
desc: {
'ja-JP': '指定したドライブのファイルが添付されている投稿一覧を取得します。',
'en-US': 'Get the notes that specified file of drive attached.'
},
requireCredential: true,
kind: 'drive-read',
params: {
fileId: $.type(ID).note({
desc: {
'ja-JP': '対象のファイルID',
'en-US': 'Target file ID'
}
})
}
};
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);
// Fetch file
const file = await DriveFile
.findOne({
_id: ps.fileId,
'metadata.userId': user._id,
'metadata.deletedAt': { $exists: false }
});
if (file === null) {
return rej('file-not-found');
}
res(await packMany(file.metadata.attachedNoteIds || [], user, {
detail: true
}));
});

View File

@@ -3,8 +3,11 @@ import DriveFile from '../../../../../models/drive-file';
import del from '../../../../../services/drive/delete-file';
import { publishDriveStream } from '../../../../../stream';
import { ILocalUser } from '../../../../../models/user';
import getParams from '../../../get-params';
export const meta = {
stability: 'stable',
desc: {
'ja-JP': 'ドライブのファイルを削除します。',
'en-US': 'Delete a file of drive.'
@@ -12,30 +15,38 @@ export const meta = {
requireCredential: true,
kind: 'drive-write'
kind: 'drive-write',
params: {
fileId: $.type(ID).note({
desc: {
'ja-JP': '対象のファイルID',
'en-US': 'Target file ID'
}
})
}
};
export default async (params: any, user: ILocalUser) => {
// Get 'fileId' parameter
const [fileId, fileIdErr] = $.type(ID).get(params.fileId);
if (fileIdErr) throw 'invalid fileId param';
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);
// Fetch file
const file = await DriveFile
.findOne({
_id: fileId,
_id: ps.fileId,
'metadata.userId': user._id
});
if (file === null) {
throw 'file-not-found';
return rej('file-not-found');
}
// Delete
await del(file);
// Publish file_deleted event
publishDriveStream(user._id, 'file_deleted', file._id);
// Publish fileDeleted event
publishDriveStream(user._id, 'fileDeleted', file._id);
return;
};
res();
});

View File

@@ -1,8 +1,11 @@
import $ from 'cafy'; import ID from '../../../../../misc/cafy-id';
import DriveFile, { pack } from '../../../../../models/drive-file';
import { ILocalUser } from '../../../../../models/user';
import getParams from '../../../get-params';
export const meta = {
stability: 'stable',
desc: {
'ja-JP': '指定したドライブのファイルの情報を取得します。',
'en-US': 'Get specified file of drive.'
@@ -10,24 +13,32 @@ export const meta = {
requireCredential: true,
kind: 'drive-read'
kind: 'drive-read',
params: {
fileId: $.type(ID).note({
desc: {
'ja-JP': '対象のファイルID',
'en-US': 'Target file ID'
}
})
}
};
export default async (params: any, user: ILocalUser) => {
// Get 'fileId' parameter
const [fileId, fileIdErr] = $.type(ID).get(params.fileId);
if (fileIdErr) throw 'invalid fileId param';
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);
// Fetch file
const file = await DriveFile
.findOne({
_id: fileId,
_id: ps.fileId,
'metadata.userId': user._id,
'metadata.deletedAt': { $exists: false }
});
if (file === null) {
throw 'file-not-found';
return rej('file-not-found');
}
// Serialize
@@ -35,5 +46,5 @@ export default async (params: any, user: ILocalUser) => {
detail: true
});
return _file;
};
res(_file);
});

View File

@@ -114,6 +114,6 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
// Response
res(fileObj);
// Publish file_updated event
publishDriveStream(user._id, 'file_updated', fileObj);
// Publish fileUpdated event
publishDriveStream(user._id, 'fileUpdated', fileObj);
});

View File

@@ -52,6 +52,6 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
// Response
res(folderObj);
// Publish folder_created event
publishDriveStream(user._id, 'folder_created', folderObj);
// Publish folderCreated event
publishDriveStream(user._id, 'folderCreated', folderObj);
});

View File

@@ -96,6 +96,6 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
// Response
res(folderObj);
// Publish folder_updated event
publishDriveStream(user._id, 'folder_updated', folderObj);
// Publish folderUpdated event
publishDriveStream(user._id, 'folderUpdated', folderObj);
});

View File

@@ -3,8 +3,11 @@ const ms = require('ms');
import User, { pack, ILocalUser } from '../../../../models/user';
import Following from '../../../../models/following';
import create from '../../../../services/following/create';
import getParams from '../../get-params';
export const meta = {
stability: 'stable',
desc: {
'ja-JP': '指定したユーザーをフォローします。',
'en-US': 'Follow a user.'
@@ -17,24 +20,32 @@ export const meta = {
requireCredential: true,
kind: 'following-write'
kind: 'following-write',
params: {
userId: $.type(ID).note({
desc: {
'ja-JP': '対象のユーザーのID',
'en-US': 'Target user ID'
}
})
}
};
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);
const follower = user;
// Get 'userId' parameter
const [userId, userIdErr] = $.type(ID).get(params.userId);
if (userIdErr) return rej('invalid userId param');
// 自分自身
if (user._id.equals(userId)) {
if (user._id.equals(ps.userId)) {
return rej('followee is yourself');
}
// Get followee
const followee = await User.findOne({
_id: userId
_id: ps.userId
}, {
fields: {
data: false,

View File

@@ -3,8 +3,11 @@ const ms = require('ms');
import User, { pack, ILocalUser } from '../../../../models/user';
import Following from '../../../../models/following';
import deleteFollowing from '../../../../services/following/delete';
import getParams from '../../get-params';
export const meta = {
stability: 'stable',
desc: {
'ja-JP': '指定したユーザーのフォローを解除します。',
'en-US': 'Unfollow a user.'
@@ -17,24 +20,32 @@ export const meta = {
requireCredential: true,
kind: 'following-write'
kind: 'following-write',
params: {
userId: $.type(ID).note({
desc: {
'ja-JP': '対象のユーザーのID',
'en-US': 'Target user ID'
}
})
}
};
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);
const follower = user;
// Get 'userId' parameter
const [userId, userIdErr] = $.type(ID).get(params.userId);
if (userIdErr) return rej('invalid userId param');
// Check if the followee is yourself
if (user._id.equals(userId)) {
if (user._id.equals(ps.userId)) {
return rej('followee is yourself');
}
// Get followee
const followee = await User.findOne({
_id: userId
_id: ps.userId
}, {
fields: {
data: false,

View File

@@ -2,6 +2,8 @@ import User, { pack, ILocalUser } from '../../../models/user';
import { IApp } from '../../../models/app';
export const meta = {
stability: 'stable',
desc: {
'ja-JP': '自分のアカウント情報を取得します。'
},

View File

@@ -5,6 +5,8 @@ import { addPinned } from '../../../../services/i/pin';
import getParams from '../../get-params';
export const meta = {
stability: 'stable',
desc: {
'ja-JP': '指定した投稿をピン留めします。'
},
@@ -16,7 +18,8 @@ export const meta = {
params: {
noteId: $.type(ID).note({
desc: {
'ja-JP': '対象の投稿のID'
'ja-JP': '対象の投稿のID',
'en-US': 'Target note ID'
}
})
}

View File

@@ -5,6 +5,8 @@ import { removePinned } from '../../../../services/i/pin';
import getParams from '../../get-params';
export const meta = {
stability: 'stable',
desc: {
'ja-JP': '指定した投稿のピン留めを解除します。'
},
@@ -16,7 +18,8 @@ export const meta = {
params: {
noteId: $.type(ID).note({
desc: {
'ja-JP': '対象の投稿のID'
'ja-JP': '対象の投稿のID',
'en-US': 'Target note ID'
}
})
}

View File

@@ -7,6 +7,8 @@ const pkg = require('../../../../package.json');
const client = require('../../../../built/client/meta.json');
export const meta = {
stability: 'stable',
desc: {
'ja-JP': 'インスタンス情報を取得します。',
'en-US': 'Get the information of this instance.'

View File

@@ -8,6 +8,8 @@ import { IApp } from '../../../../models/app';
import getParams from '../../get-params';
export const meta = {
stability: 'stable',
desc: {
'ja-JP': '投稿します。'
},

View File

@@ -2,8 +2,11 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
import Note from '../../../../models/note';
import deleteNote from '../../../../services/note/delete';
import User, { ILocalUser } from '../../../../models/user';
import getParams from '../../get-params';
export const meta = {
stability: 'stable',
desc: {
'ja-JP': '指定した投稿を削除します。',
'en-US': 'Delete a note.'
@@ -11,17 +14,25 @@ export const meta = {
requireCredential: true,
kind: 'note-write'
kind: 'note-write',
params: {
noteId: $.type(ID).note({
desc: {
'ja-JP': '対象の投稿のID',
'en-US': 'Target note ID.'
}
})
}
};
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
// Get 'noteId' parameter
const [noteId, noteIdErr] = $.type(ID).get(params.noteId);
if (noteIdErr) return rej('invalid noteId param');
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);
// Fetch note
const note = await Note.findOne({
_id: noteId
_id: ps.noteId
});
if (note === null) {

View File

@@ -5,6 +5,8 @@ import { ILocalUser } from '../../../../../models/user';
import getParams from '../../../get-params';
export const meta = {
stability: 'stable',
desc: {
'ja-JP': '指定した投稿をお気に入りに登録します。',
'en-US': 'Favorite a note.'
@@ -17,7 +19,8 @@ export const meta = {
params: {
noteId: $.type(ID).note({
desc: {
'ja-JP': '対象の投稿のID'
'ja-JP': '対象の投稿のID',
'en-US': 'Target note ID.'
}
})
}

View File

@@ -2,8 +2,11 @@ import $ from 'cafy'; import ID from '../../../../../misc/cafy-id';
import Favorite from '../../../../../models/favorite';
import Note from '../../../../../models/note';
import { ILocalUser } from '../../../../../models/user';
import getParams from '../../../get-params';
export const meta = {
stability: 'stable',
desc: {
'ja-JP': '指定した投稿のお気に入りを解除します。',
'en-US': 'Unfavorite a note.'
@@ -11,17 +14,25 @@ export const meta = {
requireCredential: true,
kind: 'favorite-write'
kind: 'favorite-write',
params: {
noteId: $.type(ID).note({
desc: {
'ja-JP': '対象の投稿のID',
'en-US': 'Target note ID.'
}
})
}
};
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
// Get 'noteId' parameter
const [noteId, noteIdErr] = $.type(ID).get(params.noteId);
if (noteIdErr) return rej('invalid noteId param');
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);
// Get favoritee
const note = await Note.findOne({
_id: noteId
_id: ps.noteId
});
if (note === null) {

View File

@@ -6,6 +6,8 @@ import { ILocalUser } from '../../../../../models/user';
import getParams from '../../../get-params';
export const meta = {
stability: 'stable',
desc: {
'ja-JP': '指定した投稿にリアクションします。',
'en-US': 'React to a note.'

View File

@@ -1,18 +1,35 @@
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
import Note, { pack } from '../../../../models/note';
import { ILocalUser } from '../../../../models/user';
import getParams from '../../get-params';
export const meta = {
stability: 'stable',
desc: {
'ja-JP': '指定した投稿を取得します。',
'en-US': 'Get a note.'
},
requireCredential: false,
params: {
noteId: $.type(ID).note({
desc: {
'ja-JP': '対象の投稿のID',
'en-US': 'Target note ID.'
}
})
}
};
/**
* Show a note
*/
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
// Get 'noteId' parameter
const [noteId, noteIdErr] = $.type(ID).get(params.noteId);
if (noteIdErr) return rej('invalid noteId param');
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);
// Get note
const note = await Note.findOne({
_id: noteId
_id: ps.noteId
});
if (note === null) {

View File

@@ -7,7 +7,7 @@ import generateUserToken from '../common/generate-native-user-token';
import config from '../../../config';
import Meta from '../../../models/meta';
import RegistrationTicket from '../../../models/registration-tickets';
import { updateUserStats } from '../../../services/update-chart';
import usersChart from '../../../chart/users';
if (config.recaptcha) {
recaptcha.init({
@@ -130,7 +130,7 @@ export default async (ctx: Koa.Context) => {
}, { upsert: true });
//#endregion
updateUserStats(account, true);
usersChart.update(account, true);
const res = await pack(account, account, {
detail: true,

Some files were not shown because too many files have changed in this diff Show More