Compare commits

...

287 Commits

Author SHA1 Message Date
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
syuilo
484f281c19 10.29.1 2018-10-21 00:33:37 +09:00
syuilo
2169bc5d3e Merge pull request #2955 from syuilo/l10n_develop
New Crowdin translations
2018-10-21 00:33:00 +09:00
syuilo
c653c84ad2 New translations ja-JP.yml (English) 2018-10-21 00:31:04 +09:00
syuilo
050f75aa60 New translations ja-JP.yml (Norwegian) 2018-10-21 00:22:05 +09:00
syuilo
dae3f3552a New translations ja-JP.yml (Dutch) 2018-10-21 00:22:02 +09:00
syuilo
8b09b170d6 New translations ja-JP.yml (Japanese, Kansai) 2018-10-21 00:21:58 +09:00
syuilo
ec88f2ed8a New translations ja-JP.yml (Spanish) 2018-10-21 00:21:53 +09:00
syuilo
607d8502ff New translations ja-JP.yml (Russian) 2018-10-21 00:21:49 +09:00
syuilo
2f084d7c15 New translations ja-JP.yml (Portuguese) 2018-10-21 00:21:45 +09:00
syuilo
5bf6e7d8f9 New translations ja-JP.yml (Polish) 2018-10-21 00:21:41 +09:00
syuilo
31cb9fbfaf New translations ja-JP.yml (Korean) 2018-10-21 00:21:37 +09:00
syuilo
c7c48f3bea New translations ja-JP.yml (Italian) 2018-10-21 00:21:33 +09:00
syuilo
6732d22e6c New translations ja-JP.yml (German) 2018-10-21 00:21:29 +09:00
syuilo
04c6b7fe31 New translations ja-JP.yml (French) 2018-10-21 00:21:25 +09:00
syuilo
2687879dbd New translations ja-JP.yml (English) 2018-10-21 00:21:21 +09:00
syuilo
20a660fa89 New translations ja-JP.yml (Chinese Simplified) 2018-10-21 00:21:16 +09:00
syuilo
ba9781e1a8 New translations ja-JP.yml (Catalan) 2018-10-21 00:21:12 +09:00
syuilo
f65ac74914 i18n 2018-10-21 00:18:01 +09:00
syuilo
6c33d9aeed 🎨 2018-10-20 23:44:35 +09:00
syuilo
68e86ad40d Fix bug 2018-10-20 23:43:42 +09:00
syuilo
0aa4aa49a7 New translations ja-JP.yml (Dutch) 2018-10-20 20:21:32 +09:00
syuilo
0ff3846e49 New translations ja-JP.yml (Portuguese) 2018-10-20 20:21:28 +09:00
syuilo
bfb81299c3 New translations ja-JP.yml (Polish) 2018-10-20 20:21:24 +09:00
syuilo
0362a8e73c New translations ja-JP.yml (English) 2018-10-20 20:21:19 +09:00
syuilo
f00f5cbed1 10.29.0 2018-10-20 15:51:47 +09:00
syuilo
c4e8cabae9 ファイル作成APIにforceオプションを実装 2018-10-20 15:50:13 +09:00
Dr. Gutfuck LLC
1729d05e8c Localized mark all as read message (#2956)
* 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
2018-10-20 15:46:01 +09:00
syuilo
770fb46ca7 Improve usability 2018-10-20 15:41:27 +09:00
syuilo
a3c4e54bc0 🎨 2018-10-20 15:37:17 +09:00
syuilo
b8a77fbada Show image info in tooltip 2018-10-20 15:21:24 +09:00
syuilo
9182ebfc19 New translations ja-JP.yml (Norwegian) 2018-10-20 12:04:11 +09:00
syuilo
25c0cf5848 New translations ja-JP.yml (Dutch) 2018-10-20 12:04:06 +09:00
syuilo
a160dc0a4d New translations ja-JP.yml (Japanese, Kansai) 2018-10-20 12:04:01 +09:00
syuilo
28f1ca9c17 New translations ja-JP.yml (Spanish) 2018-10-20 12:03:55 +09:00
syuilo
6399a0f046 New translations ja-JP.yml (Russian) 2018-10-20 12:03:49 +09:00
syuilo
639413608b New translations ja-JP.yml (Portuguese) 2018-10-20 12:03:44 +09:00
syuilo
c14e4c7d22 New translations ja-JP.yml (Polish) 2018-10-20 12:03:40 +09:00
syuilo
c74ac64237 New translations ja-JP.yml (Korean) 2018-10-20 12:03:36 +09:00
syuilo
4b3289ed99 New translations ja-JP.yml (Italian) 2018-10-20 12:03:31 +09:00
syuilo
0c432b39dc New translations ja-JP.yml (German) 2018-10-20 12:03:27 +09:00
syuilo
c4b9276713 New translations ja-JP.yml (French) 2018-10-20 12:03:22 +09:00
syuilo
df300c0663 New translations ja-JP.yml (English) 2018-10-20 12:03:17 +09:00
syuilo
518114cbbd New translations ja-JP.yml (Chinese Simplified) 2018-10-20 12:03:12 +09:00
syuilo
999f0e4d58 New translations ja-JP.yml (Catalan) 2018-10-20 12:03:08 +09:00
Dr. Gutfuck LLC
c2663529c1 Localized BSoD messages. (#2953)
* Added VSCode workspace files to :   .gitignore

* Localized Blue Screen of Death:   locales/ja-JP.yml
Localized Blue Screen of Death:   src/client/app/init.ts
2018-10-20 11:57:23 +09:00
syuilo
9df74a02b6 Fix bug 2018-10-20 11:24:02 +09:00
syuilo
71c9964e19 Fix bug and clean up 2018-10-20 11:19:27 +09:00
syuilo
ae2e47f6a9 Merge pull request #2950 from syuilo/l10n_develop
New Crowdin translations
2018-10-20 09:45:44 +09:00
syuilo
1524d35f66 New translations ja-JP.yml (English) 2018-10-20 09:41:11 +09:00
syuilo
845be966a0 10.28.0 2018-10-20 09:39:56 +09:00
syuilo
80818d79eb Fix bug 2018-10-20 09:38:36 +09:00
syuilo
cb9b3c00dd Use router-link instead of a to improve usability 2018-10-20 09:34:34 +09:00
syuilo
b3997fb5df New translations ja-JP.yml (Norwegian) 2018-10-20 09:33:07 +09:00
syuilo
09dde6b78a New translations ja-JP.yml (Dutch) 2018-10-20 09:32:57 +09:00
syuilo
3345d3ab35 New translations ja-JP.yml (Japanese, Kansai) 2018-10-20 09:32:51 +09:00
syuilo
366be7bbdd New translations ja-JP.yml (Spanish) 2018-10-20 09:32:47 +09:00
syuilo
7008ea66f8 New translations ja-JP.yml (Russian) 2018-10-20 09:32:43 +09:00
syuilo
70f881e989 New translations ja-JP.yml (Portuguese) 2018-10-20 09:32:38 +09:00
syuilo
94d2355089 New translations ja-JP.yml (Polish) 2018-10-20 09:32:33 +09:00
syuilo
dfbe48b25b New translations ja-JP.yml (Korean) 2018-10-20 09:32:29 +09:00
syuilo
931cb38b54 New translations ja-JP.yml (Italian) 2018-10-20 09:32:23 +09:00
syuilo
e5fd34f94e New translations ja-JP.yml (German) 2018-10-20 09:32:17 +09:00
syuilo
c638d7eb48 New translations ja-JP.yml (French) 2018-10-20 09:32:12 +09:00
syuilo
7e96384618 New translations ja-JP.yml (English) 2018-10-20 09:32:08 +09:00
syuilo
829cb99f5b New translations ja-JP.yml (Chinese Simplified) 2018-10-20 09:32:04 +09:00
syuilo
1f93c99304 New translations ja-JP.yml (Catalan) 2018-10-20 09:32:00 +09:00
syuilo
dbb7c756cd Fix bug 2018-10-20 09:31:56 +09:00
syuilo
13f381710c Validate param 2018-10-20 09:31:52 +09:00
syuilo
70897c0e9a 🎨 2018-10-20 09:28:48 +09:00
syuilo
f51d1c5264 Fix test 2018-10-20 09:14:16 +09:00
syuilo
70d0937aab Fix #2949 2018-10-20 09:03:04 +09:00
syuilo
7d1ab6102f 10.27.0 2018-10-20 08:04:52 +09:00
syuilo
77ddd778be ハッシュタグもデッキ内ナビゲーションするように 2018-10-20 08:03:45 +09:00
syuilo
890ecb693f Improve 2018-10-20 07:28:01 +09:00
syuilo
209fe7dcaf Improve deck usability 2018-10-20 07:21:22 +09:00
syuilo
e0d6f7c7c4 RP --> RN 2018-10-20 07:01:09 +09:00
syuilo
5d3fe9599b Improve performance 2018-10-20 06:42:19 +09:00
syuilo
0fe0b6d254 10.26.0 2018-10-20 02:52:11 +09:00
syuilo
b794216eaf Merge pull request #2940 from syuilo/l10n_develop
New Crowdin translations
2018-10-20 02:51:43 +09:00
syuilo
1fccde38f6 デッキのキーボードショートカットを強化 2018-10-20 02:49:39 +09:00
syuilo
41bd436d3e デッキのキーボードショートカットを強化 2018-10-20 02:40:37 +09:00
syuilo
c66155ed48 Improve shortcut key detection 2018-10-20 01:45:31 +09:00
syuilo
627bd410fa New translations ja-JP.yml (German) 2018-10-19 22:22:36 +09:00
syuilo
41a3932c6b New translations ja-JP.yml (English) 2018-10-19 22:12:39 +09:00
syuilo
785b8d7846 New translations ja-JP.yml (French) 2018-10-19 21:02:41 +09:00
syuilo
622c8f9598 New translations ja-JP.yml (French) 2018-10-19 20:52:24 +09:00
syuilo
ef978a6364 10.25.0 2018-10-19 15:07:46 +09:00
greenkeeper[bot]
d95fbe1c6b fix(package): update reconnecting-websocket to version 4.1.10 (#2937) 2018-10-19 15:04:46 +09:00
syuilo
d4ffddc2ab Merge pull request #2936 from syuilo/l10n_develop
New Crowdin translations
2018-10-19 15:04:36 +09:00
syuilo
3d497cedfc デッキで'T'のショートカットを使えるように 2018-10-19 15:03:23 +09:00
syuilo
e8de29ae79 Resolve #2935 2018-10-19 14:34:51 +09:00
syuilo
b622946844 10.24.0 2018-10-19 11:14:27 +09:00
syuilo
d013f78cc7 New translations ja-JP.yml (Norwegian) 2018-10-19 11:12:15 +09:00
syuilo
2afbafdb3b New translations ja-JP.yml (Dutch) 2018-10-19 11:12:11 +09:00
syuilo
67148114a8 New translations ja-JP.yml (Japanese, Kansai) 2018-10-19 11:12:05 +09:00
syuilo
7903140ec2 New translations ja-JP.yml (Spanish) 2018-10-19 11:12:01 +09:00
syuilo
cefd296200 New translations ja-JP.yml (Russian) 2018-10-19 11:11:55 +09:00
syuilo
99d1c15851 New translations ja-JP.yml (Portuguese) 2018-10-19 11:11:51 +09:00
syuilo
a3107ab26f New translations ja-JP.yml (Polish) 2018-10-19 11:11:46 +09:00
syuilo
854cfae75b New translations ja-JP.yml (Korean) 2018-10-19 11:11:42 +09:00
syuilo
36ab82957d New translations ja-JP.yml (Italian) 2018-10-19 11:11:36 +09:00
syuilo
de9f54386c New translations ja-JP.yml (German) 2018-10-19 11:11:32 +09:00
syuilo
7f43820765 New translations ja-JP.yml (French) 2018-10-19 11:11:28 +09:00
syuilo
955e907e7f New translations ja-JP.yml (English) 2018-10-19 11:11:24 +09:00
syuilo
4c18022e7d New translations ja-JP.yml (Chinese Simplified) 2018-10-19 11:11:20 +09:00
syuilo
509f59e46d New translations ja-JP.yml (Catalan) 2018-10-19 11:11:16 +09:00
syuilo
f14c372f5e Resolve #2719 2018-10-19 11:10:49 +09:00
137 changed files with 3695 additions and 1641 deletions

1
.gitignore vendored
View File

@@ -16,3 +16,4 @@ api-docs.json
/redis
/mongo
/elasticsearch
*.code-workspace

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

@@ -25,6 +25,14 @@ common:
application-authorization: "アプリの連携"
close: "閉じる"
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
BSoD:
fatal-error: ":( 致命的な問題が発生しました。"
update-browser-os: "お使いのブラウザ(またはOS)のバージョンを更新すると解決する可能性があります。"
error-code: "エラーコード"
browser-version: "ブラウザ バージョン"
client-version: "クライアント バージョン"
email-support: "問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。"
thanks: "Thank you for using Misskey."
got-it: "わかった"
customization-tips:
title: "カスタマイズのヒント"
@@ -115,6 +123,12 @@ common:
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
is-remote-user: "このユーザー情報はコピーです。"
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
error:
title: '問題が発生しました'
retry: 'やり直す'
reversi:
drawn: "引き分け"
my-turn: "あなたのターンです"
@@ -170,6 +184,7 @@ common:
rename: "名前を変更"
stack-left: "左に重ねる"
pop-right: "右に出す"
dev: "アプリの作成に失敗しました。再度お試しください。"
auth/views/form.vue:
share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
permission-ask: "このアプリは次の権限を要求しています:"
@@ -747,6 +762,7 @@ desktop/views/components/settings.vue:
api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "デザインと表示"
customize: "ホームをカスタマイズ"
wallpaper: "壁紙"
@@ -923,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
@@ -930,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: "凍結"
@@ -946,12 +967,17 @@ 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:
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1004,9 +1030,6 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よく話すユーザーはいません"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "フォト"
@@ -1027,6 +1050,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: "投稿と返信"
@@ -1283,6 +1310,7 @@ mobile/views/pages/settings.vue:
signout: "サインアウト"
sound: "サウンド"
enable-sounds: "サウンドを有効にする"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "フォローされています"
following: "フォロー"
@@ -1292,8 +1320,6 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "メディア"
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿"
images: "画像"
@@ -1339,3 +1365,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,11 +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: "Ein schwerwiegender Fehler ist aufgetreten :("
update-browser-os: "お使いのブラウザ(またはOS)のバージョンを更新すると解決する可能性があります。"
error-code: "Fehlercode"
browser-version: "Browserversion"
client-version: "Clientversion"
email-support: "問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。"
thanks: "Vielen Dank dass du Misskey verwendest."
got-it: "Verstanden!"
customization-tips:
title: "Anpassung-Tipps"
@@ -32,7 +40,7 @@ common:
paragraph2: "一部のウィジェットは、<strong><strong>右</strong>クリック</strong>することで表示を変更することができます。"
paragraph3: "ウィジェットを削除するには、ヘッダーの<strong>「ゴミ箱」</strong>と書かれたエリアにウィジェットをドラッグ&ドロップします。"
paragraph4: "カスタマイズを終了するには、右上の「完了」をクリックします。"
gotit: "Got it!"
gotit: "Verstanden!"
notification:
file-uploaded: "Datei hochgeladen!"
message-from: "Nachricht von {}:"
@@ -115,6 +123,12 @@ common:
reduce-motion: "Animationen der Benutzeroberfläche reduzieren"
this-setting-is-this-device-only: "Nur auf diesem Gerät"
do-not-use-in-production: 'Dies ist eine Entwicklungsversion. Nicht in einer Produktionsumgebung verwenden.'
is-remote-user: "このユーザー情報はコピーです。"
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
error:
title: '問題が発生しました'
retry: 'Erneut versuchen'
reversi:
drawn: "Unentschieden"
my-turn: "Du bist am Zug"
@@ -154,7 +168,7 @@ common:
widgets: "Widget hinzufügen:"
home: "Startseite"
local: "Lokal"
hybrid: "ソーシャル"
hybrid: "Sozial"
hashtag: "Hashtag"
global: "Global"
mentions: "Erwähnungen"
@@ -169,31 +183,32 @@ 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: "このアプリは次の権限を要求しています:"
account-read: "アカウントの情報を見る。"
account-write: "アカウントの情報を操作する。"
note-write: "投稿する。"
note-write: "Senden."
like-write: "いいねしたりいいね解除する。"
following-write: "フォローしたりフォロー解除する。"
drive-read: "ドライブを見る。"
drive-write: "ドライブを操作する。"
notification-read: "通知を見る。"
notification-write: "通知を操作する。"
notification-write: "Benachrichtigungen verwalten."
cancel: "Abbrechen"
accept: "Zugriff erlauben."
auth/views/index.vue:
loading: "Lädt"
denied: "アプリケーションの連携をキャンセルしました。"
denied: "Autorisierung der Anwendung wurde verweigert."
denied-paragraph: "このアプリがあなたのアカウントにアクセスすることはありません。"
already-authorized: "このアプリは既に連携済みです"
allowed: "アプリケーションの連携を許可しました"
already-authorized: "Diese Anwendung ist bereits autorisiert."
allowed: "Autorisierung der Anwendung wurde erlaubt."
callback-url: "アプリケーションに戻っています"
please-go-back: "アプリケーションに戻って、やっていってください。"
error: "セッションが存在しません。"
sign-in: "サインインしてください"
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:
matching:
waiting-for: "Warten auf {}"
@@ -334,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"
@@ -577,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:
@@ -747,6 +762,7 @@ desktop/views/components/settings.vue:
api-via-stream-desc: "API-Anfrage über WebSocket statt native Aktualisierungs-API (für bessere Leistung). Diese Einstellung wird im Browser gespeichert."
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "Erscheinungsbild und Anzeige"
customize: "Startseite anpassen"
wallpaper: "壁紙"
@@ -923,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
@@ -930,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: "凍結"
@@ -946,12 +967,17 @@ 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:
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1004,9 +1030,6 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よく話すユーザーはいません"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "フォト"
@@ -1027,6 +1050,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: "投稿と返信"
@@ -1283,6 +1310,7 @@ mobile/views/pages/settings.vue:
signout: "サインアウト"
sound: "サウンド"
enable-sounds: "サウンドを有効にする"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "フォローされています"
following: "フォロー"
@@ -1292,8 +1320,6 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "メディア"
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿"
images: "画像"
@@ -1339,3 +1365,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

@@ -25,6 +25,14 @@ common:
application-authorization: "Application authorizations"
close: "Close"
do-not-copy-paste: "Please do not enter or paste the code here. Account may be compromised."
BSoD:
fatal-error: "A fatal error has occurred :("
update-browser-os: "You might resolve to update the version of your browser (or OS)."
error-code: "Error code"
browser-version: "Browser version"
client-version: "Client version"
email-support: "If the problem persists, contact syuilotan@yahoo.co.jp please on the above information."
thanks: "Thank you for using Misskey."
got-it: "Got it!"
customization-tips:
title: "Customization tips"
@@ -115,6 +123,12 @@ common:
reduce-motion: "Reduce motion in UI"
this-setting-is-this-device-only: "Only for this device"
do-not-use-in-production: 'As this is for development, do not use this in production.'
is-remote-user: "This user information is copied."
is-remote-post: "This post information is a copy."
view-on-remote: "View it on remote"
error:
title: 'Something happened :('
retry: 'Retry'
reversi:
drawn: "Draw"
my-turn: "Your turn"
@@ -170,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:"
@@ -745,8 +760,9 @@ 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"
wallpaper: "Wallpaper"
@@ -763,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"
@@ -884,7 +900,7 @@ desktop/views/components/ui.header.account.vue:
admin: "Admin"
settings: "Settings"
signout: "Sign out"
dark: "Submerge in dark"
dark: "Toggle dark mode"
desktop/views/components/ui.header.nav.vue:
home: "Home"
deck: "Deck"
@@ -923,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "Drive"
users: "Users"
update: "Updates"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "Dashboard"
all-users: "All Users"
@@ -930,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"
@@ -946,12 +967,17 @@ 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: "お知らせ"
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:
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"
@@ -1004,10 +1030,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."
is-remote: "This profile belongs to a remote user. The profile that you see here may not be complete. "
view-remote: "See their complete profile"
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"
@@ -1027,6 +1050,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"
@@ -1175,7 +1202,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"
@@ -1249,7 +1276,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"
@@ -1283,6 +1310,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"
@@ -1292,8 +1320,6 @@ mobile/views/pages/user.vue:
timeline: "Timeline"
media: "Media"
is-suspended: "This account has been suspended."
is-remote: "The user is a remote user. The profile that you see here may not complete."
view-remote: "See his/her complete profile"
mobile/views/pages/user/home.vue:
recent-notes: "Recent notes"
images: "Images"
@@ -1339,3 +1365,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

@@ -25,6 +25,14 @@ common:
application-authorization: "Autorizaciones de la aplicación."
close: "Cerrar"
do-not-copy-paste: "Por favor no copies código aquí. Tu cuenta puede resultar comprometida."
BSoD:
fatal-error: ":( 致命的な問題が発生しました。"
update-browser-os: "お使いのブラウザ(またはOS)のバージョンを更新すると解決する可能性があります。"
error-code: "エラーコード"
browser-version: "ブラウザ バージョン"
client-version: "クライアント バージョン"
email-support: "問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。"
thanks: "Thank you for using Misskey."
got-it: "¡Listo!"
customization-tips:
title: "Consejos de personalización"
@@ -115,6 +123,12 @@ common:
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'Esto está en desarrollo, no usarlo para producción.'
is-remote-user: "このユーザー情報はコピーです。"
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
error:
title: '問題が発生しました'
retry: 'やり直す'
reversi:
drawn: "Empatado"
my-turn: "Mi turno"
@@ -170,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:"
@@ -747,6 +762,7 @@ desktop/views/components/settings.vue:
api-via-stream-desc: "Las peticiones de las API se realizan por conexiones WebSocket en lugar de las tradicionales (para una mejora en el rendimiento). Esta función depende del navegador."
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "Diseño y pantalla"
customize: "Personaliza la página principal"
wallpaper: "壁紙"
@@ -923,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
@@ -930,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: "凍結"
@@ -946,12 +967,17 @@ 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:
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1004,9 +1030,6 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よく話すユーザーはいません"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "フォト"
@@ -1027,6 +1050,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: "投稿と返信"
@@ -1283,6 +1310,7 @@ mobile/views/pages/settings.vue:
signout: "サインアウト"
sound: "サウンド"
enable-sounds: "サウンドを有効にする"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "フォローされています"
following: "フォロー"
@@ -1292,8 +1320,6 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "メディア"
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿"
images: "画像"
@@ -1339,3 +1365,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

@@ -25,6 +25,14 @@ common:
application-authorization: "Permissions de l'application"
close: "Fermer"
do-not-copy-paste: "Veuillez ne pas entrer ou coller le code ici. Le compte peut être compromis."
BSoD:
fatal-error: ":( 致命的な問題が発生しました。"
update-browser-os: "お使いのブラウザ(またはOS)のバージョンを更新すると解決する可能性があります。"
error-code: "エラーコード"
browser-version: "ブラウザ バージョン"
client-version: "クライアント バージョン"
email-support: "問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。"
thanks: "Thank you for using Misskey."
got-it: "J'ai compris !"
customization-tips:
title: "Conseils de personnalisation"
@@ -106,15 +114,21 @@ common:
my-token-regenerated: "Votre jeton vient dêtre généré, vous allez maintenant être déconnecté."
i-like-sushi: "Je préfère les sushis plutôt que le pudding"
show-reversi-board-labels: "Afficher les étiquettes des lignes et colonnes dans Reversi"
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
use-contrast-reversi-stones: "Icône avec contraste sur Reversi"
verified-user: "Compte vérifié"
disable-animated-mfm: "Désactiver les textes animés dans les publications"
always-show-nsfw: "常に閲覧注意のメディアを表示する"
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
always-show-nsfw: "Toujours afficher les contenus sensibles"
always-mark-nsfw: "Toujours marquer les notes ayant des attachements comme sensibles"
show-full-acct: "Afficher ladresse complète de lutilisateur"
reduce-motion: "Réduire les animations dans linterface utilisateur"
this-setting-is-this-device-only: "Uniquement sur cet appareil"
do-not-use-in-production: 'Il sagit dune version de développement. Ne pas utiliser dans un environnement de production.'
is-remote-user: "このユーザー情報はコピーです。"
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
error:
title: '問題が発生しました'
retry: 'やり直す'
reversi:
drawn: "Partie nulle"
my-turn: "Cest votre tour"
@@ -170,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 :"
@@ -417,24 +432,24 @@ common/views/components/trends.vue:
count: "{} utilisateurs·rices mentionnés·es"
empty: "Aucune tendance"
common/views/components/profile-editor.vue:
title: "プロフィール"
name: "名前"
account: "アカウント"
location: "場所"
description: "自己紹介"
birthday: "誕生日"
avatar: "アイコン"
banner: "バナー"
is-cat: "このアカウントはCatです"
is-bot: "このアカウントはBotです"
is-locked: "フォローを承認制にする"
title: "Profil"
name: "Nom"
account: "Compte"
location: "Lieu"
description: "À propos de moi"
birthday: "Date de naissance"
avatar: "Avatar"
banner: "Bannière"
is-cat: "Ce compte est un Chat"
is-bot: "Ce compte est un Bot"
is-locked: "Demandes dabonnements requièrent lapprobation"
careful-bot: "Botからのフォローだけ承認制にする"
advanced: "その他"
privacy: "プライバシー"
save: "保存"
saved: "プロフィールを保存しました"
uploading: "アップロード中"
upload-failed: "アップロードに失敗しました"
advanced: "Avancé"
privacy: "Vie privée"
save: "Mettre à jour le profil"
saved: "Profil mis à jour avec succès"
uploading: "En cours d'envoi …"
upload-failed: "Échec de l'envoi"
common/views/widgets/broadcast.vue:
fetching: "Récupération"
no-broadcasts: "Aucune annonce"
@@ -660,9 +675,9 @@ desktop/views/components/note-detail.vue:
renote: "Republier"
add-reaction: "Ajouter votre reaction"
desktop/views/components/note.vue:
reposted-by: "{}がRenote"
reply: "返信"
renote: "Renote"
reposted-by: "Partagé par {}"
reply: "Répondre"
renote: "Partager"
add-reaction: "リアクション"
detail: "詳細"
private: "この投稿は非公開です"
@@ -747,6 +762,7 @@ desktop/views/components/settings.vue:
api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "Affichage et design"
customize: "Personnaliser l'Accueil"
wallpaper: "壁紙"
@@ -923,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"
@@ -930,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"
@@ -946,12 +967,17 @@ 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:
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"
@@ -1004,10 +1030,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."
is-remote: "Cet utilisateur n'est pas un utilisateur Misskey. Certaines informations peuvent ne pas refléter ce profil dans sa totalité."
view-remote: "Consulter le profil complet"
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"
@@ -1027,6 +1050,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"
@@ -1283,6 +1310,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"
@@ -1292,8 +1320,6 @@ mobile/views/pages/user.vue:
timeline: "Fil d'actualité"
media: "Media"
is-suspended: "This account has been suspended."
is-remote: "Ceci est le profil dun utilisateur·rice distant·e. Certaines informations peuvent ne pas refléter ce profil dans sa totalité."
view-remote: "Consulter son profil complet"
mobile/views/pages/user/home.vue:
recent-notes: "Notes récentes"
images: "Images"
@@ -1339,3 +1365,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

@@ -25,6 +25,14 @@ common:
application-authorization: "アプリの連携"
close: "閉じる"
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
BSoD:
fatal-error: ":( 致命的な問題が発生しました。"
update-browser-os: "お使いのブラウザ(またはOS)のバージョンを更新すると解決する可能性があります。"
error-code: "エラーコード"
browser-version: "ブラウザ バージョン"
client-version: "クライアント バージョン"
email-support: "問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。"
thanks: "Thank you for using Misskey."
got-it: "わかった"
customization-tips:
title: "カスタマイズのヒント"
@@ -115,6 +123,12 @@ common:
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
is-remote-user: "このユーザー情報はコピーです。"
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
error:
title: '問題が発生しました'
retry: 'やり直す'
reversi:
drawn: "引き分け"
my-turn: "あなたのターンです"
@@ -170,6 +184,7 @@ common:
rename: "名前を変更"
stack-left: "左に重ねる"
pop-right: "右に出す"
dev: "アプリの作成に失敗しました。再度お試しください。"
auth/views/form.vue:
share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
permission-ask: "このアプリは次の権限を要求しています:"
@@ -747,6 +762,7 @@ desktop/views/components/settings.vue:
api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "デザインと表示"
customize: "ホームをカスタマイズ"
wallpaper: "壁紙"
@@ -923,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
@@ -930,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: "凍結"
@@ -946,12 +967,17 @@ 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:
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1004,9 +1030,6 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よく話すユーザーはいません"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "フォト"
@@ -1027,6 +1050,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: "投稿と返信"
@@ -1283,6 +1310,7 @@ mobile/views/pages/settings.vue:
signout: "サインアウト"
sound: "サウンド"
enable-sounds: "サウンドを有効にする"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "フォローされています"
following: "フォロー"
@@ -1292,8 +1320,6 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "メディア"
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿"
images: "画像"
@@ -1339,3 +1365,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

@@ -25,6 +25,15 @@ common:
application-authorization: "アプリの連携"
close: "閉じる"
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
BSoD:
fatal-error: ":( 致命的な問題が発生しました。"
update-browser-os: "お使いのブラウザ(またはOS)のバージョンを更新すると解決する可能性があります。"
error-code: "エラーコード"
browser-version: "ブラウザ バージョン"
client-version: "クライアント バージョン"
email-support: "問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。"
thanks: "Thank you for using Misskey."
got-it: "わかった"
customization-tips:
title: "カスタマイズのヒント"
@@ -124,6 +133,14 @@ common:
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
is-remote-user: "このユーザー情報はコピーです。"
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
error:
title: '問題が発生しました'
retry: 'やり直す'
reversi:
drawn: "引き分け"
my-turn: "あなたのターンです"
@@ -182,6 +199,8 @@ common:
stack-left: "左に重ねる"
pop-right: "右に出す"
dev: "アプリの作成に失敗しました。再度お試しください。"
auth/views/form.vue:
share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
permission-ask: "このアプリは次の権限を要求しています:"
@@ -836,6 +855,7 @@ desktop/views/components/settings.vue:
api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "デザインと表示"
customize: "ホームをカスタマイズ"
@@ -1043,6 +1063,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
@@ -1051,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: "ユーザーの凍結"
@@ -1072,6 +1097,12 @@ 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: "メディアビュー"
@@ -1079,6 +1110,7 @@ desktop/views/pages/deck/deck.tl-column.vue:
desktop/views/pages/deck/deck.user-column.vue:
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
@@ -1145,10 +1177,6 @@ desktop/views/pages/user/user.friends.vue:
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
@@ -1172,6 +1200,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: "投稿"
@@ -1480,6 +1512,7 @@ mobile/views/pages/settings.vue:
signout: "サインアウト"
sound: "サウンド"
enable-sounds: "サウンドを有効にする"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "フォローされています"
@@ -1490,8 +1523,6 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "メディア"
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿"
@@ -1545,3 +1576,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

@@ -25,6 +25,14 @@ common:
application-authorization: "アプリの連携"
close: "さいなら"
do-not-copy-paste: "ここにコードを入力したり張り付けたりせんといてください。アカウントが不正利用されるかも分からん。知らんけど。"
BSoD:
fatal-error: "あかん、やってもうたわ… (致命的なエラー"
update-browser-os: "ブラウザ(またはOS)のバージョン更新してくれへん?なおるかもしれんわ。"
error-code: "エラーコード"
browser-version: "ブラウザ バージョン"
client-version: "クライアント バージョン"
email-support: "それでもあかん?せやったら syuilotan@yahoo.co.jp に連絡してや!"
thanks: "Thank you おおきに。Misskey"
got-it: "ほい"
customization-tips:
title: "カスタマイズのヒント"
@@ -115,6 +123,12 @@ common:
reduce-motion: "UI、動き過ぎや、静かにしてや"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: '開発ビルドや。本番環境で使わんといて!知らんで!'
is-remote-user: "このユーザー情報はコピーです。"
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "ちゃんとした情報見せてや!"
error:
title: '問題が起こったわ'
retry: 'もっぺん'
reversi:
drawn: "おあいこ"
my-turn: "あんさんのターンや"
@@ -170,6 +184,7 @@ common:
rename: "名前を変更や!"
stack-left: "左に重ねんで!"
pop-right: "右に出すで!"
dev: "アプリの作成あかんかったわ。もっぺんやってみて。"
auth/views/form.vue:
share-access: "<i>{{ app.name }}</i>があんさんのアカウントにアクセスすんのを<b>許可</b>してもええか?"
permission-ask: "このアプリは次の権限を要求してんで:"
@@ -425,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: "お知らせはあらへんで"
@@ -661,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: "もっぺん"
@@ -746,7 +761,8 @@ 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: "ホームをカスタマイズ"
wallpaper: "壁紙"
@@ -767,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: "サウンド鳴らす"
@@ -923,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "知り合い全員や"
@@ -930,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: "凍結"
@@ -946,12 +967,17 @@ 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:
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "ここの人らだけ"
@@ -1004,10 +1030,7 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よう話すツレは居らん"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーはあかんわ。凍結されとる。"
is-remote: "このユーザーはリモートユーザーや。"
view-remote: "ちゃんとした情報を見る"
desktop/views/pages/user/user.home.vue:
last-used-at: "最後いつ来た?"
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "写真"
loading: "読み込んどります"
@@ -1027,6 +1050,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: "投稿と返信"
@@ -1283,6 +1310,7 @@ mobile/views/pages/settings.vue:
signout: "さいなら"
sound: "サウンド"
enable-sounds: "サウンド鳴らす"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "フォローされとるで"
following: "フォロー"
@@ -1292,8 +1320,6 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "メディア"
is-suspended: "このユーザーはあかんわ。凍結されとる。"
is-remote: "このユーザーは東京とかそこらへんのリモートユーザーや。"
view-remote: "ちゃんとした情報を見る"
mobile/views/pages/user/home.vue:
recent-notes: "最近儲かりまっか?"
images: "画像"
@@ -1339,3 +1365,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

@@ -25,6 +25,14 @@ common:
application-authorization: "앱의 연계"
close: "닫기"
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
BSoD:
fatal-error: ":( 致命的な問題が発生しました。"
update-browser-os: "お使いのブラウザ(またはOS)のバージョンを更新すると解決する可能性があります。"
error-code: "エラーコード"
browser-version: "ブラウザ バージョン"
client-version: "クライアント バージョン"
email-support: "問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。"
thanks: "Thank you for using Misskey."
got-it: "알았습니다"
customization-tips:
title: "사용자 정의 팁"
@@ -115,6 +123,12 @@ common:
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "이 장치만"
do-not-use-in-production: '이것은 개발 빌드입니다. 프로덕션 환경에서 사용하지 마십시오.'
is-remote-user: "このユーザー情報はコピーです。"
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
error:
title: '問題が発生しました'
retry: 'やり直す'
reversi:
drawn: "무승부"
my-turn: "당신의 차례입니다"
@@ -170,6 +184,7 @@ common:
rename: "이름 변경"
stack-left: "左に重ねる"
pop-right: "右に出す"
dev: "アプリの作成に失敗しました。再度お試しください。"
auth/views/form.vue:
share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
permission-ask: "このアプリは次の権限を要求しています:"
@@ -747,6 +762,7 @@ desktop/views/components/settings.vue:
api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "デザインと表示"
customize: "ホームをカスタマイズ"
wallpaper: "壁紙"
@@ -923,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
@@ -930,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: "凍結"
@@ -946,12 +967,17 @@ 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:
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1004,9 +1030,6 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よく話すユーザーはいません"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "フォト"
@@ -1027,6 +1050,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: "投稿と返信"
@@ -1283,6 +1310,7 @@ mobile/views/pages/settings.vue:
signout: "サインアウト"
sound: "サウンド"
enable-sounds: "サウンドを有効にする"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "フォローされています"
following: "フォロー"
@@ -1292,8 +1320,6 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "メディア"
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿"
images: "画像"
@@ -1339,3 +1365,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

@@ -25,6 +25,14 @@ common:
application-authorization: "アプリの連携"
close: "閉じる"
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
BSoD:
fatal-error: ":( 致命的な問題が発生しました。"
update-browser-os: "お使いのブラウザ(またはOS)のバージョンを更新すると解決する可能性があります。"
error-code: "エラーコード"
browser-version: "ブラウザ バージョン"
client-version: "クライアント バージョン"
email-support: "問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。"
thanks: "Thank you for using Misskey."
got-it: "わかった"
customization-tips:
title: "カスタマイズのヒント"
@@ -115,6 +123,12 @@ common:
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
is-remote-user: "このユーザー情報はコピーです。"
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
error:
title: '問題が発生しました'
retry: 'やり直す'
reversi:
drawn: "引き分け"
my-turn: "あなたのターンです"
@@ -170,6 +184,7 @@ common:
rename: "名前を変更"
stack-left: "左に重ねる"
pop-right: "右に出す"
dev: "アプリの作成に失敗しました。再度お試しください。"
auth/views/form.vue:
share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
permission-ask: "このアプリは次の権限を要求しています:"
@@ -747,6 +762,7 @@ desktop/views/components/settings.vue:
api-via-stream-desc: "API-verzoek wordt uitgevoerd via de WebSocket-verbinding i.p.v. de ingebouwde ophaal-API (voor verbeterde prestaties). Deze instelling wordt opgeslagen in je browser."
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "Ontwerp en weergave"
customize: "Startpagina aanpassen"
wallpaper: "壁紙"
@@ -923,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
@@ -930,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: "凍結"
@@ -946,12 +967,17 @@ 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:
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1004,10 +1030,7 @@ desktop/views/pages/user/user.friends.vue:
no-users: "Geen gebruikers"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
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"
@@ -1027,6 +1050,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"
@@ -1283,6 +1310,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"
@@ -1292,8 +1320,6 @@ mobile/views/pages/user.vue:
timeline: "Tijdlijn"
media: "Media"
is-suspended: "Dit account is geschorst."
is-remote: "Deze gebruiker is een externe gebruiker; de informatie is daarom niet volledig. "
view-remote: "Volledige informatie bekijken"
mobile/views/pages/user/home.vue:
recent-notes: "Recente notities"
images: "Afbeeldingen"
@@ -1302,7 +1328,7 @@ mobile/views/pages/user/home.vue:
domains: "Domeinnamen"
frequently-replied-users: "Frequent gesproken gebruikers"
followers-you-know: "Volgers die je kent"
last-used-at: "Laatst actief:"
last-used-at: "Laatst actief"
mobile/views/pages/user/home.followers-you-know.vue:
loading: "Bezig met laden"
no-users: "Geen gebruikers"
@@ -1339,3 +1365,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

@@ -25,6 +25,14 @@ common:
application-authorization: "アプリの連携"
close: "Lukk"
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
BSoD:
fatal-error: ":( 致命的な問題が発生しました。"
update-browser-os: "お使いのブラウザ(またはOS)のバージョンを更新すると解決する可能性があります。"
error-code: "エラーコード"
browser-version: "ブラウザ バージョン"
client-version: "クライアント バージョン"
email-support: "問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。"
thanks: "Thank you for using Misskey."
got-it: "Skjønner!"
customization-tips:
title: "カスタマイズのヒント"
@@ -115,6 +123,12 @@ common:
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
is-remote-user: "このユーザー情報はコピーです。"
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
error:
title: '問題が発生しました'
retry: 'やり直す'
reversi:
drawn: "引き分け"
my-turn: "あなたのターンです"
@@ -170,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: "このアプリは次の権限を要求しています:"
@@ -747,6 +762,7 @@ desktop/views/components/settings.vue:
api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "デザインと表示"
customize: "ホームをカスタマイズ"
wallpaper: "壁紙"
@@ -923,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: "全てのユーザー"
@@ -930,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"
@@ -946,12 +967,17 @@ 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:
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1004,9 +1030,6 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よく話すユーザーはいません"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "Bilder"
@@ -1027,6 +1050,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"
@@ -1283,6 +1310,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"
@@ -1292,8 +1320,6 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "Media"
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
mobile/views/pages/user/home.vue:
recent-notes: "Nylige innlegg"
images: "Bilder"
@@ -1339,3 +1365,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

@@ -25,6 +25,14 @@ common:
application-authorization: "アプリの連携"
close: "Zamknij"
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
BSoD:
fatal-error: ":( 致命的な問題が発生しました。"
update-browser-os: "お使いのブラウザ(またはOS)のバージョンを更新すると解決する可能性があります。"
error-code: "エラーコード"
browser-version: "ブラウザ バージョン"
client-version: "クライアント バージョン"
email-support: "問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。"
thanks: "Thank you for using Misskey."
got-it: "Rozumiem!"
customization-tips:
title: "Wskazówki o dostosowywaniu"
@@ -115,6 +123,12 @@ common:
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
is-remote-user: "このユーザー情報はコピーです。"
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
error:
title: '問題が発生しました'
retry: 'やり直す'
reversi:
drawn: "Remis"
my-turn: "Twoja kolej"
@@ -170,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ń:"
@@ -747,6 +762,7 @@ desktop/views/components/settings.vue:
api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "Wygląd i wyświetlanie"
customize: "Dostosuj stronę główną"
wallpaper: "壁紙"
@@ -923,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
@@ -930,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: "凍結"
@@ -946,12 +967,17 @@ 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:
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1004,10 +1030,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."
is-remote: "To jest użytkownik zdalnej instancji, informacje mogą nie być w pełni dokładne."
view-remote: "Wyświetl dokładne informacje"
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"
@@ -1027,6 +1050,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"
@@ -1283,6 +1310,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"
@@ -1292,8 +1320,6 @@ mobile/views/pages/user.vue:
timeline: "Oś czasu"
media: "Multimedia"
is-suspended: "To konto zostało zablokowane"
is-remote: "To jest użytkownik zdalnej instancji, informacje mogą nie być w pełni dokładne."
view-remote: "Wyświetl dokładne informacje"
mobile/views/pages/user/home.vue:
recent-notes: "Ostatnie wpisy"
images: "Zdjęcia"
@@ -1339,3 +1365,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

@@ -25,6 +25,14 @@ common:
application-authorization: "Aplicativos autorizados"
close: "Fechar"
do-not-copy-paste: "Por favor, não digite ou copie o código aqui. A conta pode ser comprometida."
BSoD:
fatal-error: ":( 致命的な問題が発生しました。"
update-browser-os: "お使いのブラウザ(またはOS)のバージョンを更新すると解決する可能性があります。"
error-code: "エラーコード"
browser-version: "ブラウザ バージョン"
client-version: "クライアント バージョン"
email-support: "問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。"
thanks: "Thank you for using Misskey."
got-it: "Entendi!"
customization-tips:
title: "Dicas de personalização"
@@ -115,6 +123,12 @@ common:
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
is-remote-user: "このユーザー情報はコピーです。"
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
error:
title: '問題が発生しました'
retry: 'やり直す'
reversi:
drawn: "Empatado"
my-turn: "Seu turno"
@@ -170,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:"
@@ -747,6 +762,7 @@ desktop/views/components/settings.vue:
api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "デザインと表示"
customize: "ホームをカスタマイズ"
wallpaper: "壁紙"
@@ -923,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"
@@ -930,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: "凍結"
@@ -946,12 +967,17 @@ 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:
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1004,9 +1030,6 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よく話すユーザーはいません"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "フォト"
@@ -1027,6 +1050,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: "投稿と返信"
@@ -1283,6 +1310,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"
@@ -1292,8 +1320,6 @@ mobile/views/pages/user.vue:
timeline: "Linha do tempo"
media: "Mídia"
is-suspended: "Esta conta foi suspensa"
is-remote: "Este é uma usuário remoto. O perfil que vê aqui pode não estar completo."
view-remote: "Ver o perfil completo."
mobile/views/pages/user/home.vue:
recent-notes: "Notas recentes"
images: "Imagens"
@@ -1302,7 +1328,7 @@ mobile/views/pages/user/home.vue:
domains: "Domínios"
frequently-replied-users: "Perguntas frequentes"
followers-you-know: "Seguidores que você conhece"
last-used-at: "Ativo pela última vez:"
last-used-at: "Ativo pela última vez"
mobile/views/pages/user/home.followers-you-know.vue:
loading: "Carregando"
no-users: "知り合いのユーザーはいません"
@@ -1339,3 +1365,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

@@ -25,6 +25,14 @@ common:
application-authorization: "アプリの連携"
close: "閉じる"
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
BSoD:
fatal-error: ":( 致命的な問題が発生しました。"
update-browser-os: "お使いのブラウザ(またはOS)のバージョンを更新すると解決する可能性があります。"
error-code: "エラーコード"
browser-version: "ブラウザ バージョン"
client-version: "クライアント バージョン"
email-support: "問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。"
thanks: "Thank you for using Misskey."
got-it: "わかった"
customization-tips:
title: "カスタマイズのヒント"
@@ -115,6 +123,12 @@ common:
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
is-remote-user: "このユーザー情報はコピーです。"
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
error:
title: '問題が発生しました'
retry: 'やり直す'
reversi:
drawn: "引き分け"
my-turn: "あなたのターンです"
@@ -170,6 +184,7 @@ common:
rename: "名前を変更"
stack-left: "左に重ねる"
pop-right: "右に出す"
dev: "アプリの作成に失敗しました。再度お試しください。"
auth/views/form.vue:
share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
permission-ask: "このアプリは次の権限を要求しています:"
@@ -747,6 +762,7 @@ desktop/views/components/settings.vue:
api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "デザインと表示"
customize: "ホームをカスタマイズ"
wallpaper: "壁紙"
@@ -923,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
@@ -930,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: "凍結"
@@ -946,12 +967,17 @@ 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:
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1004,9 +1030,6 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よく話すユーザーはいません"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "フォト"
@@ -1027,6 +1050,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: "投稿と返信"
@@ -1283,6 +1310,7 @@ mobile/views/pages/settings.vue:
signout: "サインアウト"
sound: "サウンド"
enable-sounds: "サウンドを有効にする"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "フォローされています"
following: "フォロー"
@@ -1292,8 +1320,6 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "メディア"
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿"
images: "画像"
@@ -1339,3 +1365,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

@@ -25,6 +25,14 @@ common:
application-authorization: "アプリの連携"
close: "閉じる"
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
BSoD:
fatal-error: ":( 致命的な問題が発生しました。"
update-browser-os: "お使いのブラウザ(またはOS)のバージョンを更新すると解決する可能性があります。"
error-code: "エラーコード"
browser-version: "ブラウザ バージョン"
client-version: "クライアント バージョン"
email-support: "問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。"
thanks: "Thank you for using Misskey."
got-it: "わかった"
customization-tips:
title: "カスタマイズのヒント"
@@ -115,6 +123,12 @@ common:
reduce-motion: "UIの動きを減らす"
this-setting-is-this-device-only: "このデバイスのみ"
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
is-remote-user: "このユーザー情報はコピーです。"
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
error:
title: '問題が発生しました'
retry: 'やり直す'
reversi:
drawn: "引き分け"
my-turn: "あなたのターンです"
@@ -170,6 +184,7 @@ common:
rename: "名前を変更"
stack-left: "左に重ねる"
pop-right: "右に出す"
dev: "アプリの作成に失敗しました。再度お試しください。"
auth/views/form.vue:
share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
permission-ask: "このアプリは次の権限を要求しています:"
@@ -747,6 +762,7 @@ desktop/views/components/settings.vue:
api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "デザインと表示"
customize: "ホームをカスタマイズ"
wallpaper: "壁紙"
@@ -923,6 +939,8 @@ desktop/views/pages/admin/admin.vue:
drive: "ドライブ"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
@@ -930,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: "凍結"
@@ -946,12 +967,17 @@ 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:
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
@@ -1004,9 +1030,6 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よく話すユーザーはいません"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
desktop/views/pages/user/user.home.vue:
last-used-at: "最終アクセス"
desktop/views/pages/user/user.photos.vue:
title: "フォト"
@@ -1027,6 +1050,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: "投稿と返信"
@@ -1283,6 +1310,7 @@ mobile/views/pages/settings.vue:
signout: "サインアウト"
sound: "サウンド"
enable-sounds: "サウンドを有効にする"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mobile/views/pages/user.vue:
follows-you: "フォローされています"
following: "フォロー"
@@ -1292,8 +1320,6 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "メディア"
is-suspended: "このユーザーは凍結されています。"
is-remote: "このユーザーはリモートユーザーです。"
view-remote: "正確な情報を見る"
mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿"
images: "画像"
@@ -1339,3 +1365,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.23.1",
"clientVersion": "1.0.10732",
"version": "10.30.0",
"clientVersion": "1.0.11019",
"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",
@@ -179,7 +180,7 @@
"qrcode": "1.3.0",
"ratelimiter": "3.2.0",
"recaptcha-promise": "0.1.3",
"reconnecting-websocket": "4.1.9",
"reconnecting-websocket": "4.1.10",
"redis": "2.8.0",
"request": "2.88.0",
"request-promise-native": "1.0.5",
@@ -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();

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

@@ -0,0 +1,37 @@
import autobind from 'autobind-decorator';
import * as mongo from 'mongodb';
import Chart, { Partial } from './';
/**
* ハッシュタグに関するチャート
*/
type HashtagLog = {
/**
* 投稿された数
*/
count: number;
};
class HashtagChart extends Chart<HashtagLog> {
constructor() {
super('hashtag', true);
}
@autobind
protected async getTemplate(init: boolean, latest?: HashtagLog): Promise<HashtagLog> {
return {
count: 0
};
}
@autobind
public async update(hashtag: string, userId: mongo.ObjectId) {
const inc: Partial<HashtagLog> = {
count: 1
};
await this.incIfUnique(inc, 'users', userId.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

@@ -46,6 +46,16 @@ const getKeyMap = keymap => Object.entries(keymap).map(([patterns, callback]): a
const ignoreElemens = ['input', 'textarea'];
function match(e: KeyboardEvent, patterns: action['patterns']): boolean {
const key = e.code.toLowerCase();
return patterns.some(pattern => pattern.which.includes(key) &&
pattern.ctrl == e.ctrlKey &&
pattern.shift == e.shiftKey &&
pattern.alt == e.altKey &&
e.metaKey == false
);
}
export default {
install(Vue) {
Vue.directive('hotkey', {
@@ -55,37 +65,27 @@ export default {
const actions = getKeyMap(binding.value);
// flatten
const reservedKeys = concat(concat(actions.map(a => a.patterns.map(p => p.which))));
const reservedKeys = concat(actions.map(a => a.patterns));
el.dataset.reservedKeys = reservedKeys.map(key => `'${key}'`).join(' ');
el._misskey_reservedKeys = reservedKeys;
el._keyHandler = (e: KeyboardEvent) => {
const key = e.code.toLowerCase();
const targetReservedKeys = document.activeElement ? ((document.activeElement as any).dataset || {}).reservedKeys || '' : '';
const targetReservedKeys = document.activeElement ? ((document.activeElement as any)._misskey_reservedKeys || []) : [];
if (document.activeElement && ignoreElemens.some(el => document.activeElement.matches(el))) return;
for (const action of actions) {
if (el._hotkey_global && targetReservedKeys.includes(`'${key}'`)) break;
const matched = action.patterns.some(pattern => {
const matched = pattern.which.includes(key) &&
pattern.ctrl == e.ctrlKey &&
pattern.shift == e.shiftKey &&
pattern.alt == e.altKey &&
e.metaKey == false;
if (matched) {
e.preventDefault();
e.stopPropagation();
action.callback(e);
return true;
} else {
return false;
}
});
const matched = match(e, action.patterns);
if (matched) {
if (el._hotkey_global) {
if (match(e, targetReservedKeys)) {
return;
}
}
e.preventDefault();
e.stopPropagation();
action.callback(e);
break;
}
}

View File

@@ -29,12 +29,12 @@ export default (opts: Opts = {}) => ({
computed: {
keymap(): any {
return {
'r|left': () => this.reply(true),
'r': () => this.reply(true),
'e|a|plus': () => this.react(true),
'q|right': () => this.renote(true),
'q': () => this.renote(true),
'f|b': this.favorite,
'delete|ctrl+d': this.del,
'ctrl+q|ctrl+right': this.renoteDirectly,
'ctrl+q': this.renoteDirectly,
'up|k|shift+tab': this.focusBefore,
'down|j|tab': this.focusAfter,
'esc': this.blur,

View File

@@ -114,10 +114,9 @@ export default Vue.component('misskey-flavored-markdown', {
}
case 'mention': {
return (createElement as any)('a', {
return (createElement as any)('router-link', {
attrs: {
href: `${url}/${token.canonical}`,
target: '_blank',
to: `/${token.canonical}`,
dataIsMe: (this as any).i && getAcct((this as any).i) == getAcct(token),
style: 'color:var(--mfmMention);'
},
@@ -129,10 +128,9 @@ export default Vue.component('misskey-flavored-markdown', {
}
case 'hashtag': {
return [createElement('a', {
return [createElement('router-link', {
attrs: {
href: `${url}/tags/${encodeURIComponent(token.hashtag)}`,
target: '_blank',
to: `/tags/${encodeURIComponent(token.hashtag)}`,
style: 'color:var(--mfmHashtag);'
}
}, token.content)];

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

@@ -21,6 +21,7 @@ import updateAvatar from './api/update-avatar';
import updateBanner from './api/update-banner';
import MkIndex from './views/pages/index.vue';
import MkHome from './views/pages/home.vue';
import MkDeck from './views/pages/deck/deck.vue';
import MkAdmin from './views/pages/admin/admin.vue';
import MkStats from './views/pages/stats/stats.vue';
@@ -54,6 +55,7 @@ init(async (launch) => {
mode: 'history',
routes: [
{ path: '/', name: 'index', component: MkIndex },
{ path: '/home', name: 'home', component: MkHome },
{ path: '/deck', name: 'deck', component: MkDeck },
{ path: '/admin', name: 'admin', component: MkAdmin },
{ path: '/stats', name: 'stats', component: MkStats },
@@ -64,7 +66,7 @@ init(async (launch) => {
{ path: '/i/drive/folder/:folder', component: MkDrive },
{ path: '/selectdrive', component: MkSelectDrive },
{ path: '/search', component: MkSearch },
{ path: '/tags/:tag', component: MkTag },
{ path: '/tags/:tag', name: 'tag', component: MkTag },
{ path: '/share', component: MkShare },
{ path: '/reversi/:game?', component: MkReversi },
{ path: '/@:user', name: 'user', component: MkUser },

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>
@@ -35,7 +35,7 @@
<span v-if="appearNote.isHidden" style="opacity: 0.5">%i18n:@private%</span>
<a class="reply" v-if="appearNote.reply">%fa:reply%</a>
<misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :class="$style.text"/>
<a class="rp" v-if="appearNote.renote">RP:</a>
<a class="rp" v-if="appearNote.renote">RN:</a>
</div>
<div class="files" v-if="appearNote.files.length > 0">
<mk-media-list :media-list="appearNote.files"/>

View File

@@ -4,9 +4,9 @@
<slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot>
<div v-if="!fetching && requestInitPromise != null">
<p>%i18n:@error%</p>
<button @click="resolveInitPromise">%i18n:@retry%</button>
<div v-if="!fetching && requestInitPromise != null" class="error">
<p>%fa:exclamation-triangle% %i18n:common.error.title%</p>
<ui-button @click="resolveInitPromise">%i18n:common.error.retry%</ui-button>
</div>
<div class="placeholder" v-if="fetching">
@@ -38,7 +38,6 @@
<script lang="ts">
import Vue from 'vue';
import * as config from '../../../config';
import getNoteSummary from '../../../../../misc/get-note-summary';
import XNote from './note.vue';
@@ -61,7 +60,6 @@ export default Vue.extend({
requestInitPromise: null as () => Promise<any[]>,
notes: [],
queue: [],
unreadCount: 0,
fetching: true,
moreFetching: false
};
@@ -80,12 +78,10 @@ export default Vue.extend({
},
mounted() {
document.addEventListener('visibilitychange', this.onVisibilitychange, false);
window.addEventListener('scroll', this.onScroll, { passive: true });
},
beforeDestroy() {
document.removeEventListener('visibilitychange', this.onVisibilitychange);
window.removeEventListener('scroll', this.onScroll);
},
@@ -147,10 +143,9 @@ export default Vue.extend({
}
//#endregion
// 投稿が自分のものではないかつ、タブが非表示またはスクロール位置が最上部ではないならタイトルで通知
if ((document.hidden || !this.isScrollTop()) && note.userId !== this.$store.state.i.id) {
this.unreadCount++;
document.title = `(${this.unreadCount}) ${getNoteSummary(note)}`;
// タブが非表示またはスクロール位置が最上部ではないならタイトルで通知
if (document.hidden || !this.isScrollTop()) {
this.$store.commit('pushBehindNote', note);
}
if (this.isScrollTop()) {
@@ -195,21 +190,9 @@ export default Vue.extend({
this.moreFetching = false;
},
clearNotification() {
this.unreadCount = 0;
document.title = (this as any).os.instanceName;
},
onVisibilitychange() {
if (!document.hidden) {
this.clearNotification();
}
},
onScroll() {
if (this.isScrollTop()) {
this.releaseQueue();
this.clearNotification();
}
if (this.$store.state.settings.fetchOnScroll !== false) {
@@ -232,6 +215,16 @@ export default Vue.extend({
> *
transition transform .3s ease, opacity .3s ease
> .error
max-width 300px
margin 0 auto
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

@@ -97,6 +97,9 @@
<ui-radio v-model="navbar" value="left">%i18n:@navbar-position-left%</ui-radio>
<ui-radio v-model="navbar" value="right">%i18n:@navbar-position-right%</ui-radio>
</section>
<section>
<ui-switch v-model="deckDefault">%i18n:@deck-default%</ui-switch>
</section>
<section>
<ui-switch v-model="darkmode">%i18n:@dark-mode%</ui-switch>
<ui-switch v-model="useShadow">%i18n:@use-shadow%</ui-switch>
@@ -366,6 +369,11 @@ export default Vue.extend({
set(value) { this.$store.commit('device/set', { key: 'deckColumnAlign', value }); }
},
deckDefault: {
get() { return this.$store.state.device.deckDefault; },
set(value) { this.$store.commit('device/set', { key: 'deckDefault', value }); }
},
enableSounds: {
get() { return this.$store.state.device.enableSounds; },
set(value) { this.$store.commit('device/set', { key: 'enableSounds', value }); }

View File

@@ -5,7 +5,7 @@
<span v-if="note.deletedAt" style="opacity: 0.5">%i18n:@deleted%</span>
<a class="reply" v-if="note.replyId">%fa:reply%</a>
<misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i"/>
<a class="rp" v-if="note.renoteId" :href="`/notes/${note.renoteId}`">RP: ...</a>
<a class="rp" v-if="note.renoteId" :href="`/notes/${note.renoteId}`">RN: ...</a>
</div>
<details v-if="note.files.length > 0">
<summary>({{ '%i18n:@media-count%'.replace('{}', note.files.length) }})</summary>

View File

@@ -2,18 +2,22 @@
<div class="nav">
<ul>
<template v-if="$store.getters.isSignedIn">
<li class="home" :class="{ active: $route.name == 'index' }" @click="goToTop">
<router-link to="/">
%fa:home%
<p>%i18n:@home%</p>
</router-link>
</li>
<li class="deck" :class="{ active: $route.name == 'deck' }" @click="goToTop">
<router-link to="/deck">
%fa:columns%
<p>%i18n:@deck%</p>
</router-link>
</li>
<template v-if="$store.state.device.deckDefault">
<li class="deck" :class="{ active: $route.name == 'deck' || $route.name == 'index' }" @click="goToTop">
<router-link to="/">%fa:columns%<p>%i18n:@deck%</p></router-link>
</li>
<li class="home" :class="{ active: $route.name == 'home' }" @click="goToTop">
<router-link to="/home">%fa:home%<p>%i18n:@home%</p></router-link>
</li>
</template>
<template v-else>
<li class="home" :class="{ active: $route.name == 'home' || $route.name == 'index' }" @click="goToTop">
<router-link to="/">%fa:home%<p>%i18n:@home%</p></router-link>
</li>
<li class="deck" :class="{ active: $route.name == 'deck' }" @click="goToTop">
<router-link to="/deck">%fa:columns%<p>%i18n:@deck%</p></router-link>
</li>
</template>
<li class="messaging">
<a @click="messaging">
%fa:comments%

View File

@@ -6,12 +6,22 @@
</div>
<div class="nav" v-if="$store.getters.isSignedIn">
<div class="home" :class="{ active: $route.name == 'index' }" @click="goToTop">
<router-link to="/">%fa:home%</router-link>
</div>
<div class="deck" :class="{ active: $route.name == 'deck' }" @click="goToTop">
<router-link to="/deck">%fa:columns%</router-link>
</div>
<template v-if="$store.state.device.deckDefault">
<div class="deck" :class="{ active: $route.name == 'deck' || $route.name == 'index' }" @click="goToTop">
<router-link to="/">%fa:columns%</router-link>
</div>
<div class="home" :class="{ active: $route.name == 'home' }" @click="goToTop">
<router-link to="/home">%fa:home%</router-link>
</div>
</template>
<template v-else>
<div class="home" :class="{ active: $route.name == 'home' || $route.name == 'index' }" @click="goToTop">
<router-link to="/">%fa:home%</router-link>
</div>
<div class="deck" :class="{ active: $route.name == 'deck' }" @click="goToTop">
<router-link to="/deck">%fa:columns%</router-link>
</div>
</template>
<div class="messaging">
<a @click="messaging">%fa:comments%<template v-if="hasUnreadMessagingMessage">%fa:circle%</template></a>
</div>

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"/>
<x-tl-column v-else-if="column.type == 'local'" :column="column" :is-stacked="isStacked"/>
<x-tl-column v-else-if="column.type == 'hybrid'" :column="column" :is-stacked="isStacked"/>
<x-tl-column v-else-if="column.type == 'global'" :column="column" :is-stacked="isStacked"/>
<x-tl-column v-else-if="column.type == 'list'" :column="column" :is-stacked="isStacked"/>
<x-tl-column v-else-if="column.type == 'hashtag'" :column="column" :is-stacked="isStacked"/>
<x-mentions-column v-else-if="column.type == 'mentions'" :column="column" :is-stacked="isStacked"/>
<x-direct-column v-else-if="column.type == 'direct'" :column="column" :is-stacked="isStacked"/>
<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">
@@ -38,6 +38,12 @@ export default Vue.extend({
required: false,
default: false
}
},
methods: {
focus() {
this.$children[0].focus();
}
}
});
</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

@@ -34,5 +34,11 @@ export default Vue.extend({
return '%i18n:common.deck.direct%';
}
},
methods: {
focus() {
this.$refs.tl.focus();
}
}
});
</script>

View File

@@ -58,6 +58,7 @@ export default Vue.extend({
}, rej);
}));
},
more() {
this.moreFetching = true;
@@ -82,11 +83,16 @@ export default Vue.extend({
return promise;
},
onNote(note) {
// Prepend a note
if (note.visibility == 'specified') {
(this.$refs.timeline as any).prepend(note);
}
},
focus() {
this.$refs.timeline.focus();
}
}
});

View File

@@ -0,0 +1,103 @@
<template>
<x-column>
<span slot="header">
%fa:hashtag%<span>{{ tag }}</span>
</span>
<div class="xroyrflcmhhtmlwmyiwpfqiirqokfueb">
<div ref="chart" class="chart"></div>
<x-hashtag-tl :tag-tl="tagTl" class="tl"/>
</div>
</x-column>
</template>
<script lang="ts">
import Vue from 'vue';
import XColumn from './deck.column.vue';
import XHashtagTl from './deck.hashtag-tl.vue';
import * as ApexCharts from 'apexcharts';
import * as tinycolor from 'tinycolor2';
export default Vue.extend({
components: {
XColumn,
XHashtagTl
},
props: {
tag: {
type: String,
required: true
}
},
computed: {
tagTl(): any {
return {
query: [[this.tag]]
};
}
},
mounted() {
(this as any).api('charts/hashtag', {
tag: this.tag,
span: 'hour',
limit: 24
}).then(stats => {
const data = [];
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);
data.push([x, stats.count[i]]);
}
const color = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--primary'));
const chart = new ApexCharts(this.$refs.chart, {
chart: {
type: 'area',
height: 60,
sparkline: {
enabled: true
},
},
stroke: {
curve: 'straight',
width: 2
},
series: [{
name: 'count',
data: data
}],
xaxis: {
type: 'datetime',
},
colors: [`#${color.clone().toHex()}`]
});
chart.render();
});
}
});
</script>
<style lang="stylus" scoped>
.xroyrflcmhhtmlwmyiwpfqiirqokfueb
background var(--deckColumnBg)
> .chart
padding 16px
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"/>
<x-notes ref="timeline" :more="existMore ? more : null" :media-view="mediaView"/>
</template>
<script lang="ts">
@@ -47,14 +47,16 @@ export default Vue.extend({
mounted() {
if (this.connection) this.connection.close();
this.connection = (this as any).os.stream.connectToChannel('hashtag', this.tagTl.query);
this.connection = (this as any).os.stream.connectToChannel('hashtag', {
q: this.tagTl.query
});
this.connection.on('note', this.onNote);
this.fetch();
},
beforeDestroy() {
this.connection.close();
this.connection.dispose();
},
methods: {
@@ -80,6 +82,7 @@ export default Vue.extend({
}, rej);
}));
},
more() {
this.moreFetching = true;
@@ -105,11 +108,16 @@ export default Vue.extend({
return promise;
},
onNote(note) {
if (this.mediaOnly && note.files.length == 0) return;
// Prepend a note
(this.$refs.timeline as any).prepend(note);
},
focus() {
this.$refs.timeline.focus();
}
}
});

View File

@@ -84,6 +84,7 @@ export default Vue.extend({
}, rej);
}));
},
more() {
this.moreFetching = true;
@@ -109,17 +110,24 @@ export default Vue.extend({
return promise;
},
onNote(note) {
if (this.mediaOnly && note.files.length == 0) return;
// Prepend a note
(this.$refs.timeline as any).prepend(note);
},
onUserAdded() {
this.fetch();
},
onUserRemoved() {
this.fetch();
},
focus() {
this.$refs.timeline.focus();
}
}
});

View File

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

View File

@@ -57,6 +57,7 @@ export default Vue.extend({
}, rej);
}));
},
more() {
this.moreFetching = true;
@@ -80,9 +81,14 @@ export default Vue.extend({
return promise;
},
onNote(note) {
// Prepend a note
(this.$refs.timeline as any).prepend(note);
},
focus() {
this.$refs.timeline.focus();
}
}
});

View File

@@ -5,7 +5,12 @@
</span>
<div class="rvtscbadixhhbsczoorqoaygovdeecsx" v-if="note">
<div class="is-remote" v-if="note.user.host != null">%fa:exclamation-triangle% %i18n:@is-remote%<a :href="note.url || note.uri" target="_blank">%i18n:@view-remote%</a></div>
<div class="is-remote" v-if="note.user.host != null">
<details>
<summary>%fa:exclamation-triangle% %i18n:common.is-remote-post%</summary>
<a :href="note.url || note.uri" target="_blank">%i18n:common.view-on-remote%</a>
</details>
</div>
<x-note :note="note" :detail="true" :mini="true"/>
</div>
</x-column>

View File

@@ -8,16 +8,21 @@
</template>
</div>
<div v-if="!fetching && requestInitPromise != null">
<p>%i18n:@error%</p>
<button @click="resolveInitPromise">%i18n:@retry%</button>
<div v-if="!fetching && requestInitPromise != null" class="error">
<p>%fa:exclamation-triangle% %i18n:common.error.title%</p>
<ui-button @click="resolveInitPromise">%i18n:common.error.retry%</ui-button>
</div>
<!-- トランジションを有効にするとなぜかメモリリークする -->
<!--<transition-group name="mk-notes" class="transition">-->
<div class="notes">
<!--<transition-group name="mk-notes" class="transition" ref="notes">-->
<div class="notes" ref="notes">
<template v-for="(note, i) in _notes">
<x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)" :media-view="mediaView" :mini="true"/>
<x-note
:note="note"
:key="note.id"
@update:note="onNoteUpdated(i, $event)"
:media-view="mediaView"
: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>
@@ -102,7 +107,7 @@ export default Vue.extend({
methods: {
focus() {
(this.$el as any).children[0].focus();
(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();
},
onNoteUpdated(i, note) {
@@ -154,6 +159,11 @@ export default Vue.extend({
}
//#endregion
// タブが非表示またはスクロール位置が最上部ではないならタイトルで通知
if (document.hidden || !this.isScrollTop()) {
this.$store.commit('pushBehindNote', note);
}
if (this.isScrollTop()) {
// Prepend the note
this.notes.unshift(note);
@@ -211,6 +221,13 @@ export default Vue.extend({
> *
transition transform .3s ease, opacity .3s ease
> .error
max-width 300px
margin 0 auto
padding 16px
text-align center
color var(--text)
> .placeholder
padding 16px
opacity 0.3

View File

@@ -14,9 +14,25 @@
<ui-switch v-model="column.isMediaOnly" @change="onChangeSettings">%i18n:@is-media-only%</ui-switch>
<ui-switch v-model="column.isMediaView" @change="onChangeSettings">%i18n:@is-media-view%</ui-switch>
</div>
<x-list-tl v-if="column.type == 'list'" :list="column.list" :media-only="column.isMediaOnly" :media-view="column.isMediaView"/>
<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"/>
<x-tl v-else :src="column.type" :media-only="column.isMediaOnly" :media-view="column.isMediaView"/>
<x-list-tl v-if="column.type == 'list'"
:list="column.list"
:media-only="column.isMediaOnly"
:media-view="column.isMediaView"
ref="tl"
/>
<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"
/>
<x-tl v-else
:src="column.type"
:media-only="column.isMediaOnly"
:media-view="column.isMediaView"
ref="tl"
/>
</x-column>
</template>
@@ -77,6 +93,10 @@ export default Vue.extend({
methods: {
onChangeSettings(v) {
this.$store.dispatch('settings/saveDeck');
},
focus() {
this.$refs.tl.focus();
}
}
});

View File

@@ -5,9 +5,15 @@
</span>
<div class="zubukjlciycdsyynicqrnlsmdwmymzqu" v-if="user">
<div class="is-remote" v-if="user.host != null">%fa:exclamation-triangle% %i18n:@is-remote%<a :href="user.url || user.uri" target="_blank">%i18n:@view-remote%</a></div>
<div class="is-remote" v-if="user.host != null">
<details>
<summary>%fa:exclamation-triangle% %i18n:common.is-remote-user%</summary>
<a :href="user.url || user.uri" target="_blank">%i18n:common.view-on-remote%</a>
</details>
</div>
<header :style="bannerStyle">
<div>
<button class="menu" @click="menu" ref="menu">%fa:ellipsis-h%</button>
<mk-follow-button v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" class="follow"/>
<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
<span class="name">{{ user | userName }}</span>
@@ -26,7 +32,15 @@
</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"></router-link>
<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 class="activity">
<div ref="chart"></div>
</div>
<div class="tl">
<x-notes ref="timeline" :more="existMore ? fetchMoreNotes : null"/>
@@ -41,7 +55,11 @@ import parseAcct from '../../../../../../misc/acct/parse';
import XColumn from './deck.column.vue';
import XNotes from './deck.notes.vue';
import XNote from '../../components/note.vue';
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;
@@ -111,7 +129,68 @@ 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, 6);
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: 60,
sparkline: {
enabled: true
},
},
tooltip: {
shared: true,
intersect: false
},
series: [{
name: 'Normal',
data: normal
}, {
name: 'Reply',
data: reply
}, {
name: 'Renote',
data: renote
}],
xaxis: {
type: 'datetime'
}
});
chart.render();
});
});
},
@@ -161,13 +240,37 @@ export default Vue.extend({
return promise;
},
menu() {
let menu = [{
icon: '%fa:list%',
text: '%i18n:@push-to-a-list%',
action: () => {
const w = (this as any).os.new(MkUserListsWindow);
w.$once('choosen', async list => {
w.close();
await (this as any).api('users/lists/push', {
listId: list.id,
userId: this.user.id
});
(this as any).os.new(Ok);
});
}
}];
this.os.new(Menu, {
source: this.$refs.menu,
compact: false,
items: menu
});
}
}
});
</script>
<style lang="stylus" scoped>
.zubukjlciycdsyynicqrnlsmdwmymzqu
background var(--deckUserColumnBg)
background var(--deckColumnBg)
> .is-remote
padding 8px 16px
@@ -191,6 +294,14 @@ export default Vue.extend({
color #fff
text-align center
> .menu
position absolute
top 8px
left 8px
padding 8px
font-size 16px
text-shadow 0 0 8px #000
> .follow
position absolute
top 16px
@@ -215,7 +326,7 @@ export default Vue.extend({
> .info
padding 16px
font-size 14px
font-size 12px
color var(--text)
text-align center
background var(--face)
@@ -237,7 +348,7 @@ export default Vue.extend({
> .pinned
padding-bottom 16px
background var(--deckUserColumnBg)
background var(--deckColumnBg)
> p
margin 0
@@ -250,18 +361,23 @@ export default Vue.extend({
> .images
display grid
grid-template-rows 1fr 1fr 1fr
grid-template-columns 1fr 1fr 1fr
gap 4px
height 250px
gap 8px
padding 16px
margin-bottom 16px
background var(--face)
> *
height 70px
background-position center center
background-size cover
background-clip content-box
border-radius 4px
> .activity
padding 16px
margin-bottom 16px
background var(--face)
> .tl
background var(--face)

View File

@@ -1,17 +1,18 @@
<template>
<mk-ui :class="$style.root">
<div class="qlvquzbjribqcaozciifydkngcwtyzje" :style="style" :class="{ center: $store.state.device.deckColumnAlign == 'center' }">
<div class="qlvquzbjribqcaozciifydkngcwtyzje" ref="body" :style="style" :class="{ center: $store.state.device.deckColumnAlign == 'center' }" v-hotkey.global="keymap">
<template v-for="ids in layout">
<div v-if="ids.length > 1" class="folder">
<template v-for="id, i in ids">
<x-column-core :ref="id" :key="id" :column="columns.find(c => c.id == id)" :is-stacked="true"/>
<x-column-core :ref="id" :key="id" :column="columns.find(c => c.id == id)" :is-stacked="true" @parentFocus="moveFocus(id, $event)"/>
</template>
</div>
<x-column-core v-else :ref="ids[0]" :key="ids[0]" :column="columns.find(c => c.id == ids[0])"/>
<x-column-core v-else :ref="ids[0]" :key="ids[0]" :column="columns.find(c => c.id == ids[0])" @parentFocus="moveFocus(ids[0], $event)"/>
</template>
<template v-if="temporaryColumn">
<x-user-column v-if="temporaryColumn.type == 'user'" :acct="temporaryColumn.acct" :key="temporaryColumn.acct"/>
<x-note-column v-else-if="temporaryColumn.type == 'note'" :note-id="temporaryColumn.noteId" :key="temporaryColumn.noteId"/>
<x-hashtag-column v-else-if="temporaryColumn.type == 'tag'" :tag="temporaryColumn.tag" :key="temporaryColumn.tag"/>
</template>
<button ref="add" @click="add" title="%i18n:common.deck.add-column%">%fa:plus%</button>
</div>
@@ -25,6 +26,7 @@ import Menu from '../../../../common/views/components/menu.vue';
import MkUserListsWindow from '../../components/user-lists-window.vue';
import XUserColumn from './deck.user-column.vue';
import XNoteColumn from './deck.note-column.vue';
import XHashtagColumn from './deck.hashtag-column.vue';
import * as uuid from 'uuid';
@@ -32,7 +34,8 @@ export default Vue.extend({
components: {
XColumnCore,
XUserColumn,
XNoteColumn
XNoteColumn,
XHashtagColumn
},
computed: {
@@ -55,6 +58,25 @@ export default Vue.extend({
temporaryColumn(): any {
return this.$store.state.device.deckTemporaryColumn;
},
keymap(): any {
return {
't': this.focus
};
}
},
watch: {
temporaryColumn() {
if (this.temporaryColumn != null) {
this.$nextTick(() => {
this.$refs.body.scrollTo({
left: this.$refs.body.scrollWidth - this.$refs.body.clientWidth,
behavior: 'smooth'
});
});
}
}
},
@@ -143,6 +165,15 @@ export default Vue.extend({
}
});
return true;
} else if (to.name == 'tag') {
this.$store.commit('device/set', {
key: 'deckTemporaryColumn',
value: {
type: 'tag',
tag: to.params.tag
}
});
return true;
}
},
@@ -253,6 +284,71 @@ export default Vue.extend({
}
}]
});
},
focus() {
// Flatten array of arrays
const ids = [].concat.apply([], this.layout);
const firstTl = ids.find(id => this.isTlColumn(id));
if (firstTl) {
this.$refs[firstTl][0].focus();
}
},
moveFocus(id, direction) {
let targetColumn;
if (direction == 'right') {
const currentColumnIndex = this.layout.findIndex(ids => ids.includes(id));
this.layout.some((ids, i) => {
if (i <= currentColumnIndex) return false;
const tl = ids.find(id => this.isTlColumn(id));
if (tl) {
targetColumn = tl;
return true;
}
});
} else if (direction == 'left') {
const currentColumnIndex = [...this.layout].reverse().findIndex(ids => ids.includes(id));
[...this.layout].reverse().some((ids, i) => {
if (i <= currentColumnIndex) return false;
const tl = ids.find(id => this.isTlColumn(id));
if (tl) {
targetColumn = tl;
return true;
}
});
} else if (direction == 'down') {
const currentColumn = this.layout.find(ids => ids.includes(id));
const currentIndex = currentColumn.indexOf(id);
currentColumn.some((_id, i) => {
if (i <= currentIndex) return false;
if (this.isTlColumn(_id)) {
targetColumn = _id;
return true;
}
});
} else if (direction == 'up') {
const currentColumn = [...this.layout.find(ids => ids.includes(id))].reverse();
const currentIndex = currentColumn.indexOf(id);
currentColumn.some((_id, i) => {
if (i <= currentIndex) return false;
if (this.isTlColumn(_id)) {
targetColumn = _id;
return true;
}
});
}
if (targetColumn) {
this.$refs[targetColumn][0].focus();
}
},
isTlColumn(id) {
const column = this.columns.find(c => c.id === id);
return ['home', 'local', 'hybrid', 'global', 'list', 'hashtag', 'mentions', 'direct'].includes(column.type);
}
}
});

View File

@@ -1,16 +1,25 @@
<template>
<component :is="$store.getters.isSignedIn ? 'home' : 'welcome'"></component>
<component :is="page"></component>
</template>
<script lang="ts">
import Vue from 'vue';
import Home from './home.vue';
import Welcome from './welcome.vue';
import Deck from './deck/deck.vue';
export default Vue.extend({
components: {
Home,
Deck,
Welcome
},
computed: {
page(): string {
if (!this.$store.getters.isSignedIn) return 'welcome';
return this.$store.state.device.deckDefault ? 'deck' : 'home';
}
}
});
</script>

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

@@ -9,11 +9,11 @@
</p>
</div>
<div class="action-form">
<button class="mute ui" @click="user.isMuted ? unmute() : mute()" v-if="$store.state.i.id != user.id">
<ui-button @click="user.isMuted ? unmute() : mute()" v-if="$store.state.i.id != user.id">
<span v-if="user.isMuted">%fa:eye% %i18n:@unmute%</span>
<span v-if="!user.isMuted">%fa:eye-slash% %i18n:@mute%</span>
</button>
<button class="mute ui" @click="list">%fa:list% %i18n:@push-to-a-list%</button>
</ui-button>
<ui-button @click="list">%fa:list% %i18n:@push-to-a-list%</ui-button>
</div>
</div>
</template>

View File

@@ -2,7 +2,7 @@
<mk-ui>
<div class="xygkxeaeontfaokvqmiblezmhvhostak" v-if="!fetching">
<div class="is-suspended" v-if="user.isSuspended">%fa:exclamation-triangle% %i18n:@is-suspended%</div>
<div class="is-remote" v-if="user.host != null">%fa:exclamation-triangle% %i18n:@is-remote%<a :href="user.url || user.uri" target="_blank">%i18n:@view-remote%</a></div>
<div class="is-remote" v-if="user.host != null">%fa:exclamation-triangle% %i18n:common.is-remote-user%<a :href="user.url || user.uri" target="_blank">%i18n:common.view-on-remote%</a></div>
<main>
<div class="main">
<x-header :user="user"/>

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

@@ -161,6 +161,18 @@ export default (callback: (launch: (router: VueRouter, api?: (os: MiOS) => API)
}
});
document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
os.store.commit('clearBehindNotes');
}
}, false);
window.addEventListener('scroll', () => {
if (window.scrollY <= 8) {
os.store.commit('clearBehindNotes');
}
}, { passive: true });
Vue.mixin({
data() {
return {
@@ -210,15 +222,15 @@ function panic(e) {
document.documentElement.style.background = '#1269e2';
document.body.innerHTML =
'<div id="error">'
+ '<h1>:( 致命的な問題が発生しました。</h1>'
+ '<p>お使いのブラウザ(またはOS)のバージョンを更新すると解決する可能性があります。</p>'
+ '<h1>%i18n.common.BSoD.fatal-error%</h1>'
+ '<p>%i18n.common.BSoD.update-browser-os%</p>'
+ '<hr>'
+ `<p>エラーコード: ${e.toString()}</p>`
+ `<p>ブラウザ バージョン: ${navigator.userAgent}</p>`
+ `<p>クライアント バージョン: ${version}</p>`
+ `<p>%i18n.common.BSoD.error-code%: ${e.toString()}</p>`
+ `<p>%i18n.common.BSoD.browser-version%: ${navigator.userAgent}</p>`
+ `<p>%i18n.common.BSoD.client-version%: ${version}</p>`
+ '<hr>'
+ '<p>問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。</p>'
+ '<p>Thank you for using Misskey.</p>'
+ '<p>%i18n.common.BSoD.email-support%</p>'
+ '<p>%i18n.common.BSoD.thanks%</p>'
+ '</div>';
// TODO: Report the bug

View File

@@ -244,10 +244,10 @@ export default class MiOS extends EventEmitter {
this.store.dispatch('login', me);
fetched();
} else {
this.initStream();
// Finish init
callback();
this.initStream();
}
});
}

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

@@ -31,7 +31,7 @@
<span v-if="appearNote.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
<a class="reply" v-if="appearNote.reply">%fa:reply%</a>
<misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :class="$style.text"/>
<a class="rp" v-if="appearNote.renote != null">RP:</a>
<a class="rp" v-if="appearNote.renote != null">RN:</a>
</div>
<div class="files" v-if="appearNote.files.length > 0">
<mk-media-list :media-list="appearNote.files"/>

View File

@@ -37,7 +37,6 @@
<script lang="ts">
import Vue from 'vue';
import getNoteSummary from '../../../../../misc/get-note-summary';
const displayLimit = 30;
@@ -54,7 +53,6 @@ export default Vue.extend({
requestInitPromise: null as () => Promise<any[]>,
notes: [],
queue: [],
unreadCount: 0,
fetching: true,
moreFetching: false
};
@@ -83,12 +81,10 @@ export default Vue.extend({
},
mounted() {
document.addEventListener('visibilitychange', this.onVisibilitychange, false);
window.addEventListener('scroll', this.onScroll, { passive: true });
},
beforeDestroy() {
document.removeEventListener('visibilitychange', this.onVisibilitychange);
window.removeEventListener('scroll', this.onScroll);
},
@@ -146,10 +142,9 @@ export default Vue.extend({
}
//#endregion
// 投稿が自分のものではないかつ、タブが非表示またはスクロール位置が最上部ではないならタイトルで通知
if ((document.hidden || !this.isScrollTop()) && note.userId !== this.$store.state.i.id) {
this.unreadCount++;
document.title = `(${this.unreadCount}) ${getNoteSummary(note)}`;
// タブが非表示またはスクロール位置が最上部ではないならタイトルで通知
if (document.hidden || !this.isScrollTop()) {
this.$store.commit('pushBehindNote', note);
}
if (this.isScrollTop()) {
@@ -187,21 +182,9 @@ export default Vue.extend({
this.moreFetching = false;
},
clearNotification() {
this.unreadCount = 0;
document.title = (this as any).os.instanceName;
},
onVisibilitychange() {
if (!document.hidden) {
this.clearNotification();
}
},
onScroll() {
if (this.isScrollTop()) {
this.releaseQueue();
this.clearNotification();
}
if (this.$store.state.settings.fetchOnScroll !== false) {

View File

@@ -5,7 +5,7 @@
<span v-if="note.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span>
<a class="reply" v-if="note.replyId">%fa:reply%</a>
<misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i"/>
<a class="rp" v-if="note.renoteId">RP: ...</a>
<a class="rp" v-if="note.renoteId">RN: ...</a>
</div>
<details v-if="note.files.length > 0">
<summary>({{ '%i18n:@media-count%'.replace('{}', note.files.length) }})</summary>

View File

@@ -3,7 +3,7 @@
<template slot="header" v-if="!fetching"><img :src="user.avatarUrl" alt="">{{ user | userName }}</template>
<main v-if="!fetching">
<div class="is-suspended" v-if="user.isSuspended"><p>%fa:exclamation-triangle% %i18n:@is-suspended%</p></div>
<div class="is-remote" v-if="user.host != null"><p>%fa:exclamation-triangle% %i18n:@is-remote%<a :href="user.url || user.uri" target="_blank">%i18n:@view-remote%</a></p></div>
<div class="is-remote" v-if="user.host != null"><p>%fa:exclamation-triangle% %i18n:common.is-remote-user%<a :href="user.url || user.uri" target="_blank">%i18n:common.view-on-remote%</a></p></div>
<header>
<div class="banner" :style="style"></div>
<div class="body">

View File

@@ -5,6 +5,7 @@ import * as nestedProperty from 'nested-property';
import MiOS from './mios';
import { hostname } from './config';
import { erase } from '../../prelude/array';
import getNoteSummary from '../../misc/get-note-summary';
const defaultSettings = {
home: null,
@@ -60,7 +61,8 @@ const defaultDeviceSettings = {
navbar: 'top',
deckColumnAlign: 'center',
mobileNotificationPosition: 'bottom',
deckTemporaryColumn: null
deckTemporaryColumn: null,
deckDefault: false
};
export default (os: MiOS) => new Vuex.Store({
@@ -72,7 +74,8 @@ export default (os: MiOS) => new Vuex.Store({
i: null,
indicate: false,
uiHeaderHeight: 0,
navHook: null
navHook: null,
behindNotes: []
},
getters: {
@@ -98,6 +101,18 @@ export default (os: MiOS) => new Vuex.Store({
navHook(state, callback) {
state.navHook = callback;
},
pushBehindNote(state, note) {
if (note.userId === state.i.id) return;
if (state.behindNotes.some(n => n.id === note.id)) return;
state.behindNotes.push(note);
document.title = `(${state.behindNotes.length}) ${getNoteSummary(note)}`;
},
clearBehindNotes(state) {
state.behindNotes = [];
document.title = os.instanceName;
}
},

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

@@ -25,9 +25,9 @@
<tbody>
<tr><td><kbd class="key"></kbd>, <kbd class="key">K</kbd>, <kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">Tab</kbd></kbd></td><td>上の投稿にフォーカスを移動</td><td>-</td></tr>
<tr><td><kbd class="key"></kbd>, <kbd class="key">J</kbd>, <kbd class="key">Tab</kbd></td><td>下の投稿にフォーカスを移動</td><td>-</td></tr>
<tr><td><kbd class="key"></kbd>, <kbd class="key">R</kbd></td><td>返信フォームを開く</td><td><b>R</b>eply</td></tr>
<tr><td><kbd class="key"></kbd>, <kbd class="key">Q</kbd></td><td>Renoteフォームを開く</td><td><b>Q</b>uote</td></tr>
<tr><td><kbd class="group"><kbd class="key">Ctrl</kbd> + <kbd class="key"></kbd></kbd>, <kbd class="group"><kbd class="key">Ctrl</kbd> + <kbd class="key">Q</kbd></kbd></td><td>即刻Renoteする(フォームを開かずに)</td><td>-</td></tr>
<tr><td><kbd class="key">R</kbd></td><td>返信フォームを開く</td><td><b>R</b>eply</td></tr>
<tr><td><kbd class="key">Q</kbd></td><td>Renoteフォームを開く</td><td><b>Q</b>uote</td></tr>
<tr><td><kbd class="group"><kbd class="key">Ctrl</kbd> + <kbd class="key">Q</kbd></kbd></td><td>即刻Renoteする(フォームを開かずに)</td><td>-</td></tr>
<tr><td><kbd class="key">E</kbd>, <kbd class="key">A</kbd>, <kbd class="key">+</kbd></td><td>リアクションフォームを開く</td><td><b>E</b>mote, re<b>A</b>ction</td></tr>
<tr><td><kbd class="key">0</kbd>~<kbd class="key">9</kbd></td><td>数字に対応したリアクションをする(対応については後述)</td><td>-</td></tr>
<tr><td><kbd class="key">F</kbd>, <kbd class="key">B</kbd></td><td>お気に入りに登録</td><td><b>F</b>avorite, <b>B</b>ookmark</td></tr>
@@ -86,6 +86,19 @@
</tbody>
</table>
## デッキ
<table>
<thead>
<tr><th>ショートカット</th><th>効果</th><th>由来</th></tr>
</thead>
<tbody>
<tr><td>投稿にフォーカスした状態で<kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key"></kbd></kbd></td><td>上のカラムにフォーカス</td><td>-</td></tr>
<tr><td>投稿にフォーカスした状態で<kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key"></kbd></kbd></td><td>下のカラムにフォーカス</td><td>-</td></tr>
<tr><td>投稿にフォーカスした状態で<kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key"></kbd></kbd></td><td>右のカラムにフォーカス</td><td>-</td></tr>
<tr><td>投稿にフォーカスした状態で<kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key"></kbd></kbd></td><td>左のカラムにフォーカス</td><td>-</td></tr>
</tbody>
</table>
# 例
<table>
<thead>

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

@@ -9,9 +9,9 @@ export type TextElementHashtag = {
};
export default function(text: string, i: number) {
if (!(/^\s#[^\s\.,]+/.test(text) || (i == 0 && /^#[^\s\.,]+/.test(text)))) return null;
if (!(/^\s#[^\s\.,!\?]+/.test(text) || (i == 0 && /^#[^\s\.,!\?]+/.test(text)))) return null;
const isHead = text.startsWith('#');
const hashtag = text.match(/^\s?#[^\s\.,]+/)[0];
const hashtag = text.match(/^\s?#[^\s\.,!\?]+/)[0];
const res: any[] = !isHead ? [{
type: 'text',
content: text[0]

View File

@@ -38,9 +38,9 @@ const summarize = (note: any): string => {
// Renoteのとき
if (note.renoteId) {
if (note.renote) {
summary += ` RP: ${summarize(note.renote)}`;
summary += ` RN: ${summarize(note.renote)}`;
} else {
summary += ' RP: ...';
summary += ' RN: ...';
}
}

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);
});

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