Compare commits

..

186 Commits

Author SHA1 Message Date
syuilo
031287aebd 10.79.0 2019-01-27 16:43:50 +09:00
syuilo
b24de13a37 Update CHANGELOG.md 2019-01-27 16:43:06 +09:00
Aya Morisawa
c113fdc20e Merge pull request #3987 from syuilo/math-block
複数行用の数式構文を追加
2019-01-27 16:41:49 +09:00
Aya Morisawa
1af1638e2b Merge branch 'develop' into math-block 2019-01-27 16:41:30 +09:00
syuilo
8c62aafa97 Fix test 2019-01-27 16:36:01 +09:00
syuilo
4de62220e3 [MFM] Add flip syntax
Resolve #4002
2019-01-27 16:31:00 +09:00
syuilo
e5d9381503 [MFM] Add spin syntax
Resolve #4003
2019-01-27 16:18:04 +09:00
syuilo
d906d90010 [Server] Introduce admin stream channel 2019-01-27 14:55:02 +09:00
syuilo
b836528b51 Celan up 2019-01-27 14:44:39 +09:00
syuilo
4b191c7f68 [Client] Improve syntax highlighting
Resolve #3926
Resolve #3390
2019-01-27 14:34:52 +09:00
syuilo
f9f70d5df4 Clean up 2019-01-27 14:30:57 +09:00
syuilo
50b809784f Improve readability and some cleanups 2019-01-27 13:55:11 +09:00
Acid Chicken (硫酸鶏)
54ce19bd56 Update parser.ts (#3999)
* Update parser.ts

* Update parser.ts

* Update parser.ts
2019-01-27 13:50:47 +09:00
syuilo
71210595d2 [Test] Add a MFM test 2019-01-27 13:48:56 +09:00
syuilo
085325e65f [MFM] Improve title syntax detection 2019-01-27 13:40:38 +09:00
syuilo
7dcea49be7 Refactoring 2019-01-27 13:29:53 +09:00
syuilo
7c91915e50 Merge pull request #3997 from syuilo/l10n_develop
New Crowdin translations
2019-01-27 13:12:00 +09:00
syuilo
895e80d794 New translations ja-JP.yml (French) 2019-01-27 13:11:47 +09:00
dependabot[bot]
be5714a9f1 Update qrcode requirement from 1.3.2 to 1.3.3 (#3988)
Updates the requirements on [qrcode](https://github.com/soldair/node-qrcode) to permit the latest version.
- [Release notes](https://github.com/soldair/node-qrcode/releases)
- [Changelog](https://github.com/soldair/node-qrcode/blob/master/CHANGELOG.md)
- [Commits](https://github.com/soldair/node-qrcode/commits/v1.3.3)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-01-27 12:36:47 +09:00
syuilo
1ba8374292 Merge pull request #3983 from syuilo/l10n_develop
New Crowdin translations
2019-01-27 10:59:28 +09:00
MeiMei
94154a1aa2 テーマによってコードが見づらいのを少し改善 (#3993) 2019-01-26 21:10:25 +09:00
syuilo
5ae576bad1 🎨 2019-01-26 20:59:14 +09:00
syuilo
f9e6c84d00 New translations ja-JP.yml (English) 2019-01-26 19:22:23 +09:00
syuilo
3ab5d2e0e1 New translations ja-JP.yml (Norwegian) 2019-01-26 18:04:03 +09:00
syuilo
b8f60527f6 New translations ja-JP.yml (Dutch) 2019-01-26 18:03:57 +09:00
syuilo
a83c3557fc New translations ja-JP.yml (Japanese, Kansai) 2019-01-26 18:03:50 +09:00
syuilo
a45abf858f New translations ja-JP.yml (Spanish) 2019-01-26 18:03:45 +09:00
syuilo
a82eeb7e92 New translations ja-JP.yml (Russian) 2019-01-26 18:03:39 +09:00
syuilo
3781f07c49 New translations ja-JP.yml (Portuguese) 2019-01-26 18:03:35 +09:00
syuilo
990bc976de New translations ja-JP.yml (Polish) 2019-01-26 18:03:30 +09:00
syuilo
ff2fc2267d New translations ja-JP.yml (Korean) 2019-01-26 18:03:26 +09:00
syuilo
ec50240a03 New translations ja-JP.yml (Italian) 2019-01-26 18:03:21 +09:00
syuilo
bf00b59339 New translations ja-JP.yml (German) 2019-01-26 18:03:17 +09:00
syuilo
456b01590b New translations ja-JP.yml (French) 2019-01-26 18:03:13 +09:00
syuilo
5eeec0becb New translations ja-JP.yml (English) 2019-01-26 18:03:08 +09:00
syuilo
e5fcb2aea0 New translations ja-JP.yml (Chinese Simplified) 2019-01-26 18:03:03 +09:00
syuilo
d717b1d404 New translations ja-JP.yml (Catalan) 2019-01-26 18:02:59 +09:00
syuilo
2334b41375 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-01-26 17:54:09 +09:00
syuilo
6f6974a6ae Update CHANGELOG.md 2019-01-26 17:54:00 +09:00
MeiMei
0854f2e180 管理画面からリモートユーザー情報を更新できるように (#3992) 2019-01-26 17:53:35 +09:00
syuilo
a0f8c7e94e Resolve #2253 2019-01-26 17:47:56 +09:00
syuilo
1f5e3040ed New translations ja-JP.yml (Norwegian) 2019-01-26 17:23:40 +09:00
syuilo
6128e62751 New translations ja-JP.yml (Dutch) 2019-01-26 17:23:36 +09:00
syuilo
7c6088b2b4 New translations ja-JP.yml (Japanese, Kansai) 2019-01-26 17:23:32 +09:00
syuilo
9450e567c6 New translations ja-JP.yml (Spanish) 2019-01-26 17:23:27 +09:00
syuilo
c9c2e36540 New translations ja-JP.yml (Russian) 2019-01-26 17:23:22 +09:00
syuilo
fbfd3a60ed New translations ja-JP.yml (Portuguese) 2019-01-26 17:23:18 +09:00
syuilo
9b386fd50e New translations ja-JP.yml (Polish) 2019-01-26 17:23:14 +09:00
syuilo
db5a404081 New translations ja-JP.yml (Korean) 2019-01-26 17:23:09 +09:00
syuilo
3bffe605f7 New translations ja-JP.yml (Italian) 2019-01-26 17:23:02 +09:00
syuilo
4e92eb55cd New translations ja-JP.yml (German) 2019-01-26 17:22:57 +09:00
syuilo
6667ddbc26 New translations ja-JP.yml (French) 2019-01-26 17:22:50 +09:00
syuilo
16e4bb7f79 New translations ja-JP.yml (English) 2019-01-26 17:22:45 +09:00
syuilo
821182cad5 New translations ja-JP.yml (Chinese Simplified) 2019-01-26 17:22:41 +09:00
syuilo
b3730e3373 New translations ja-JP.yml (Catalan) 2019-01-26 17:22:36 +09:00
syuilo
741efa1a9a Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-01-26 17:14:53 +09:00
syuilo
de1a7b4364 Update CHANGELOG.md 2019-01-26 17:14:43 +09:00
MeiMei
85cd647946 Improve moderation UI (#3989)
* admin users UI

* tune
2019-01-26 17:14:10 +09:00
syuilo
da7d1938c9 [Server] Fix #3991 2019-01-26 17:11:02 +09:00
MeiMei
5a795c4ab2 tools/resync-remote-user で exit しないように (#3990) 2019-01-26 16:56:05 +09:00
Aya Morisawa
95d4937e16 Remove whiteP and blackP from Reversi (#3736) 2019-01-26 11:30:30 +09:00
Acid Chicken (硫酸鶏)
6bbc6a80b2 Update style.styl (#3986) 2019-01-26 11:26:32 +09:00
Aya Morisawa
79d2374d8e Add multiline math syntax
Co-authored-by: syuilo <syuilotan@yahoo.co.jp>
2019-01-25 23:08:06 +09:00
syuilo
e4601962d0 New translations ja-JP.yml (English) 2019-01-25 19:53:11 +09:00
syuilo
4398651841 Refactor 2019-01-25 19:37:45 +09:00
syuilo
42cd7c8a75 [MFM] Improve italic syntax detection 2019-01-25 16:41:51 +09:00
syuilo
501379c82c [Test] Add sone streaming tests 2019-01-25 16:14:09 +09:00
syuilo
92b45d1a9d Refactoring 2019-01-25 15:56:49 +09:00
syuilo
3330c3f548 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-01-25 11:05:34 +09:00
syuilo
3552b1c900 Update CHANGELOG.md 2019-01-25 11:05:24 +09:00
syuilo
428d27a27b New translations ja-JP.yml (Spanish) 2019-01-25 11:01:47 +09:00
dependabot[bot]
4f6e387d49 Update koa-views requirement from 6.1.4 to 6.1.5 (#3979)
Updates the requirements on [koa-views](https://github.com/queckezz/koa-views) to permit the latest version.
- [Release notes](https://github.com/queckezz/koa-views/releases)
- [Changelog](https://github.com/queckezz/koa-views/blob/master/history.md)
- [Commits](https://github.com/queckezz/koa-views/commits/v6.1.5)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-01-25 11:00:02 +09:00
syuilo
01c0253c89 Merge pull request #3972 from syuilo/l10n_develop
New Crowdin translations
2019-01-25 10:59:34 +09:00
syuilo
76fcf122f9 [Test] Add notes/timeline test 2019-01-25 10:58:39 +09:00
syuilo
926ad23033 [Test] Add API test 2019-01-25 10:52:04 +09:00
syuilo
b956d63c46 New translations ja-JP.yml (Spanish) 2019-01-25 10:11:46 +09:00
syuilo
1970927b5e New translations ja-JP.yml (Spanish) 2019-01-25 08:52:43 +09:00
syuilo
3995ae0957 New translations ja-JP.yml (Spanish) 2019-01-25 08:21:24 +09:00
syuilo
c2a840fa19 New translations ja-JP.yml (English) 2019-01-25 08:21:20 +09:00
syuilo
c7a6321a08 New translations ja-JP.yml (Spanish) 2019-01-25 08:12:04 +09:00
syuilo
0c42c54b7a New translations ja-JP.yml (Spanish) 2019-01-25 08:01:47 +09:00
syuilo
8b775c85f6 New translations ja-JP.yml (Spanish) 2019-01-25 07:52:46 +09:00
syuilo
5c14ff661f New translations ja-JP.yml (Spanish) 2019-01-25 07:42:51 +09:00
syuilo
160c1a3022 New translations ja-JP.yml (Spanish) 2019-01-25 07:22:31 +09:00
MeiMei
5eca0a31f7 Filter hidden replies / mentions (#3981)
* Fix: 非公開投稿が返信一覧に出てくる

* Fix: 非公開投稿がメンション一覧に出てくる

* 非公開投稿は通知/メンション通知しない

* repliesにフォロワー限定がかからなかったのを修正

* Fix: ホームにフォロワー限定投稿が表示されない

* 認証必須エンドポイントで user == null にはならない

* mentionsにフォロワー限定がかからなかったのを修正
2019-01-25 00:06:20 +09:00
syuilo
44d53488e0 New translations ja-JP.yml (English) 2019-01-24 21:05:22 +09:00
syuilo
d731c7da13 Update CONTRIBUTING.md 2019-01-24 19:52:00 +09:00
MeiMei
225544e985 Reapply the theme first (#3971)
* Reapply the theme first

* Cannot
2019-01-24 18:06:20 +09:00
Acid Chicken (硫酸鶏)
e2f7e82cac 外部サービス認証情報の配信 (#3975)
* Update person.ts

* Update person.ts

* Update person.ts

* Update person.ts

* Create original model

* Make type formal

* Update person.ts

* Follow @mei23's review

refs: https://github.com/syuilo/misskey/pull/3975#pullrequestreview-195770172
2019-01-24 17:33:39 +09:00
syuilo
0be7bf93d9 New translations ja-JP.yml (Chinese Simplified) 2019-01-24 11:52:03 +09:00
syuilo
75f9e6bdf5 New translations ja-JP.yml (Chinese Simplified) 2019-01-24 11:26:44 +09:00
syuilo
8f6ea13696 New translations ja-JP.yml (English) 2019-01-24 09:21:48 +09:00
syuilo
f434b3a875 New translations ja-JP.yml (English) 2019-01-24 09:12:19 +09:00
syuilo
70b980d463 New translations ja-JP.yml (Norwegian) 2019-01-23 20:54:03 +09:00
syuilo
a59cf87374 New translations ja-JP.yml (Dutch) 2019-01-23 20:53:56 +09:00
syuilo
c9a9d5dbfd New translations ja-JP.yml (Japanese, Kansai) 2019-01-23 20:53:51 +09:00
syuilo
e0480f4e01 New translations ja-JP.yml (Spanish) 2019-01-23 20:53:45 +09:00
syuilo
d8cfd8f56d New translations ja-JP.yml (Russian) 2019-01-23 20:53:38 +09:00
syuilo
593d3e2d55 New translations ja-JP.yml (Portuguese) 2019-01-23 20:53:33 +09:00
syuilo
a79901b441 New translations ja-JP.yml (Polish) 2019-01-23 20:53:29 +09:00
syuilo
4c526f2837 New translations ja-JP.yml (Korean) 2019-01-23 20:53:24 +09:00
syuilo
3c28dd92ec New translations ja-JP.yml (Italian) 2019-01-23 20:53:19 +09:00
syuilo
d09af4754a New translations ja-JP.yml (German) 2019-01-23 20:53:13 +09:00
syuilo
44ab6cbc39 New translations ja-JP.yml (French) 2019-01-23 20:53:09 +09:00
syuilo
6c42db7589 New translations ja-JP.yml (English) 2019-01-23 20:53:04 +09:00
syuilo
20dfd2faca New translations ja-JP.yml (Chinese Simplified) 2019-01-23 20:52:59 +09:00
syuilo
13696a85ee New translations ja-JP.yml (Catalan) 2019-01-23 20:52:53 +09:00
Acid Chicken (硫酸鶏)
4632eecb76 Back to the #3813 (#3949)
* Revert "Revert "Resolve #3813 (#3814)""

This reverts commit f433182c4c.

* Keep CW is back

* New Wave

https://github.com/syuilo/misskey/pull/3949#pullrequestreview-194787210
2019-01-23 20:43:06 +09:00
syuilo
44a3df0acf Merge pull request #3965 from syuilo/l10n_develop
New Crowdin translations
2019-01-23 19:47:22 +09:00
syuilo
4ab96251f5 [Client] Resolve #3967 2019-01-23 19:45:14 +09:00
syuilo
b795379417 Clean up 2019-01-23 19:43:45 +09:00
syuilo
aa9ba31675 Squash multiple space 2019-01-23 19:42:47 +09:00
syuilo
ad66c8478a Refactoring 2019-01-23 19:33:29 +09:00
syuilo
4ec64b4c57 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-01-23 19:25:52 +09:00
syuilo
2f0b75a882 Refactoring 2019-01-23 19:25:36 +09:00
Acid Chicken (硫酸鶏)
912ee60781 Update index.ts
refs: 67dacb7725
2019-01-23 16:53:07 +09:00
syuilo
67dacb7725 Supress log when test 2019-01-23 14:19:03 +09:00
syuilo
82f1fc6cda Fix test 2019-01-23 13:50:36 +09:00
syuilo
4d24741d48 Fix test 2019-01-23 13:35:22 +09:00
syuilo
931f17c589 Refactor: Separate some test files 2019-01-23 12:15:27 +09:00
syuilo
ff898b4c20 Update tslint.json 2019-01-23 11:55:47 +09:00
syuilo
7b507c8480 New translations ja-JP.yml (English) 2019-01-23 11:51:44 +09:00
syuilo
bb688f78fc New translations ja-JP.yml (English) 2019-01-23 10:47:34 +09:00
syuilo
a2a31236f6 10.78.5 2019-01-23 05:37:37 +09:00
syuilo
0b191b4d0e Merge pull request #3959 from syuilo/l10n_develop
New Crowdin translations
2019-01-23 05:33:51 +09:00
syuilo
2e97f29411 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-01-23 05:30:12 +09:00
syuilo
eb1ad54427 Clean up 2019-01-23 05:30:02 +09:00
syuilo
e4974392e5 🎨 2019-01-23 05:25:46 +09:00
dependabot[bot]
4e0d43b45a Update progress-bar-webpack-plugin requirement from 1.11.0 to 1.12.0 (#3962)
Updates the requirements on [progress-bar-webpack-plugin](https://github.com/clessg/progress-bar-webpack-plugin) to permit the latest version.
- [Release notes](https://github.com/clessg/progress-bar-webpack-plugin/releases)
- [Commits](https://github.com/clessg/progress-bar-webpack-plugin/commits/v1.12.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-01-23 05:24:23 +09:00
syuilo
78c185a05a [Client] Improve usability 2019-01-23 05:20:28 +09:00
MeiMei
fa124abbe2 Supports Twemoji / CustomEmoji in poll (#3960)
* Supports Twemoji / CustomEmoji in poll

* extract emojis in polls
2019-01-23 04:49:16 +09:00
syuilo
f4fa3f031e New translations ja-JP.yml (Korean) 2019-01-22 22:43:09 +09:00
syuilo
3cc7a99d0f New translations ja-JP.yml (Korean) 2019-01-22 22:34:27 +09:00
syuilo
8bf9e87117 Fix types 2019-01-22 21:42:05 +09:00
syuilo
97e8ac1d27 [Client] Fix bug 2019-01-22 21:32:51 +09:00
syuilo
45fb2ecb3a [Client] Fix UI 2019-01-22 21:25:37 +09:00
syuilo
d5e80caac8 [Server] Fix #3958 2019-01-22 21:21:47 +09:00
Acid Chicken (硫酸鶏)
7ceea61170 Create CODEOWNERS (#3882)
* Create CODEOWNERS

* Update CODEOWNERS

* Update CODEOWNERS

* Update CODEOWNERS
2019-01-22 20:56:55 +09:00
syuilo
a3ce65ee28 Merge pull request #3956 from syuilo/l10n_develop
New Crowdin translations
2019-01-22 20:56:10 +09:00
syuilo
d6b7a048e4 Merge branches 'develop', 'develop', 'develop' and 'develop' of https://github.com/syuilo/misskey into develop 2019-01-22 18:31:16 +09:00
syuilo
f7c8e31b36 Update mfm.ts 2019-01-22 18:30:58 +09:00
syuilo
26c327145f [Test] Add streaming test
#3955
2019-01-22 18:30:49 +09:00
syuilo
b7afd07d6a New translations ja-JP.yml (Chinese Simplified) 2019-01-22 18:11:53 +09:00
syuilo
eaff52548f New translations ja-JP.yml (Chinese Simplified) 2019-01-22 17:52:48 +09:00
syuilo
76828adc54 New translations ja-JP.yml (Chinese Simplified) 2019-01-22 17:42:35 +09:00
syuilo
198b0b3de3 New translations ja-JP.yml (Chinese Simplified) 2019-01-22 17:34:17 +09:00
syuilo
3cdee2732a New translations ja-JP.yml (Chinese Simplified) 2019-01-22 17:22:13 +09:00
syuilo
27a7bb7229 New translations ja-JP.yml (Chinese Simplified) 2019-01-22 17:15:40 +09:00
Stanislas
cf38a6d0a0 Little english improvements (#3931)
* Little english improvements

* Revert incorrect fix
2019-01-22 16:04:37 +09:00
syuilo
02c88f9b3b Merge pull request #3954 from syuilo/l10n_develop
New Crowdin translations
2019-01-22 15:35:47 +09:00
syuilo
3ac1077b36 New translations ja-JP.yml (French) 2019-01-22 15:32:15 +09:00
syuilo
2b4f6abc15 New translations ja-JP.yml (French) 2019-01-22 15:22:17 +09:00
syuilo
7bd24348d2 New translations ja-JP.yml (French) 2019-01-22 15:11:55 +09:00
dependabot[bot]
c49ae672f2 Update @types/koa-router requirement from 7.0.35 to 7.0.38 (#3951)
Updates the requirements on [@types/koa-router](https://github.com/DefinitelyTyped/DefinitelyTyped) to permit the latest version.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-01-22 08:08:29 +09:00
dependabot[bot]
2eb2cc7880 Update nodemailer requirement from 5.0.0 to 5.1.1 (#3950)
Updates the requirements on [nodemailer](https://github.com/nodemailer/nodemailer) to permit the latest version.
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/commits/v5.1.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-01-22 08:08:21 +09:00
dependabot[bot]
f2f3d4beec Update typescript requirement from 3.2.2 to 3.2.4 (#3953)
Updates the requirements on [typescript](https://github.com/Microsoft/TypeScript) to permit the latest version.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/commits/v3.2.4)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-01-22 08:08:02 +09:00
dependabot[bot]
3fd1ea900a Update vue-loader requirement from 15.4.2 to 15.5.1 (#3952)
Updates the requirements on [vue-loader](https://github.com/vuejs/vue-loader) to permit the latest version.
- [Release notes](https://github.com/vuejs/vue-loader/releases)
- [Changelog](https://github.com/vuejs/vue-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-loader/commits/v15.5.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-01-22 08:07:37 +09:00
syuilo
c815d11ed2 10.78.4 2019-01-21 21:55:18 +09:00
syuilo
350151ca5b Merge pull request #3945 from syuilo/l10n_develop
New Crowdin translations
2019-01-21 21:52:34 +09:00
syuilo
4339f9af29 [Server] Fix #3947 2019-01-21 21:51:58 +09:00
syuilo
b44227948d Add unique index
#3946
2019-01-21 21:45:11 +09:00
syuilo
5dc8c8846d New translations ja-JP.yml (Norwegian) 2019-01-21 21:05:29 +09:00
syuilo
e1bee8adf3 New translations ja-JP.yml (Dutch) 2019-01-21 21:05:23 +09:00
syuilo
b9ef750321 New translations ja-JP.yml (Japanese, Kansai) 2019-01-21 21:05:17 +09:00
syuilo
e05c0e7d37 New translations ja-JP.yml (Spanish) 2019-01-21 21:05:11 +09:00
syuilo
a3eb0ddc4f New translations ja-JP.yml (Russian) 2019-01-21 21:05:04 +09:00
syuilo
da6e71f2e0 New translations ja-JP.yml (Portuguese) 2019-01-21 21:04:59 +09:00
syuilo
09e08e829d New translations ja-JP.yml (Polish) 2019-01-21 21:04:54 +09:00
syuilo
1b78ae6290 New translations ja-JP.yml (Korean) 2019-01-21 21:04:48 +09:00
syuilo
97f5ba0bc5 New translations ja-JP.yml (Italian) 2019-01-21 21:04:41 +09:00
syuilo
8e29ccdc7f New translations ja-JP.yml (German) 2019-01-21 21:04:34 +09:00
syuilo
4e48214068 New translations ja-JP.yml (French) 2019-01-21 21:04:28 +09:00
syuilo
1bd128d507 New translations ja-JP.yml (English) 2019-01-21 21:04:24 +09:00
syuilo
bfc1f8a25d New translations ja-JP.yml (Chinese Simplified) 2019-01-21 21:04:17 +09:00
syuilo
6369d79aaf New translations ja-JP.yml (Catalan) 2019-01-21 21:04:12 +09:00
syuilo
2df2cf0983 [Client] Fix #3321 2019-01-21 21:03:55 +09:00
syuilo
c93fe423ea [API] Fix bug 2019-01-21 21:01:04 +09:00
syuilo
ecac2990eb [Client] Fix bug 2019-01-21 20:56:10 +09:00
syuilo
a483af1b08 10.78.3 2019-01-21 17:28:53 +09:00
syuilo
01584a6bf9 Fix 404 2019-01-21 17:25:36 +09:00
syuilo
443f967611 Update reversi.vue 2019-01-21 17:20:41 +09:00
syuilo
bf931f2c82 🎨 2019-01-21 17:19:00 +09:00
MeiMei
5b32b900e4 投票未対応インスタンス向けメッセージをわかりやすくする (#3944)
* Poll message

* fix

* fix

* とりあえず日本語にしちゃう

* TODO

* fix
2019-01-21 16:34:17 +09:00
Acid Chicken (硫酸鶏)
0bdcb15b3b Re: Fix routing
本当に申し訳ありませんでした。
2019-01-21 15:52:25 +09:00
Acid Chicken (硫酸鶏)
1b316ab98b Update README.md [AUTOGEN] (#3943) 2019-01-21 15:46:59 +09:00
syuilo
4cd79dd530 Update README.md 2019-01-21 15:40:58 +09:00
101 changed files with 1908 additions and 1350 deletions

39
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,39 @@
# PATH OWNERS
/.autogen/ @acid-chicken
/.circleci/ @syuilo @acid-chicken
/.config/ @syuilo @AyaMorisawa @mei23 @acid-chicken
# /.config/mongo_initdb_example.js @khws4v1
/.github/ @syuilo @AyaMorisawa @acid-chicken
/.vscode/ @acid-chicken
/assets/ @syuilo # @tamaina
/cli/ @syuilo
/docs/ @syuilo
/docs/*.en.md @AyaMorisawa # @skid9000
# /docs/*.fr.md @BoFFire
# /docs/docker.*.md @khws4v1
/locales/ @syuilo
/src/ @syuilo @AyaMorisawa @mei23 @acid-chicken
# /src/crypto_key.cc @akihikodaki
# /src/crypto_key.d.ts @akihikodaki
/.dockerignore @syuilo # @khws4v1
/.editorconfig @syuilo @AyaMorisawa
/.eslintrc @syuilo
/.gitattributes @syuilo
/.gitignore @syuilo
/.npmrc @syuilo
/.vsls.json @AyaMorisawa
/CHANGELOG.md @syuilo
/CODE_OF_CONDUCT.md @syuilo
/CONTRIBUTING.md @syuilo
/Dockerfile @syuilo @AyaMorisawa @acid-chicken # @khws4v1
/LICENSE @syuilo
/README.md @syuilo @AyaMorisawa @acid-chicken # @nikhiljha
# /binding.gyp @akihikodaki
/crowdin.yml @syuilo
# /docker-compose.yml @khws4v1
/gulpfile.ts @syuilo @AyaMorisawa
/jsconfig.json @syuilo @AyaMorisawa
/package.json @syuilo @AyaMorisawa
/tsconfig.json @syuilo @AyaMorisawa
/tslint.json @syuilo @AyaMorisawa
/webpack.config.ts @syuilo @AyaMorisawa

View File

@@ -1,6 +1,46 @@
ChangeLog
=========
10.79.0
----------
* 返信するときにCWを維持するかどうか設定できるように
* 外部サービス認証情報の配信
* 管理画面のモデレーションのUIを強化
* 管理画面からリモートユーザーの情報を更新できるように
* 回転構文の追加
* 左右反転構文の追加
* 複数行の数式構文を追加
* シンタックスハイライトの強化
* 引用投稿を削除したとき単なるRenoteとしてタイムラインに残る問題を修正
* イタリック構文の判定の改善
* タイトル構文の判定の改善
* テーマが反映されないことがある問題を修正
* ホームにフォロワー限定投稿が表示されない問題を修正
* 返信一覧を取得すると非公開投稿も取得されてしまう問題を修正
* メンション一覧を取得すると非公開投稿も取得されてしまう問題を修正
* 通知に非公開投稿が表示される問題を修正
* ダイレクトで投稿すると100の確率で表示が二重になる問題を修正
* ウィジットの投稿フォームで投稿するとデフォルトの公開範囲が適用されない問題を修正
10.78.5
----------
* アンケートの選択肢にカスタム絵文字を使えるように
* 投稿の返信を取得したときにミュートが適用されていない問題を修正
* ユーザビリティの強化
10.78.4
----------
* フォロワー限定投稿がユーザータイムラインに含まれていない問題を修正
* データベースのインデックス設定を修正
* UIの修正
* など
10.78.3
----------
* 投票未対応インスタンス向けメッセージをわかりやすく
* リバーシが404になる問題を修正
* デザインの修正
10.78.2
----------
* リバーシが404になる問題を修正

View File

@@ -38,3 +38,9 @@ Stands for _**M**iss**k**ey_.
### SW
Stands for _**S**ervice**W**orker_.
### Nyaize
な を にゃ にすること
#### Denyaize
Nyaizeを解除すること

View File

@@ -13,7 +13,7 @@
<a href="https://misskey.xyz">Misskey</a> is a decentralized microblogging platform born on Earth.
Since it exists within the Fediverse (a universe where various social media platforms are organized),
it is mutually linked with other social media platforms.
Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet? <a href="https://joinmisskey.github.io/">Find instance!</a>
Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet? <a href="https://joinmisskey.github.io/">Find an instance!</a>
</p>
<a href="https://www.patreon.com/syuilo"><img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" alt="Become a Patron!" width="160" /></a>
@@ -61,7 +61,7 @@ Organize and store your files! Want to post a picture you have already uploaded?
...and more! Experience Misskey with your own eyes at [misskey.xyz](https://misskey.xyz) or join one of the [other instances](https://joinmisskey.github.io/) that are available.
:package: Create Your Own Instance
:package: Create your own instance
----------------------------------------------------------------
Please see the [Setup and Installation Guide](./docs/setup.en.md).
@@ -69,6 +69,22 @@ Please see the [Setup and Installation Guide](./docs/setup.en.md).
----------------------------------------------------------------
Please see the [Contribution Guide](./CONTRIBUTING.md).
### Collaborators
<table>
<tr>
<td><img src="https://avatars3.githubusercontent.com/u/4439005?s=460&v=4" alt="syuilo" width="100"></td>
<td><img src="https://avatars0.githubusercontent.com/u/10798641?s=460&v=4" alt="AyaMorisawa" width="100"></td>
<td><img src="https://avatars1.githubusercontent.com/u/30769358?s=460&v=4" alt="mei23" width="100"></td>
<td><img src="https://avatars2.githubusercontent.com/u/20679825?s=460&v=4" alt="acid-chicken" width="100"></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/syuilo">@syuilo</a></td>
<td align="center"><a href="https://github.com/AyaMorisawa">@AyaMorisawa</a></td>
<td align="center"><a href="https://github.com/mei23">@mei23</a></td>
<td align="center"><a href="https://github.com/acid-chicken">@acid-chicken</a></td>
</tr>
</table>
:heart: Backers & Sponsors
----------------------------------------------------------------
<!-- PATREON_START -->
@@ -95,7 +111,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=2PsbFNw0tnubZzgSXD01R6hIgncfiElG7H7HX2Y3dyo%3D" alt="nemu" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3?token-time=2145916800&token-hash=9JtETp0X8gI280Ne1E8bxn6j4Lw5o2k4mJkICx97V_k%3D" alt="YUKIMOCHI" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/8241184/39e18850e87a449e9c9a71acb3310ebd/2?token-time=2145916800&token-hash=zrInDotuEIFslKphuSiCqr3M-r-rveTXjVKWr-VK6M0%3D" alt="Acid Chicken" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/8241184/39e18850e87a449e9c9a71acb3310ebd/3?token-time=2145916800&token-hash=gMq30aylxu5v3G8pRhWR5jeRBbYWEoRKjGbNeiCQz5g%3D" alt="Acid Chicken" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/2?token-time=2145916800&token-hash=zcwFxb2zopzWwksKVU1YpfAEjsl4yKT02aQ6yiAFRiQ%3D" alt="natalie" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=5T8XcaAf9Zyzfg3QubR06s_kJZkArVEM2dwObrBVAU4%3D" alt="Hiratake" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/10789744/97175095d8f04c0f86225ff47cb98d40/1?token-time=2145916800&token-hash=ubVARikVOg3v7NW6LDhtG-ClE1LTU3I2TJ3js2-5xDs%3D" alt="Naoki Hirayama" width="100"></td>
@@ -122,7 +138,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
</tr></table>
**Last updated:** Fri, 18 Jan 2019 11:58:06 UTC
**Last updated:** Mon, 21 Jan 2019 06:45:06 UTC
<!-- PATREON_END -->
:four_leaf_clover: Copyright

View File

@@ -357,7 +357,6 @@ common/views/components/user-menu.vue:
unblock: "ブロック解除"
push-to-list: "リストに追加"
select-list: "リストを選択してください"
list-pushed: "{user}を{list}に追加しました"
report-abuse: "スパムを報告"
report-abuse-detail: "どのような迷惑行為を行っていますか?"
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
@@ -789,6 +788,8 @@ desktop/views/components/settings.vue:
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
keep-cw: "CW保持"
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "デザインと表示"
customize: "ホームをカスタマイズ"
@@ -1133,15 +1134,22 @@ admin/views/users.vue:
user-not-found: "ユーザーが見つかりません"
lookup: "照会"
reset-password: "パスワードをリセット"
reset-password-confirm: "パスワードをリセットしますか?"
password-updated: "パスワードは現在「{password}」です"
suspend: "凍結"
suspend-confirm: "凍結しますか?"
suspended: "凍結しました"
unsuspend: "凍結の解除"
unsuspend-confirm: "凍結を解除しますか?"
unsuspended: "凍結を解除しました"
verify: "公式アカウントにする"
verify-confirm: "公式アカウントにしますか?"
verified: "公式アカウントにしました"
unverify: "公式アカウントを解除する"
unverify-confirm: "公式アカウントを解除しますか?"
unverified: "公式アカウントを解除しました"
update-remote-user: "リモートユーザー情報の更新"
remote-user-updated: "リモートユーザー情報を更新しました"
users:
title: "ユーザー"
sort:
@@ -1468,6 +1476,7 @@ mobile/views/pages/settings.vue:
notification-position-top: "上"
behavior: "動作"
fetch-on-scroll: "スクロールで自動読み込み"
keep-cw: "CW保持"
note-visibility: "投稿の公開範囲"
default-note-visibility: "デフォルトの公開範囲"
remember-note-visibility: "投稿の公開範囲を記憶する"

View File

@@ -357,7 +357,6 @@ common/views/components/user-menu.vue:
unblock: "ブロック解除"
push-to-list: "リストに追加"
select-list: "リストを選択してください"
list-pushed: "{user}を{list}に追加しました"
report-abuse: "スパムを報告"
report-abuse-detail: "どのような迷惑行為を行っていますか?"
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
@@ -789,6 +788,8 @@ desktop/views/components/settings.vue:
auto-popout-desc: "Pop-out ein offenes Fenster wenn möglich. Diese Einstellung wird im Browser gespeichert."
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
keep-cw: "CW保持"
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "Erscheinungsbild und Anzeige"
customize: "Startseite anpassen"
@@ -1133,15 +1134,22 @@ admin/views/users.vue:
user-not-found: "ユーザーが見つかりません"
lookup: "照会"
reset-password: "パスワードをリセット"
reset-password-confirm: "パスワードをリセットしますか?"
password-updated: "パスワードは現在「{password}」です"
suspend: "凍結"
suspend-confirm: "凍結しますか?"
suspended: "凍結しました"
unsuspend: "凍結の解除"
unsuspend-confirm: "凍結を解除しますか?"
unsuspended: "凍結を解除しました"
verify: "公式アカウントにする"
verify-confirm: "公式アカウントにしますか?"
verified: "公式アカウントにしました"
unverify: "公式アカウントを解除する"
unverify-confirm: "公式アカウントを解除しますか?"
unverified: "公式アカウントを解除しました"
update-remote-user: "リモートユーザー情報の更新"
remote-user-updated: "リモートユーザー情報を更新しました"
users:
title: "ユーザー"
sort:
@@ -1468,6 +1476,7 @@ mobile/views/pages/settings.vue:
notification-position-top: "上"
behavior: "動作"
fetch-on-scroll: "スクロールで自動読み込み"
keep-cw: "CW保持"
note-visibility: "投稿の公開範囲"
default-note-visibility: "デフォルトの公開範囲"
remember-note-visibility: "投稿の公開範囲を記憶する"

View File

@@ -357,10 +357,9 @@ common/views/components/user-menu.vue:
unblock: "Unblock"
push-to-list: "Add to list"
select-list: "Select a list"
list-pushed: "Successfully added {user} to {list}."
report-abuse: "Report abuse"
report-abuse-detail: "どのような迷惑行為を行っていますか?"
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
report-abuse-detail: "What kind of nuisance did you encounter?"
report-abuse-reported: "The issue has been reported to the administrator. Your cooperation is much appreciated."
common/views/components/poll.vue:
vote-to: "Vote for '{}'"
vote-count: "{} votes"
@@ -789,7 +788,9 @@ desktop/views/components/settings.vue:
auto-popout-desc: "If it's possible, pop-out display will be used instead of opening a new window. This setting is stored in your browser."
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"
keep-cw: "Preserve content warning"
keep-cw-desc: "When replying to a post, the same content warning is set by default to the reply, as has been set by the original post."
deck-default: "Use Deck view as the default UI"
display: "Design and display"
customize: "Customize home layout"
wallpaper: "Wallpaper"
@@ -806,8 +807,8 @@ desktop/views/components/settings.vue:
timeline: "Timeline"
show-my-renotes: "Show my renotes in the timeline"
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"
show-local-renotes: "Show renoted local posts in the timelines"
show-maps: "Display a map to show location"
remain-deleted-note: "Continue to show deleted posts"
deck-column-align: "Deck column alignment"
deck-column-align-center: "Center"
@@ -1054,7 +1055,7 @@ admin/views/instance.vue:
disable-registration: "Disable new user registration"
disable-local-timeline: "Disable the Local Timeline"
disable-global-timeline: "Disable global timeline"
disabling-timelines-info: "これらのタイムラインを無効にしても、管理者およびモデレーターは引き続き利用できます。"
disabling-timelines-info: "Even if you disable these timelines, the administrator as well as moderators can use them continually."
invite: "Invite"
save: "Save"
saved: "Saved"
@@ -1126,23 +1127,30 @@ admin/views/drive.vue:
deleted: "Deleted successfully"
mark-as-sensitive: "Mark as 'sensitive'"
unmark-as-sensitive: "Unmark as 'sensitive'"
marked-as-sensitive: "閲覧注意に設定しました"
unmarked-as-sensitive: "閲覧注意を解除しました"
marked-as-sensitive: "Set a sensitive content notice"
unmarked-as-sensitive: "Remove the sensitive content notice"
admin/views/users.vue:
operation: "Operations"
username-or-userid: "Username or user ID"
user-not-found: "User not found"
lookup: "Look up"
reset-password: "Reset password"
reset-password-confirm: "Do you want to reset your password?"
password-updated: "The password is now \"{password}\""
suspend: "Suspend"
suspend-confirm: "Do you want to suspend this account?"
suspended: "Successfully suspended."
unsuspend: "Unsuspend"
unsuspend-confirm: "Are you sure you want to unsuspend this account?"
unsuspended: "The user has successfully unsuspended."
verify: "Verify account"
verify-confirm: "Do you want this to be a verified account?"
verified: "The account is now being verified"
unverify: "Unverify account"
unverify-confirm: "Do you want to remove the 'verified account' designation?"
unverified: "The account is now being unverified"
update-remote-user: "Update information about remote user"
remote-user-updated: "The information regarding the remote user has been updated."
users:
title: "Users"
sort:
@@ -1469,6 +1477,7 @@ mobile/views/pages/settings.vue:
notification-position-top: "Top"
behavior: "Behavior"
fetch-on-scroll: "Endless loading on scroll"
keep-cw: "Preserve content warning"
note-visibility: "Post visibility"
default-note-visibility: "Default visibility"
remember-note-visibility: "Remember post visibility"

View File

@@ -5,18 +5,18 @@ meta:
common:
misskey: "Una ⭐️ del fediverso"
about-title: "Una ⭐️ del fediverso"
about: "Gracias por encontrae Misskey. Misskey es una <b>plataforma descentralizada de microblogging</b> nacida en la Tierra. Gracias a existir dentro del Fediverso (un universo donde se organizan varias plataformas sociales) se encuentra enlazada mutuamente con otras plataformas sociales. ¿Por què no te tomas un respiro del caos de la ciudad y te sumerges es una nueva manera de entender Internet?"
about: "Gracias por encontrar Misskey. Misskey es una <b>plataforma descentralizada de microblogging</b> nacida en la Tierra. Porque el servicio existe dentro del Fediverso (un universo donde se organizan varias plataformas sociales), se encuentra enlazado mutuamente con otras plataformas sociales. ¿Por qué no te tomas un respiro del caos de la ciudad y te sumerges es una nueva manera de entender Internet?"
intro:
title: "Misskeyって?"
about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
features: "特徴"
rich-contents: "投稿"
rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
reaction: "リアクション"
title: "¿Misskey?"
about: "Misskey es un <b>Servicio de red social descentralizada de microblogging</b> de código abierto. Contiene una interfaz de usuario altamente personalizable, reacciones a posts, almacenamiento para poder manejar archivos y otras funciones avanzadas. Además de conectarse con la red llamada Fediverso, puede intercambiar mensajes con otras redes sociales. Por ejemplo, si contribuyes con algo, esa contribución es transmitida no sólo a Misskey sino a otras redes sociales. Imagina que se parece a transmitir una onda de radio de un planeta a otro."
features: "Características"
rich-contents: "Posts"
rich-contents-desc: "Escribe sobre tus pensamientos, eventos, todo lo que quieras compartir. Si es necesario, puedes usar varias sintaxis, decorar tus posts y añadir tus imágenes favoritas, archivos de viddeo y encuestas."
reaction: "Reacciones"
reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
ui: "インターフェース"
ui: "Interfaz"
ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
drive: "ドライブ"
drive: "Drive"
drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんかもしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんかMisskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
adblock:
@@ -27,7 +27,7 @@ common:
do-not-copy-paste: "Por favor no copies código aquí. Tu cuenta puede resultar comprometida."
load-more: "もっと読み込む"
enter-password: "パスワードを入力してください"
2fa: "二段階認証"
2fa: "Autenticación de dos factores"
got-it: "¡Listo!"
customization-tips:
title: "Consejos de personalización"
@@ -54,8 +54,8 @@ common:
years_ago: "Hace {} año(s)"
month-and-day: "{day} de {month}"
trash: "Papelera"
drive: "ドライブ"
messaging: "トーク"
drive: "Drive"
messaging: "Conversación"
weekday-short:
sunday: "domingo"
monday: "lunes"
@@ -84,16 +84,16 @@ common:
rip: "RIP"
pudding: "Chafado"
note-visibility:
public: "公開"
home: "ホーム"
home-desc: "ホームタイムラインにのみ公開"
followers: "フォロワー"
followers-desc: "自分のフォロワーにのみ公開"
specified: "ダイレクト"
specified-desc: "指定したユーザーにのみ公開"
local-public: "公開 (ローカルのみ)"
local-home: "ホーム (ローカルのみ)"
local-followers: "フォロワー (ローカルのみ)"
public: "Público"
home: "Inicio"
home-desc: "Sólo en el timeline de inicio"
followers: "Seguidores"
followers-desc: "Sólo para tus seguidores"
specified: "Mensaje directo"
specified-desc: "Sólo para ciertos usuarios"
local-public: "Público (sólo local)"
local-home: "Inicio (sólo local)"
local-followers: "Seguidores (sólo local)"
note-placeholders:
a: "¿Qué haces?"
b: "¿Qué está pasando?"
@@ -122,9 +122,9 @@ common:
this-setting-is-this-device-only: "このデバイスのみ"
use-os-default-emojis: "OS標準の絵文字を使用"
line-width: "線の太さ"
line-width-thin: "細い"
line-width-thin: "Fino"
line-width-normal: "普通"
line-width-thick: "太い"
line-width-thick: "Grueso"
hide-password: "パスワードを隠す"
show-password: "パスワードを表示する"
do-not-use-in-production: "Esto está en desarrollo, no usarlo para producción."
@@ -171,7 +171,7 @@ common:
hashtags: "Etiquetas"
dev: "アプリの作成に失敗しました。再度お試しください。"
ai-chan-kawaii: "藍ちゃかわいい"
you: "あなた"
you: ""
auth/views/form.vue:
share-access: "<i>{name}</i>があなたのアカウントにアクセスすることを許可しますか?"
permission-ask: "La aplicación requiere los siguientes permisos:"
@@ -267,48 +267,48 @@ common/views/components/media-banner.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
common/views/components/theme.vue:
theme: "テーマ"
theme: "Tema"
light-theme: "非ダークモード時に使用するテーマ"
dark-theme: "ダークモード時に使用するテーマ"
light-themes: "明るいテーマ"
dark-themes: "暗いテーマ"
install-a-theme: "テーマのインストール"
theme-code: "テーマコード"
install: "インストール"
light-themes: "Tema claro"
dark-themes: "Tema oscuro"
install-a-theme: "Instalar tema"
theme-code: "Código del tema"
install: "Instalación"
installed: "「{}」をインストールしました"
create-a-theme: "テーマの作成"
save-created-theme: "テーマを保存"
primary-color: "プライマリ カラー"
secondary-color: "セカンダリ カラー"
primary-color: "Color primario"
secondary-color: "Color secundario"
text-color: "文字色"
base-theme: "ベーステーマ"
base-theme-light: "Light"
base-theme-dark: "Dark"
base-theme: "Tema base"
base-theme-light: "Claro"
base-theme-dark: "Oscuro"
find-more-theme: "その他のテーマを入手"
theme-name: "テーマ名"
preview-created-theme: "プレビュー"
theme-name: "Nombre del tema"
preview-created-theme: "Vista previa"
invalid-theme: "テーマが正しくありません。"
already-installed: "既にそのテーマはインストールされています。"
saved: "保存しました"
manage-themes: "テーマの管理"
builtin-themes: "標準テーマ"
my-themes: "マイテーマ"
installed-themes: "インストールされたテーマ"
my-themes: "Mis temas"
installed-themes: "Temas instalados"
select-theme: "テーマを選択してください"
uninstall: "アンインストール"
uninstall: "Desinstalar"
uninstalled: "「{}」をアンインストールしました"
author: "作者"
desc: "説明"
export: "エクスポート"
import: "インポート"
export: "Exportar"
import: "Importar"
import-by-code: "またはコードをペースト"
theme-name-required: "テーマ名は必須です。"
common/views/components/cw-button.vue:
hide: "隠す"
show: "もっと見る"
chars: "{count}文字"
files: "{count}ファイル"
poll: "アンケート"
show: "Mostrar"
chars: "{count} letras"
files: "{count} archivos"
poll: "Encuesta"
common/views/components/messaging.vue:
search-user: "Encuentra un usuario"
you: "Tu"
@@ -338,7 +338,7 @@ common/views/components/nav.vue:
develop: "Desarrolladores"
feedback: "Opiniones"
common/views/components/note-menu.vue:
mention: "メンション"
mention: "Menciones"
detail: "Detalles"
copy-content: "内容をコピー"
copy-link: "Copiar enlace"
@@ -350,14 +350,13 @@ common/views/components/note-menu.vue:
delete-confirm: "¿Seguro que quieres borrar la publicación?"
remote: "Ver el original"
common/views/components/user-menu.vue:
mention: "メンション"
mute: "ミュート"
mention: "Menciones"
mute: "Silenciar"
unmute: "ミュート解除"
block: "ブロック"
block: "Bloquear"
unblock: "ブロック解除"
push-to-list: "リストに追加"
select-list: "リストを選択してください"
list-pushed: "{user}を{list}に追加しました"
report-abuse: "スパムを報告"
report-abuse-detail: "どのような迷惑行為を行っていますか?"
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
@@ -377,15 +376,15 @@ common/views/components/poll-editor.vue:
common/views/components/reaction-picker.vue:
choose-reaction: "Escoge una reacción"
common/views/components/emoji-picker.vue:
custom-emoji: "カスタム絵文字"
people: ""
animals-and-nature: "動物&自然"
food-and-drink: "食べ物&飲み物"
activity: "アクティビティ"
travel-and-places: "場所"
objects: ""
symbols: "記号"
flags: ""
custom-emoji: "Personalizados"
people: "Gente"
animals-and-nature: "Naturaleza"
food-and-drink: "Comida y bebida"
activity: "Actividad"
travel-and-places: "Viajes y lugares"
objects: "Objetos"
symbols: "Símbolos"
flags: "Países"
common/views/components/signin.vue:
username: "Usuario"
password: "Contraseña"
@@ -394,8 +393,8 @@ common/views/components/signin.vue:
signin: "Entra"
or: "O"
signin-with-twitter: "Ingresar con Twitter"
signin-with-github: "GitHubでログイン"
signin-with-discord: "Discordでログイン"
signin-with-github: "Ingresar con Github"
signin-with-discord: "Ingresar con Discord"
login-failed: "Autenticación fallida. Asegúrate de haber usado el nombre de usuario y contraseña correctos."
common/views/components/signup.vue:
invitation-code: "Código de invitación"
@@ -478,17 +477,17 @@ common/views/components/language-settings.vue:
specify-language: "言語を指定"
info: "変更はページの再度読み込み後に反映されます。"
common/views/components/profile-editor.vue:
title: "プロフィール"
name: "名前"
account: "アカウント"
title: "Perfil"
name: "Nombre"
account: "Cuenta"
location: "場所"
description: "自己紹介"
language: "言語"
birthday: "誕生日"
avatar: "アイコン"
banner: "バナー"
is-cat: "このアカウントはCatです"
is-bot: "このアカウントはBotです"
avatar: "Avatar"
banner: "Banner"
is-cat: "Esta cuenta es un gato"
is-bot: "Esta cuenta es un bot"
is-locked: "フォローを承認制にする"
careful-bot: "Botからのフォローだけ承認制にする"
auto-accept-followed: "フォローしているユーザーからのフォローを自動承認する"
@@ -499,11 +498,11 @@ common/views/components/profile-editor.vue:
uploading: "アップロード中"
upload-failed: "アップロードに失敗しました"
email: "メール設定"
email-address: "メールアドレス"
email-address: "Correo electrónico"
email-verified: "メールアドレスが確認されました"
email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。"
common/views/components/user-list-editor.vue:
users: "ユーザー"
users: "Usuarios"
rename: "リスト名を変更"
delete: "リストを削除"
remove-user: "このリストから削除"
@@ -568,7 +567,7 @@ common/views/pages/follow.vue:
following: "Siguiendo"
follow: "Seguir"
request-pending: "Solicitud pendiente"
follow-processing: "フォロー処理中"
follow-processing: "Solicitud en proceso"
follow-request: "Solicitar suscripción"
desktop:
banner-crop-title: "Corta la parte que aparece como un banner"
@@ -591,7 +590,7 @@ desktop/views/components/activity.vue:
title: "Actividad"
toggle: "Alternar vistas"
desktop/views/components/calendar.vue:
title: "{year} {month}"
title: "{year} / {month}"
prev: "Mes anterior"
next: "Próximo mes"
go: "Click para navegar"
@@ -702,7 +701,7 @@ desktop/views/components/note-detail.vue:
desktop/views/components/note.vue:
reply: "返信"
renote: "Renote"
add-reaction: "リアクション"
add-reaction: "Reacción"
undo-reaction: "リアクション解除"
detail: "詳細"
private: "この投稿は非公開です"
@@ -760,23 +759,23 @@ desktop/views/components/renote-form.vue:
desktop/views/components/renote-form-window.vue:
title: "¿Seguro qué quieres volver a publicarlo?"
desktop/views/pages/user-following-or-followers.vue:
following: "{user}のフォロー"
followers: "{user}のフォロワー"
following: "{user} sigue a"
followers: "Seguidores de {user}"
desktop/views/components/settings-window.vue:
settings: "Configuración"
desktop/views/components/settings.vue:
profile: "Perfil"
notification: "Notificación"
apps: "Aplicaciones"
tags: "ハッシュタグ"
mute-and-block: "ミュート/ブロック"
blocking: "ブロック"
tags: "Hashtags"
mute-and-block: "Silenciar/Bloquear"
blocking: "Bloquear"
security: "Seguridad"
signin: "Historial de inicios de sesión"
password: "Contraseña"
other: "Otros"
license: "Licencia"
theme: "テーマ"
theme: "Tema"
behaviour: "Acciones"
fetch-on-scroll: "Desplazamiento infinito"
fetch-on-scroll-desc: "Cuando te deslizas al final de la página nuevo contenido se carga automáticamente."
@@ -789,6 +788,8 @@ desktop/views/components/settings.vue:
auto-popout-desc: "Muestra una ventana emergente si es posible. Esta configuración depende del navegador."
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
keep-cw: "CW保持"
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "Diseño y pantalla"
customize: "Personaliza la página principal"
@@ -811,30 +812,30 @@ desktop/views/components/settings.vue:
remain-deleted-note: "削除された投稿を表示し続ける"
deck-column-align: "デッキのカラムの配置"
deck-column-align-center: "中央"
deck-column-align-left: ""
deck-column-align-flexible: "フレキシブル"
deck-column-align-left: "Izquierda"
deck-column-align-flexible: "Flexible"
deck-column-width: "デッキのカラムの幅"
deck-column-width-narrow: "狭"
deck-column-width-narrower: "やや狭"
deck-column-width-normal: "普通"
deck-column-width-wider: "やや広"
deck-column-width-wide: "広"
sound: "サウンド"
sound: "Sonidos"
enable-sounds: "サウンドを有効にする"
enable-sounds-desc: "投稿やメッセージを送受信したときなどにサウンドを再生します。この設定はブラウザに記憶されます。"
volume: "ボリューム"
test: "テスト"
cache: "キャッシュ"
clean-cache: "クリーンアップ"
volume: "Volúmen"
test: "Prueba"
cache: "Caché"
clean-cache: "Borrar caché"
cache-warn: "クリーンアップを行うと、ブラウザに記憶されたアカウント情報のキャッシュ、書きかけの投稿・返信・メッセージ、およびその他のデータ(設定情報含む)が削除されます。クリーンアップを行った後はページを再度読み込みする必要があります。"
cache-cleared: "キャッシュを削除しました"
cache-cleared-desc: "ページを再度読み込みしてください。"
about: "Misskeyについて"
operator: "このサーバーの運営者"
update: "Misskey Update"
version: "バージョン:"
latest-version: "最新のバージョン:"
update-checking: "アップデートを確認中"
update: "Actualizar Misskey"
version: "Versión"
latest-version: "Última versión"
update-checking: "Chequeando actualización"
do-update: "Chequear por actualizaciones"
update-settings: "Configuración avanzada"
prevent-update: "Posponer actualizaciones (no recomendado)"
@@ -852,9 +853,9 @@ desktop/views/components/settings.vue:
task-manager: "Navegador de tareas"
third-parties: "Servicios externos"
navbar-position: "ナビゲーションバーの位置"
navbar-position-top: ""
navbar-position-left: ""
navbar-position-right: ""
navbar-position-top: "Arriba"
navbar-position-left: "Izquierda"
navbar-position-right: "Derecha"
desktop/views/components/settings.2fa.vue:
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
detail: "Ver detalles..."
@@ -867,7 +868,7 @@ desktop/views/components/settings.2fa.vue:
enter-password: "Escribe una contraseña"
authenticator: "Primero, necesitas instalar Google Authenticator en tu dispositivo:"
howtoinstall: "Cómo instalar"
token: "トークン"
token: "Token"
scan: "Luego, escanea el código QR:"
done: "Por favor ingresa el token mostrado en tu dispositivo:"
submit: "Enviar"
@@ -882,7 +883,7 @@ common/views/components/api-settings.vue:
token: "Token:"
enter-password: "パスワードを入力してください"
console:
title: "APIコンソール"
title: "Consola API"
endpoint: "エンドポイント"
parameter: "パラメータ"
credential-info: "「i」パラメータは自動で付与されます。"
@@ -896,9 +897,9 @@ common/views/components/drive-settings.vue:
in-use: "使用中"
stats: "統計"
common/views/components/mute-and-block.vue:
mute-and-block: "ミュートとブロック"
mute: "ミュート"
block: "ブロック"
mute-and-block: "Silenciar y bloquear"
mute: "Silenciar"
block: "Bloquear"
no-muted-users: "ミュートしているユーザーはいません"
no-blocked-users: "ブロックしているユーザーはいません"
word-mute: "ワードミュート"
@@ -917,26 +918,26 @@ desktop/views/components/sub-note-content.vue:
private: "この投稿は非公開です"
deleted: "この投稿は削除されました"
media-count: "{}つのメディア"
poll: "アンケート"
poll: "Encuesta"
desktop/views/components/settings.tags.vue:
title: "タグ"
title: "Etiqueta"
query: "クエリ (省略可)"
add: "追加"
save: "保存"
desktop/views/components/taskmanager.vue:
title: "タスクマネージャ"
desktop/views/components/timeline.vue:
home: "ホーム"
local: "ローカル"
hybrid: "ソーシャル"
global: "グローバル"
home: "Inicio"
local: "Local"
hybrid: "Social"
global: "Global"
mentions: "あなた宛て"
messages: "メッセージ"
list: "リスト"
hashtag: "ハッシュタグ"
list: "Listas"
hashtag: "Hashtags"
add-tag-timeline: "ハッシュタグを追加"
add-list: "リストを追加"
list-name: "リスト名"
list-name: "Nombre de lista"
desktop/views/components/ui.header.vue:
welcome-back: "Bienvenido/a de vuelta,"
adjective: "-san"
@@ -970,35 +971,35 @@ desktop/views/components/user-lists-window.vue:
list-name: "Nombre de lista"
desktop/views/components/user-preview.vue:
notes: "Publicaciones"
following: "フォロー"
followers: "フォロワー"
following: "Sigue"
followers: "Seguidores"
desktop/views/components/users-list.vue:
all: "すべて"
all: "Todo"
iknow: "知り合い"
fetching: "読み込んでいます"
desktop/views/components/users-list-item.vue:
followed: "フォローされています"
desktop/views/components/window.vue:
popout: "ポップアウト"
close: "閉じる"
close: "Cerrar"
admin/views/index.vue:
dashboard: "ダッシュボード"
instance: "インスタンス"
dashboard: "Panel de control"
instance: "Instancia"
emoji: "カスタム絵文字"
moderators: "モデレーター"
users: "ユーザー"
moderators: "Moderadores"
users: "Usuarios"
federation: "連合"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
hashtags: "Hashtags"
abuse: "スパム報告"
back-to-misskey: "Misskeyに戻る"
back-to-misskey: "Volver a Misskey"
admin/views/dashboard.vue:
dashboard: "ダッシュボード"
accounts: "アカウント"
dashboard: "Panel de Control"
accounts: "Cuenta"
notes: "投稿"
drive: "ドライブ"
instances: "インスタンス"
this-instance: "このインスタンス"
drive: "Drive"
instances: "Instancias"
this-instance: "Esta instancia"
federated: "連合"
admin/views/abuse.vue:
title: "スパム報告"
@@ -1007,10 +1008,10 @@ admin/views/abuse.vue:
details: "詳細"
remove-report: "削除"
admin/views/instance.vue:
instance: "インスタンス"
instance-name: "インスタンス名"
instance: "Instancia"
instance-name: "Nombre de la instancia"
instance-description: "インスタンスの紹介"
host: "ホスト"
host: "Host"
banner-url: "バナー画像URL"
error-image-url: "エラー画像URL"
languages: "インスタンスの対象言語"
@@ -1028,7 +1029,7 @@ admin/views/instance.vue:
recaptcha-info: "reCAPTCHAを有効にする場合、reCAPTCHAトークンを取得する必要があります。https://www.google.com/recaptcha/intro/ にアクセスしてトークンを取得してください。"
enable-recaptcha: "reCAPTCHAを有効にする"
recaptcha-site-key: "reCAPTCHA site key"
recaptcha-secret-key: "reCAPTCHA secret key"
recaptcha-secret-key: "clave secreta reCAPTCHA"
twitter-integration-config: "Twitter連携の設定"
twitter-integration-info: "コールバックURLは {url} に設定します。"
enable-twitter-integration: "Twitter連携を有効にする"
@@ -1061,7 +1062,7 @@ admin/views/instance.vue:
enable-external-user-recommendation: "外部ユーザーレコメンデーションを有効にする"
external-user-recommendation-engine: "エンジン"
external-user-recommendation-engine-desc: "例: https://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-misskey-api.cgi?{{host}}+{{user}}+{{limit}}+{{offset}}"
external-user-recommendation-timeout: "タイムアウト"
external-user-recommendation-timeout: "Tiempo de espera"
external-user-recommendation-timeout-desc: "ミリ秒単位 (例: 300000)"
email-config: "メールサーバーの設定"
email-config-info: "メールアドレス確認やパスワードリセットの際に使われます。"
@@ -1069,10 +1070,10 @@ admin/views/instance.vue:
email: "メールアドレス"
smtp-secure: "SMTP接続に暗黙的なSSL/TLSを使用する"
smtp-secure-info: "STARTTLS使用時はオフにします。"
smtp-host: "SMTPホスト"
smtp-port: "SMTPポート"
smtp-user: "SMTPユーザー"
smtp-pass: "SMTPパスワード"
smtp-host: "Host SMTP"
smtp-port: "Puerto SMTP"
smtp-user: "Usuario SMTP"
smtp-pass: "Contraseña SMTP"
serviceworker-config: "ServiceWorker"
enable-serviceworker: "ServiceWorkerを有効にする"
serviceworker-info: "プッシュ通知を行うには有効する必要があります。"
@@ -1080,14 +1081,14 @@ admin/views/instance.vue:
vapid-privatekey: "VAPID秘密鍵"
vapid-info: "ServiceWorkerを有効にする場合、VAPIDキーペアを生成する必要があります。シェルで次のようにします:"
admin/views/charts.vue:
title: "チャート"
per-day: "1日ごと"
per-hour: "1時間ごと"
federation: "フェデレーション"
title: "Gráficos"
per-day: "Por día"
per-hour: "Por hora"
federation: "Federación"
notes: "投稿"
users: "ユーザー"
drive: "ドライブ"
network: "ネットワーク"
users: "Usuarios"
drive: "Drive"
network: "Red"
charts:
federation-instances: "インスタンスの増減"
federation-instances-total: "インスタンスの積算"
@@ -1111,16 +1112,16 @@ admin/views/drive.vue:
file-not-found: "ファイルが見つかりません"
lookup: "照会"
sort:
title: "ソート"
title: "Ordenar"
createdAtAsc: "アップロード日時が古い順"
createdAtDesc: "アップロード日時が新しい順"
sizeAsc: "サイズが小さい順"
sizeDesc: "サイズが大きい順"
origin:
title: "オリジン"
combined: "ローカル+リモート"
local: "ローカル"
remote: "リモート"
combined: "Local+Remoto"
local: "Local"
remote: "Remoto"
delete: "削除"
deleted: "削除しました"
mark-as-sensitive: "閲覧注意に設定"
@@ -1133,15 +1134,22 @@ admin/views/users.vue:
user-not-found: "ユーザーが見つかりません"
lookup: "照会"
reset-password: "パスワードをリセット"
reset-password-confirm: "パスワードをリセットしますか?"
password-updated: "パスワードは現在「{password}」です"
suspend: "凍結"
suspend-confirm: "凍結しますか?"
suspended: "凍結しました"
unsuspend: "凍結の解除"
unsuspend-confirm: "凍結を解除しますか?"
unsuspended: "凍結を解除しました"
verify: "公式アカウントにする"
verify-confirm: "公式アカウントにしますか?"
verified: "公式アカウントにしました"
unverify: "公式アカウントを解除する"
unverify-confirm: "公式アカウントを解除しますか?"
unverified: "公式アカウントを解除しました"
update-remote-user: "リモートユーザー情報の更新"
remote-user-updated: "リモートユーザー情報を更新しました"
users:
title: "ユーザー"
sort:
@@ -1468,6 +1476,7 @@ mobile/views/pages/settings.vue:
notification-position-top: "上"
behavior: "動作"
fetch-on-scroll: "スクロールで自動読み込み"
keep-cw: "CW保持"
note-visibility: "投稿の公開範囲"
default-note-visibility: "デフォルトの公開範囲"
remember-note-visibility: "投稿の公開範囲を記憶する"

View File

@@ -350,17 +350,16 @@ common/views/components/note-menu.vue:
delete-confirm: "Supprimer cette publication ?"
remote: "Afficher la note originale"
common/views/components/user-menu.vue:
mention: "メンション"
mute: "ミュート"
unmute: "ミュート解除"
block: "ブロック"
unblock: "ブロック解除"
push-to-list: "リストに追加"
select-list: "リストを選択してください"
list-pushed: "{user}を{list}に追加しました"
report-abuse: "スパムを報告"
report-abuse-detail: "どのような迷惑行為を行っていますか?"
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
mention: "Mention"
mute: "Silencier"
unmute: "Enlever la sourdine"
block: "Bloquer"
unblock: "Débloquer"
push-to-list: "Ajouter à une liste"
select-list: "Sélectionnez une liste"
report-abuse: "Signaler un abus"
report-abuse-detail: "Détail du signalement"
report-abuse-reported: "Transmit à ladministrateur. Merci de votre collaboration."
common/views/components/poll.vue:
vote-to: "Voter pour '{}'"
vote-count: "{} votes"
@@ -491,7 +490,7 @@ common/views/components/profile-editor.vue:
is-bot: "Ce compte est un Bot"
is-locked: "Demandes dabonnements requièrent lapprobation"
careful-bot: "Les demandes dabonnements venant de Bots requièrent lapprobation"
auto-accept-followed: "フォローしているユーザーからのフォローを自動承認する"
auto-accept-followed: "Accepter automatiquement les demandes dabonnement venant des gens que vous suivez"
advanced: "Avancé"
privacy: "Vie privée"
save: "Mettre à jour le profil"
@@ -789,6 +788,8 @@ desktop/views/components/settings.vue:
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
deck-nav: "Deck sans tansitions"
deck-nav-desc: "Vous obtenez une colonne temporaire sans transitions dans la page pendant la navigation, lors de lutilisation du Deck."
keep-cw: "Maintenir l'avertissement de contenu"
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
deck-default: "Utiliser le Deck comme IU par défaut"
display: "Affichage et design"
customize: "Personnaliser l'Accueil"
@@ -990,7 +991,7 @@ admin/views/index.vue:
federation: "Fédération"
announcements: "Annonces"
hashtags: "Hashtags"
abuse: "スパム報告"
abuse: "Abus"
back-to-misskey: "Retour vers Misskey"
admin/views/dashboard.vue:
dashboard: "Tableau de bord"
@@ -1001,11 +1002,11 @@ admin/views/dashboard.vue:
this-instance: "Cette instance"
federated: "Fédérées"
admin/views/abuse.vue:
title: "スパム報告"
target: "対象"
reporter: "報告者"
details: "詳細"
remove-report: "削除"
title: "Abus"
target: "Cible"
reporter: "Signalé par"
details: "Détails"
remove-report: "Supprimer"
admin/views/instance.vue:
instance: "Instance"
instance-name: "Nom de linstance"
@@ -1052,7 +1053,7 @@ admin/views/instance.vue:
max-note-text-length: "Nombre maximal de caractères pour les messages"
disable-registration: "Désactiver les inscriptions"
disable-local-timeline: "Désactiver le fil local"
disable-global-timeline: "グローバルタイムラインを無効にする"
disable-global-timeline: "Désactiver le fil global"
disabling-timelines-info: "これらのタイムラインを無効にしても、管理者およびモデレーターは引き続き利用できます。"
invite: "Inviter"
save: "Sauvegarder"
@@ -1106,10 +1107,10 @@ admin/views/charts.vue:
network-time: "Temps de réponse"
network-usage: "Traffic"
admin/views/drive.vue:
operation: "操作"
fileid-or-url: "ファイルIDまたはファイルURL"
file-not-found: "ファイルが見つかりません"
lookup: "照会"
operation: "Actions"
fileid-or-url: "ID du fichier ou URL"
file-not-found: "Fichier non trouvé"
lookup: "Recherche"
sort:
title: "Tri"
createdAtAsc: "Âge - Du plus ancien"
@@ -1125,23 +1126,30 @@ admin/views/drive.vue:
deleted: "Supprimé"
mark-as-sensitive: "Marquer comme sensible"
unmark-as-sensitive: "Ne pas marquer comme sensible"
marked-as-sensitive: "閲覧注意に設定しました"
unmarked-as-sensitive: "閲覧注意を解除しました"
marked-as-sensitive: "Marqué comme sensible"
unmarked-as-sensitive: "Marqué comme non sensible"
admin/views/users.vue:
operation: "Actions"
username-or-userid: "Nom dutilisateur·rice ou ID utilisateur"
user-not-found: "Utilisateur non trouvé"
lookup: "Recherche"
reset-password: "Réinitialiser mot de passe"
reset-password-confirm: "パスワードをリセットしますか?"
password-updated: "Le mot de passe est « {password} »"
suspend: "Suspendre"
suspend-confirm: "Désirez-vous suspendre ce compte ?"
suspended: "Suspendu avec succès."
unsuspend: "Suspension levée"
unsuspend-confirm: "Souhaiteriez-vous ne plus suspendre ce compte ?"
unsuspended: "La suspension de lutilisateur a été levée avec succès"
verify: "Vérification du compte"
verify-confirm: "Souhaiteriez-vous rendre votre compte comme étant un compte vérifié ?"
verified: "Le compte a été vérifié"
unverify: "Enlever la vérification du compte"
unverify-confirm: "公式アカウントを解除しますか?"
unverified: "Ce compte n'est plus vérifié"
update-remote-user: "Mettre à jour les informations de lutilisateur·rice distant·e"
remote-user-updated: "Les informations de lutilisateur·rice distant·e ont étés mis à jour"
users:
title: "Utilisateurs"
sort:
@@ -1253,7 +1261,7 @@ desktop/views/pages/user/user.photos.vue:
no-photos: "Pas de photos"
desktop/views/pages/user/user.profile.vue:
follows-you: "Vous suit"
menu: "メニュー"
menu: "Menu"
desktop/views/pages/user/user.header.vue:
posts: "Notes"
following: "Suit"
@@ -1468,6 +1476,7 @@ mobile/views/pages/settings.vue:
notification-position-top: "en haut"
behavior: "Comportement"
fetch-on-scroll: "Chargement lors du défilement"
keep-cw: "Garder l'avertissement de contenu"
note-visibility: "Visibilité de la publication"
default-note-visibility: "Visibilité par défaut"
remember-note-visibility: "Se souvenir du mode de visibilité de la publication"

View File

@@ -357,7 +357,6 @@ common/views/components/user-menu.vue:
unblock: "ブロック解除"
push-to-list: "リストに追加"
select-list: "リストを選択してください"
list-pushed: "{user}を{list}に追加しました"
report-abuse: "スパムを報告"
report-abuse-detail: "どのような迷惑行為を行っていますか?"
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
@@ -789,6 +788,8 @@ desktop/views/components/settings.vue:
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
keep-cw: "CW保持"
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "デザインと表示"
customize: "ホームをカスタマイズ"
@@ -1133,15 +1134,22 @@ admin/views/users.vue:
user-not-found: "ユーザーが見つかりません"
lookup: "照会"
reset-password: "パスワードをリセット"
reset-password-confirm: "パスワードをリセットしますか?"
password-updated: "パスワードは現在「{password}」です"
suspend: "凍結"
suspend-confirm: "凍結しますか?"
suspended: "凍結しました"
unsuspend: "凍結の解除"
unsuspend-confirm: "凍結を解除しますか?"
unsuspended: "凍結を解除しました"
verify: "公式アカウントにする"
verify-confirm: "公式アカウントにしますか?"
verified: "公式アカウントにしました"
unverify: "公式アカウントを解除する"
unverify-confirm: "公式アカウントを解除しますか?"
unverified: "公式アカウントを解除しました"
update-remote-user: "リモートユーザー情報の更新"
remote-user-updated: "リモートユーザー情報を更新しました"
users:
title: "ユーザー"
sort:
@@ -1468,6 +1476,7 @@ mobile/views/pages/settings.vue:
notification-position-top: "上"
behavior: "動作"
fetch-on-scroll: "スクロールで自動読み込み"
keep-cw: "CW保持"
note-visibility: "投稿の公開範囲"
default-note-visibility: "デフォルトの公開範囲"
remember-note-visibility: "投稿の公開範囲を記憶する"

View File

@@ -388,7 +388,6 @@ common/views/components/user-menu.vue:
unblock: "ブロック解除"
push-to-list: "リストに追加"
select-list: "リストを選択してください"
list-pushed: "{user}を{list}に追加しました"
report-abuse: "スパムを報告"
report-abuse-detail: "どのような迷惑行為を行っていますか?"
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
@@ -731,10 +730,6 @@ desktop/views/components/drive.vue:
upload: "ファイルをアップロード"
url-upload: "URLからアップロード"
desktop/views/components/media-image.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
desktop/views/components/media-video.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
@@ -885,6 +880,8 @@ desktop/views/components/settings.vue:
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
keep-cw: "CW保持"
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "デザインと表示"
@@ -979,6 +976,10 @@ desktop/views/components/settings.2fa.vue:
failed: "設定に失敗しました。トークンに誤りがないかご確認ください。"
info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。"
common/views/components/media-image.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
common/views/components/api-settings.vue:
intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。"
caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
@@ -1265,15 +1266,22 @@ admin/views/users.vue:
user-not-found: "ユーザーが見つかりません"
lookup: "照会"
reset-password: "パスワードをリセット"
reset-password-confirm: "パスワードをリセットしますか?"
password-updated: "パスワードは現在「{password}」です"
suspend: "凍結"
suspend-confirm: "凍結しますか?"
suspended: "凍結しました"
unsuspend: "凍結の解除"
unsuspend-confirm: "凍結を解除しますか?"
unsuspended: "凍結を解除しました"
verify: "公式アカウントにする"
verify-confirm: "公式アカウントにしますか?"
verified: "公式アカウントにしました"
unverify: "公式アカウントを解除する"
unverify-confirm: "公式アカウントを解除しますか?"
unverified: "公式アカウントを解除しました"
update-remote-user: "リモートユーザー情報の更新"
remote-user-updated: "リモートユーザー情報を更新しました"
users:
title: "ユーザー"
sort:
@@ -1485,10 +1493,6 @@ mobile/views/components/drive.file-detail.vue:
mark-as-sensitive: "閲覧注意に設定"
unmark-as-sensitive: "閲覧注意を解除"
mobile/views/components/media-image.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
mobile/views/components/media-video.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
@@ -1665,6 +1669,7 @@ mobile/views/pages/settings.vue:
notification-position-top: "上"
behavior: "動作"
fetch-on-scroll: "スクロールで自動読み込み"
keep-cw: "CW保持"
note-visibility: "投稿の公開範囲"
default-note-visibility: "デフォルトの公開範囲"
remember-note-visibility: "投稿の公開範囲を記憶する"

View File

@@ -357,7 +357,6 @@ common/views/components/user-menu.vue:
unblock: "ブロック解除"
push-to-list: "リストに追加"
select-list: "リストを選択してください"
list-pushed: "{user}を{list}に追加しました"
report-abuse: "スパムを報告"
report-abuse-detail: "どのような迷惑行為を行っていますか?"
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
@@ -789,6 +788,8 @@ desktop/views/components/settings.vue:
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトすんで。この設定はブラウザに記憶されんで。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使うとるとき、ナビゲーションが発生するときにページ移動せんで、一時的なカラムで受けれるようにするで"
keep-cw: "CW保持"
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "見た感じ"
customize: "ホームをカスタマイズ"
@@ -1133,15 +1134,22 @@ admin/views/users.vue:
user-not-found: "ユーザーが見つからへん!"
lookup: "照会"
reset-password: "パスワードをリセット"
reset-password-confirm: "パスワードをリセットしますか?"
password-updated: "パスワードは現在「{password} 」やで"
suspend: "凍結"
suspend-confirm: "凍結しますか?"
suspended: "凍結しました"
unsuspend: "凍結の解除"
unsuspend-confirm: "凍結を解除しますか?"
unsuspended: "凍結を解除しました"
verify: "公式アカウントにする"
verify-confirm: "公式アカウントにしますか?"
verified: "公式アカウントにしました"
unverify: "公式アカウントを解除する"
unverify-confirm: "公式アカウントを解除しますか?"
unverified: "公式アカウントを解除しました"
update-remote-user: "リモートユーザー情報の更新"
remote-user-updated: "リモートユーザー情報を更新しました"
users:
title: "ユーザー"
sort:
@@ -1468,6 +1476,7 @@ mobile/views/pages/settings.vue:
notification-position-top: "キタの方"
behavior: "動き"
fetch-on-scroll: "スクロールしたらもっと見せてや"
keep-cw: "CW保持"
note-visibility: "投稿の公開範囲"
default-note-visibility: "もとからの公開範囲"
remember-note-visibility: "投稿の公開範囲おぼえといて"

View File

@@ -27,7 +27,7 @@ common:
do-not-copy-paste: "여기에 코드를 입력하거나 붙여넣지 마십시오. 계정이 무단으로 사용될 수 있습니다."
load-more: "더보기"
enter-password: "비밀번호를 입력하여 주십시오"
2fa: "二段階認証"
2fa: "2단계 인증"
got-it: "알겠습니다"
customization-tips:
title: "커스터마이징 도움말"
@@ -350,17 +350,16 @@ common/views/components/note-menu.vue:
delete-confirm: "이 글을 삭제하시겠습니까?"
remote: "글 원본 보기"
common/views/components/user-menu.vue:
mention: "メンション"
mute: "ミュート"
unmute: "ミュート解除"
block: "ブロック"
unblock: "ブロック解除"
push-to-list: "リストに追加"
select-list: "リストを選択してください"
list-pushed: "{user}を{list}に追加しました"
report-abuse: "スパムを報告"
report-abuse-detail: "どのような迷惑行為を行っていますか?"
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
mention: "멘션"
mute: "뮤트"
unmute: "뮤트 해제"
block: "차단"
unblock: "차단 해제"
push-to-list: "리스트에 추가"
select-list: "리스트를 선택하여 주십시오"
report-abuse: "스팸 신고"
report-abuse-detail: "어떤 스팸 행위를 하고 있습니까?"
report-abuse-reported: "관리자에게 보고되었습니다. 협조해주셔서 감사합니다."
common/views/components/poll.vue:
vote-to: "\"{}\"에 투표하기"
vote-count: "{}표"
@@ -789,6 +788,8 @@ desktop/views/components/settings.vue:
auto-popout-desc: "창이 열릴 때 팝아웃 (브라우저 밖으로 분리) 이 가능한 경우 자동으로 팝아웃합니다. 이 설정은 브라우저에 저장됩니다."
deck-nav: "덱 내 탐색"
deck-nav-desc: "덱을 사용중일 때, 내비게이션이 발생하였을 경우 페이지를 이동하지 않고 일시적으로 임시 칼럼을 생성하도록 합니다."
keep-cw: "CW保持"
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
deck-default: "덱을 기본 UI로 설정"
display: "디자인 및 표시"
customize: "홈 커스터마이징"
@@ -990,7 +991,7 @@ admin/views/index.vue:
federation: "연합"
announcements: "공지사항"
hashtags: "해시태그"
abuse: "スパム報告"
abuse: "스팸 신고"
back-to-misskey: "Misskey로 돌아가기"
admin/views/dashboard.vue:
dashboard: "대시보드"
@@ -1001,11 +1002,11 @@ admin/views/dashboard.vue:
this-instance: "이 인스턴스"
federated: "연합"
admin/views/abuse.vue:
title: "スパム報告"
target: "対象"
reporter: "報告者"
details: "詳細"
remove-report: "削除"
title: "스팸 신고"
target: "대상"
reporter: "신고자"
details: "상세"
remove-report: "삭제"
admin/views/instance.vue:
instance: "인스턴스"
instance-name: "인스턴스 이름"
@@ -1053,7 +1054,7 @@ admin/views/instance.vue:
disable-registration: "사용자 등록 비활성화"
disable-local-timeline: "로컬 타임라인 비활성화"
disable-global-timeline: "글로벌 타임라인 비활성화"
disabling-timelines-info: "これらのタイムラインを無効にしても、管理者およびモデレーターは引き続き利用できます。"
disabling-timelines-info: "이 타임라인들을 비활성화해도 관리자 및 모더레이터는 계속 사용할 수 있습니다."
invite: "초대"
save: "저장"
saved: "저장하였습니다"
@@ -1097,7 +1098,7 @@ admin/views/charts.vue:
notes-total: "글 누적 수"
users: "사용자 증감"
users-total: "사용자 누적"
active-users: "アクティブユーザー数"
active-users: "활성 사용자 수"
drive: "드라이브 사용량 증감"
drive-total: "드라이브 사용량 누적"
drive-files: "드라이브 파일 수 증감"
@@ -1106,10 +1107,10 @@ admin/views/charts.vue:
network-time: "응답시간"
network-usage: "통신량"
admin/views/drive.vue:
operation: "操作"
fileid-or-url: "ファイルIDまたはファイルURL"
file-not-found: "ファイルが見つかりません"
lookup: "照会"
operation: "작업"
fileid-or-url: "파일 ID 또는 파일 URL"
file-not-found: "파일을 찾을 수 없습니다"
lookup: "조회"
sort:
title: "정렬"
createdAtAsc: "업로드 날짜 오랜 순"
@@ -1125,23 +1126,30 @@ admin/views/drive.vue:
deleted: "삭제하였습니다"
mark-as-sensitive: "열람주의로 설정"
unmark-as-sensitive: "열람주의 해제"
marked-as-sensitive: "閲覧注意に設定しました"
unmarked-as-sensitive: "閲覧注意を解除しました"
marked-as-sensitive: "열람주의로 설정하였습니다"
unmarked-as-sensitive: "열람주의를 제거하였습니다"
admin/views/users.vue:
operation: "작업"
username-or-userid: "사용자명 혹은 사용자 ID"
user-not-found: "사용자를 찾을 수 없습니다"
lookup: "조회"
reset-password: "암호 재설정"
reset-password-confirm: "パスワードをリセットしますか?"
password-updated: "암호는 현재 \"{password}\" 입니다"
suspend: "정지"
suspend-confirm: "凍結しますか?"
suspended: "정지하였습니다"
unsuspend: "정지 해제"
unsuspend-confirm: "凍結を解除しますか?"
unsuspended: "정지를 해제하였습니다"
verify: "공식 계정으로 설정"
verify-confirm: "公式アカウントにしますか?"
verified: "공식 계정으로 설정하였습니다"
unverify: "공식 계정 해제"
unverify-confirm: "公式アカウントを解除しますか?"
unverified: "공식 계정을 해제하였습니다"
update-remote-user: "リモートユーザー情報の更新"
remote-user-updated: "リモートユーザー情報を更新しました"
users:
title: "사용자"
sort:
@@ -1253,7 +1261,7 @@ desktop/views/pages/user/user.photos.vue:
no-photos: "사진이 없습니다"
desktop/views/pages/user/user.profile.vue:
follows-you: "당신을 팔로우합니다"
menu: "メニュー"
menu: "메뉴"
desktop/views/pages/user/user.header.vue:
posts: "글"
following: "팔로잉"
@@ -1468,6 +1476,7 @@ mobile/views/pages/settings.vue:
notification-position-top: "위"
behavior: "동작"
fetch-on-scroll: "스크롤하여 자동으로 불러오기"
keep-cw: "CW保持"
note-visibility: "게시물의 공개 범위"
default-note-visibility: "기본 공개 범위"
remember-note-visibility: "글의 공개 범위를 기억하기"

View File

@@ -357,7 +357,6 @@ common/views/components/user-menu.vue:
unblock: "ブロック解除"
push-to-list: "リストに追加"
select-list: "リストを選択してください"
list-pushed: "{user}を{list}に追加しました"
report-abuse: "スパムを報告"
report-abuse-detail: "どのような迷惑行為を行っていますか?"
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
@@ -789,6 +788,8 @@ desktop/views/components/settings.vue:
auto-popout-desc: "Venster uitvouwen, indien mogelijk. Deze instelling wordt opgeslagen in je browser."
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
keep-cw: "CW保持"
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "Ontwerp en weergave"
customize: "Startpagina aanpassen"
@@ -1133,15 +1134,22 @@ admin/views/users.vue:
user-not-found: "ユーザーが見つかりません"
lookup: "照会"
reset-password: "パスワードをリセット"
reset-password-confirm: "パスワードをリセットしますか?"
password-updated: "パスワードは現在「{password}」です"
suspend: "凍結"
suspend-confirm: "凍結しますか?"
suspended: "凍結しました"
unsuspend: "凍結の解除"
unsuspend-confirm: "凍結を解除しますか?"
unsuspended: "凍結を解除しました"
verify: "公式アカウントにする"
verify-confirm: "公式アカウントにしますか?"
verified: "公式アカウントにしました"
unverify: "公式アカウントを解除する"
unverify-confirm: "公式アカウントを解除しますか?"
unverified: "公式アカウントを解除しました"
update-remote-user: "リモートユーザー情報の更新"
remote-user-updated: "リモートユーザー情報を更新しました"
users:
title: "ユーザー"
sort:
@@ -1468,6 +1476,7 @@ mobile/views/pages/settings.vue:
notification-position-top: "上"
behavior: "Gedrag"
fetch-on-scroll: "Ophalen bij scrollen"
keep-cw: "CW保持"
note-visibility: "投稿の公開範囲"
default-note-visibility: "デフォルトの公開範囲"
remember-note-visibility: "投稿の公開範囲を記憶する"

View File

@@ -357,7 +357,6 @@ common/views/components/user-menu.vue:
unblock: "ブロック解除"
push-to-list: "リストに追加"
select-list: "リストを選択してください"
list-pushed: "{user}を{list}に追加しました"
report-abuse: "スパムを報告"
report-abuse-detail: "どのような迷惑行為を行っていますか?"
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
@@ -789,6 +788,8 @@ desktop/views/components/settings.vue:
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
keep-cw: "CW保持"
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "デザインと表示"
customize: "ホームをカスタマイズ"
@@ -1133,15 +1134,22 @@ admin/views/users.vue:
user-not-found: "ユーザーが見つかりません"
lookup: "照会"
reset-password: "パスワードをリセット"
reset-password-confirm: "パスワードをリセットしますか?"
password-updated: "パスワードは現在「{password}」です"
suspend: "凍結"
suspend-confirm: "凍結しますか?"
suspended: "凍結しました"
unsuspend: "凍結の解除"
unsuspend-confirm: "凍結を解除しますか?"
unsuspended: "凍結を解除しました"
verify: "公式アカウントにする"
verify-confirm: "公式アカウントにしますか?"
verified: "公式アカウントにしました"
unverify: "公式アカウントを解除する"
unverify-confirm: "公式アカウントを解除しますか?"
unverified: "公式アカウントを解除しました"
update-remote-user: "リモートユーザー情報の更新"
remote-user-updated: "リモートユーザー情報を更新しました"
users:
title: "ユーザー"
sort:
@@ -1468,6 +1476,7 @@ mobile/views/pages/settings.vue:
notification-position-top: "Topp"
behavior: "Oppførsel"
fetch-on-scroll: "スクロールで自動読み込み"
keep-cw: "CW保持"
note-visibility: "投稿の公開範囲"
default-note-visibility: "デフォルトの公開範囲"
remember-note-visibility: "投稿の公開範囲を記憶する"

View File

@@ -357,7 +357,6 @@ common/views/components/user-menu.vue:
unblock: "ブロック解除"
push-to-list: "リストに追加"
select-list: "リストを選択してください"
list-pushed: "{user}を{list}に追加しました"
report-abuse: "スパムを報告"
report-abuse-detail: "どのような迷惑行為を行っていますか?"
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
@@ -789,6 +788,8 @@ desktop/views/components/settings.vue:
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
keep-cw: "CW保持"
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
deck-default: "Użyj Talię jako domyślne UI"
display: "Wygląd i wyświetlanie"
customize: "Dostosuj stronę główną"
@@ -1133,15 +1134,22 @@ admin/views/users.vue:
user-not-found: "Nie znaleziono użytkownika"
lookup: "照会"
reset-password: "パスワードをリセット"
reset-password-confirm: "パスワードをリセットしますか?"
password-updated: "パスワードは現在「{password}」です"
suspend: "凍結"
suspend-confirm: "凍結しますか?"
suspended: "凍結しました"
unsuspend: "凍結の解除"
unsuspend-confirm: "凍結を解除しますか?"
unsuspended: "凍結を解除しました"
verify: "公式アカウントにする"
verify-confirm: "公式アカウントにしますか?"
verified: "公式アカウントにしました"
unverify: "公式アカウントを解除する"
unverify-confirm: "公式アカウントを解除しますか?"
unverified: "公式アカウントを解除しました"
update-remote-user: "リモートユーザー情報の更新"
remote-user-updated: "リモートユーザー情報を更新しました"
users:
title: "Użytkownicy"
sort:
@@ -1468,6 +1476,7 @@ mobile/views/pages/settings.vue:
notification-position-top: "Góra"
behavior: "Zachowanie"
fetch-on-scroll: "Automatycznie ładuj po przeciągnięciu w dół"
keep-cw: "CW保持"
note-visibility: "Widoczność wpisów"
default-note-visibility: "Domyślna widoczność"
remember-note-visibility: "Zapamiętaj widoczność wpisów"

View File

@@ -357,7 +357,6 @@ common/views/components/user-menu.vue:
unblock: "ブロック解除"
push-to-list: "リストに追加"
select-list: "リストを選択してください"
list-pushed: "{user}を{list}に追加しました"
report-abuse: "スパムを報告"
report-abuse-detail: "どのような迷惑行為を行っていますか?"
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
@@ -789,6 +788,8 @@ desktop/views/components/settings.vue:
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
keep-cw: "CW保持"
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "デザインと表示"
customize: "ホームをカスタマイズ"
@@ -1133,15 +1134,22 @@ admin/views/users.vue:
user-not-found: "ユーザーが見つかりません"
lookup: "照会"
reset-password: "パスワードをリセット"
reset-password-confirm: "パスワードをリセットしますか?"
password-updated: "パスワードは現在「{password}」です"
suspend: "凍結"
suspend-confirm: "凍結しますか?"
suspended: "凍結しました"
unsuspend: "凍結の解除"
unsuspend-confirm: "凍結を解除しますか?"
unsuspended: "凍結を解除しました"
verify: "公式アカウントにする"
verify-confirm: "公式アカウントにしますか?"
verified: "公式アカウントにしました"
unverify: "公式アカウントを解除する"
unverify-confirm: "公式アカウントを解除しますか?"
unverified: "公式アカウントを解除しました"
update-remote-user: "リモートユーザー情報の更新"
remote-user-updated: "リモートユーザー情報を更新しました"
users:
title: "ユーザー"
sort:
@@ -1468,6 +1476,7 @@ mobile/views/pages/settings.vue:
notification-position-top: "上"
behavior: "動作"
fetch-on-scroll: "スクロールで自動読み込み"
keep-cw: "CW保持"
note-visibility: "投稿の公開範囲"
default-note-visibility: "デフォルトの公開範囲"
remember-note-visibility: "投稿の公開範囲を記憶する"

View File

@@ -357,7 +357,6 @@ common/views/components/user-menu.vue:
unblock: "ブロック解除"
push-to-list: "リストに追加"
select-list: "リストを選択してください"
list-pushed: "{user}を{list}に追加しました"
report-abuse: "スパムを報告"
report-abuse-detail: "どのような迷惑行為を行っていますか?"
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
@@ -789,6 +788,8 @@ desktop/views/components/settings.vue:
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
keep-cw: "CW保持"
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "デザインと表示"
customize: "ホームをカスタマイズ"
@@ -1133,15 +1134,22 @@ admin/views/users.vue:
user-not-found: "ユーザーが見つかりません"
lookup: "照会"
reset-password: "パスワードをリセット"
reset-password-confirm: "パスワードをリセットしますか?"
password-updated: "パスワードは現在「{password}」です"
suspend: "凍結"
suspend-confirm: "凍結しますか?"
suspended: "凍結しました"
unsuspend: "凍結の解除"
unsuspend-confirm: "凍結を解除しますか?"
unsuspended: "凍結を解除しました"
verify: "公式アカウントにする"
verify-confirm: "公式アカウントにしますか?"
verified: "公式アカウントにしました"
unverify: "公式アカウントを解除する"
unverify-confirm: "公式アカウントを解除しますか?"
unverified: "公式アカウントを解除しました"
update-remote-user: "リモートユーザー情報の更新"
remote-user-updated: "リモートユーザー情報を更新しました"
users:
title: "ユーザー"
sort:
@@ -1468,6 +1476,7 @@ mobile/views/pages/settings.vue:
notification-position-top: "上"
behavior: "動作"
fetch-on-scroll: "スクロールで自動読み込み"
keep-cw: "CW保持"
note-visibility: "投稿の公開範囲"
default-note-visibility: "デフォルトの公開範囲"
remember-note-visibility: "投稿の公開範囲を記憶する"

View File

@@ -3,17 +3,17 @@ meta:
lang: "中文(简体)"
divider: ""
common:
misskey: "A ⭐ of the fediverse"
about-title: "A ⭐ of the fediverse."
about: "感谢您找到Misskey. Misskey是一个开源的程序, 她可以同其他的社交网站进行联系. 为什么不从喧嚣的城市中休息一下, 来试一试这个新的程序呢?"
misskey: "Fediverse中的一颗⭐"
about-title: "Fediverse中的一颗⭐"
about: "非常感谢您找到Misskey Misskey是出生于地球上的<b>分布式微博SNS</b>。因为她处于Fediverse(由各种SNS组成的宇宙)中所以她与其他SNS相互连接。想要远离喧嚣的城市不如深入这个新的互联网来探索一下吧。"
intro:
title: "什么是 Misskey 呢?"
about: "Misskey是开源的<b>分散式微博服务</b>。复杂的完全可定制的Ui各种各样的帖子反应提供集成管理系统和其他先进功能的免费文件存储。此外网络系统称为“Fediverse”使我们能够与其他SNS的用户进行通信。比如如果你张贴一些东西那么你的帖子不仅会发送给Misskey还会发送到其他平台。想象一下行星正在向另一颗行星发送微波来进行通信。"
features: "特点"
about: "Misskey是开源的<b>分散式微博SNS</b>。复杂的完全可定制的Ui各种各样的帖子反应提供集成管理系统和其他先进功能的免费文件存储。此外称为“Fediverse”的网络系统使我们能够与其他SNS的用户进行通信。比如如果你张贴一些东西那么你的帖子不仅会发送给Misskey还会发送到其他SNS平台。想象一下,正如一颗行星和另一颗行星通过发送微波来进行通信一样。"
features: "功能"
rich-contents: "发布"
rich-contents-desc: "您只需要发布您的想法, 热门话题或者任何您想分享的好东西. 你可以装饰你的文字, 加上你最喜欢的图片, 发送文件或者电影, 甚至创造一个投票. 这些事情您都可以在 Misskey 上做。"
reaction: "情绪"
reaction-desc: "一个最简单的方式去告诉别人你的情绪. Misskey 允许您在别人的帖子中加入各种的情绪反应类型, 就像 Facebook 一样. 在 Misskey 上的情感体验永远不会出现在其他只能点赞的SNSs上。"
rich-contents-desc: "请分享您的想法热门话题,以及任何您想与大家分享的内容。如果有需要的话,您可以使用各种语法来修饰文章,发布问卷调查,或者添加各种您喜欢的图像和视频等文件。"
reaction: "回应"
reaction-desc: "表达情绪的最简单方法。 Misskey允许您向其他帖子添加各种类型的回应。 一旦体验过Misskey的回应功能就再也不会想回到那些只有点赞功能的其他SNS上。"
ui: "交互界面"
ui-desc: "世界上没有一个UI可以适合每一个人. 所以, Misskey 提供一个可以高度定制的UI交互界面. 您可以通过编辑, 调整布局, 放置可选择的小部件来轻松定制您的专属UI界面。"
drive: "Misskey 云盘"
@@ -27,7 +27,7 @@ common:
do-not-copy-paste: "请不要在这里输入或粘贴代码。您帐户可能会受到损害。"
load-more: "加载更多"
enter-password: "请输入您的密码"
2fa: "二段階認証"
2fa: "双重身份验证"
got-it: "没问题"
customization-tips:
title: "客制化提示"
@@ -54,7 +54,7 @@ common:
years_ago: "{}年前"
month-and-day: "{month}月 {day}日"
trash: "垃圾箱"
drive: "Misskey 云盘"
drive: "盘"
messaging: "聊天"
weekday-short:
sunday: "日"
@@ -350,17 +350,16 @@ common/views/components/note-menu.vue:
delete-confirm: "确定删除这个投稿吗?"
remote: "显示原始投稿"
common/views/components/user-menu.vue:
mention: "メンション"
mute: "ミュート"
unmute: "ミュート解除"
block: "ブロック"
unblock: "ブロック解除"
push-to-list: "リストに追加"
select-list: "リストを選択してください"
list-pushed: "{user}を{list}に追加しました"
report-abuse: "スパムを報告"
report-abuse-detail: "どのような迷惑行為を行っていますか?"
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
mention: "提及"
mute: "静音"
unmute: "取消静音"
block: "屏蔽"
unblock: "取消屏蔽"
push-to-list: "添加至列表"
select-list: "请选择一个列表"
report-abuse: "举报垃圾邮件"
report-abuse-detail: "做了什么骚扰的行为?"
report-abuse-reported: "已报告给管理员。 非常感谢你的合作。"
common/views/components/poll.vue:
vote-to: "为\"{}\"投票"
vote-count: "{}票"
@@ -698,12 +697,12 @@ desktop/views/components/note-detail.vue:
location: "位置信息"
renote: "转发"
add-reaction: "添加一个反应"
undo-reaction: "リアクション解除"
undo-reaction: "取消反应"
desktop/views/components/note.vue:
reply: "回复"
renote: "Renote"
add-reaction: "添加一个反应"
undo-reaction: "リアクション解除"
undo-reaction: "取消反应"
detail: "详细信息"
private: "这个投稿是私密的"
deleted: "投稿已删除"
@@ -789,6 +788,8 @@ desktop/views/components/settings.vue:
auto-popout-desc: "如果可用,将使用弹出显示而不是打开新窗口。 此设置存储在浏览器中。"
deck-nav: "Deck 内的导航"
deck-nav-desc: "在使用Deck时您会在导航期间获得一个没有页面过渡的临时列。"
keep-cw: "保留内容警告"
keep-cw-desc: "在回复帖子时,如果原帖设置了内容警告,默认情况下回帖也会设置相同的内容警告。"
deck-default: "将Deck界面设置为默认UI显示界面"
display: "设计与展示"
customize: "自定义首页摆放"
@@ -809,16 +810,16 @@ desktop/views/components/settings.vue:
show-local-renotes: "在时间线中显示Local Renote(s)"
show-maps: "显示地图以显示位置"
remain-deleted-note: "继续显示已删除的帖子"
deck-column-align: "デッキのカラムの配置"
deck-column-align: "列对齐设置"
deck-column-align-center: "中央"
deck-column-align-left: "左"
deck-column-align-flexible: "フレキシブル"
deck-column-width: "デッキのカラムの幅"
deck-column-width-narrow: ""
deck-column-width-narrower: "やや狭"
deck-column-align-flexible: "可变"
deck-column-width: "列宽度"
deck-column-width-narrow: ""
deck-column-width-narrower: "更窄"
deck-column-width-normal: "正常"
deck-column-width-wider: "やや広"
deck-column-width-wide: ""
deck-column-width-wider: "更宽"
deck-column-width-wide: ""
sound: "声音"
enable-sounds: "开启声音"
enable-sounds-desc: "收到帖子/留言时播放声音。 此设置将被存储在浏览器中。"
@@ -867,7 +868,7 @@ desktop/views/components/settings.2fa.vue:
enter-password: "请输入您的密码"
authenticator: "首先,您需要在设备上安装 Google Authenticator"
howtoinstall: "怎样安装"
token: "トークン"
token: "令牌"
scan: "然后,扫描二维码:"
done: "请输入显示在您设备上的密钥:"
submit: "提交"
@@ -990,7 +991,7 @@ admin/views/index.vue:
federation: "联合"
announcements: "公告"
hashtags: "标签"
abuse: "スパム報告"
abuse: "举报垃圾信息"
back-to-misskey: "返回 Misskey"
admin/views/dashboard.vue:
dashboard: "Dashboard"
@@ -1001,11 +1002,11 @@ admin/views/dashboard.vue:
this-instance: "此实例"
federated: "联合"
admin/views/abuse.vue:
title: "スパム報告"
target: "対象"
reporter: "告者"
details: "詳細"
remove-report: "除"
title: "举报垃圾信息"
target: "目标"
reporter: "告者"
details: "详情"
remove-report: "除"
admin/views/instance.vue:
instance: "例子"
instance-name: "实例名称"
@@ -1052,8 +1053,8 @@ admin/views/instance.vue:
max-note-text-length: "最大帖子字符数"
disable-registration: "停用新用户注册功能"
disable-local-timeline: "停用本地时间线功能"
disable-global-timeline: "グローバルタイムラインを無効にする"
disabling-timelines-info: "これらのタイムラインを無効にしても、管理者およびモデレーターは引き続き利用できます。"
disable-global-timeline: "禁用全局时间线"
disabling-timelines-info: "即使禁用时间线,管理员和版主仍然可用。"
invite: "邀请"
save: "保存"
saved: "保存完毕"
@@ -1075,7 +1076,7 @@ admin/views/instance.vue:
smtp-pass: "SMTP 密码"
serviceworker-config: "ServiceWorker"
enable-serviceworker: "启用ServiceWorker"
serviceworker-info: "プッシュ通知を行うには有効する必要があります。"
serviceworker-info: "您需要启用推送通知"
vapid-publickey: "VAPID公钥"
vapid-privatekey: "VAPID私钥"
vapid-info: "如果您想要启用ServiceWorker那么您需要生成VAPID秘钥。除非您已经在其他地方设置了全局node_modules位置否则您需要将其作为root用户运行"
@@ -1097,7 +1098,7 @@ admin/views/charts.vue:
notes-total: "帖子总数"
users: "用户数量:增加/减少"
users-total: "用户总数"
active-users: "アクティブユーザー数"
active-users: "活跃用户数"
drive: "存储容量:增加/减少"
drive-total: "云盘总容量"
drive-files: "云盘上的文件数:增加/减少"
@@ -1107,15 +1108,15 @@ admin/views/charts.vue:
network-usage: "网络流量"
admin/views/drive.vue:
operation: "操作"
fileid-or-url: "ファイルIDまたはファイルURL"
file-not-found: "ファイルが見つかりません"
lookup: "照会"
fileid-or-url: "文件ID或文件URL"
file-not-found: "找不到文件"
lookup: "查询"
sort:
title: "排序"
createdAtAsc: "アップロード日時が古い順"
createdAtDesc: "アップロード日時が新しい順"
sizeAsc: "サイズが小さい順"
sizeDesc: "サイズが大きい順"
createdAtAsc: "按上传时间(升序)"
createdAtDesc: "按上传时间(降序)"
sizeAsc: "按大小(升序)"
sizeDesc: "按大小(降序)"
origin:
title: "源自"
combined: "本地+远程"
@@ -1125,23 +1126,30 @@ admin/views/drive.vue:
deleted: "已删除"
mark-as-sensitive: "标记为“敏感”"
unmark-as-sensitive: "取消标记为“敏感”"
marked-as-sensitive: "閲覧注意に設定しました"
unmarked-as-sensitive: "閲覧注意を解除しました"
marked-as-sensitive: "标记为关注"
unmarked-as-sensitive: "解除关注标记"
admin/views/users.vue:
operation: "操作"
username-or-userid: "用户名或用户ID"
user-not-found: "用户不存在"
lookup: "订阅"
reset-password: "密码重置"
reset-password-confirm: "パスワードをリセットしますか?"
password-updated: "密码为「{password}」"
suspend: "被冻结"
suspend-confirm: "凍結しますか?"
suspended: "成功冻结用户"
unsuspend: "已解除冻结"
unsuspend-confirm: "凍結を解除しますか?"
unsuspended: "已成功解除用户冻结"
verify: "认证用户"
verify-confirm: "公式アカウントにしますか?"
verified: "此账户已被认证"
unverify: "解除账户认证"
unverify-confirm: "公式アカウントを解除しますか?"
unverified: "该帐户未经认证"
update-remote-user: "リモートユーザー情報の更新"
remote-user-updated: "リモートユーザー情報を更新しました"
users:
title: "用户"
sort:
@@ -1151,13 +1159,13 @@ admin/views/users.vue:
updatedAtAsc: "更新时间从旧到新"
updatedAtDesc: "更新时间从新到旧"
state:
title: "状"
all: "すべて"
admin: "管理"
moderator: "モデレーター"
adminOrModerator: "管理者+モデレーター"
verified: "公式アカウント"
suspended: "凍結済み"
title: "状"
all: "全部"
admin: "管理"
moderator: "版主"
adminOrModerator: "管理员+版主"
verified: "官方认证账户"
suspended: "已冻结"
origin:
title: "源自"
combined: "本地+远程"
@@ -1171,7 +1179,7 @@ admin/views/moderators.vue:
add: "注册"
added: "已注册版主。"
remove: "解除"
removed: "モデレーター登録を解除しました"
removed: "取消注册版主"
admin/views/emoji.vue:
add-emoji:
title: "添加emoji"
@@ -1253,7 +1261,7 @@ desktop/views/pages/user/user.photos.vue:
no-photos: "没有图片"
desktop/views/pages/user/user.profile.vue:
follows-you: "关注您"
menu: "メニュー"
menu: "菜单"
desktop/views/pages/user/user.header.vue:
posts: "帖子"
following: "关注中"
@@ -1267,7 +1275,7 @@ desktop/views/pages/user/user.timeline.vue:
default: "帖子"
with-replies: "帖子与回复"
with-media: "媒体"
my-posts: "私の投稿"
my-posts: "我的帖子"
empty: "看起来这个用户还没有发布什么呢。"
desktop/views/widgets/messaging.vue:
title: "信息"
@@ -1468,6 +1476,7 @@ mobile/views/pages/settings.vue:
notification-position-top: "顶部"
behavior: "动作"
fetch-on-scroll: "滚动无限加载"
keep-cw: "保留内容警告"
note-visibility: "帖子可见性"
default-note-visibility: "默认可见性"
remember-note-visibility: "记住帖子可见性"

View File

@@ -1,8 +1,8 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "10.78.2",
"clientVersion": "2.0.13649",
"version": "10.79.0",
"clientVersion": "2.0.13835",
"codename": "nighthike",
"main": "./built/index.js",
"private": true,
@@ -54,7 +54,7 @@
"@types/koa-logger": "3.1.1",
"@types/koa-mount": "3.0.1",
"@types/koa-multer": "1.0.0",
"@types/koa-router": "7.0.35",
"@types/koa-router": "7.0.38",
"@types/koa-send": "4.1.1",
"@types/koa-views": "2.0.3",
"@types/koa__cors": "2.2.3",
@@ -97,8 +97,8 @@
"bootstrap-vue": "2.0.0-rc.11",
"cafy": "12.0.0",
"chai": "4.2.0",
"chalk": "2.4.2",
"chai-http": "4.2.1",
"chalk": "2.4.2",
"commander": "2.19.0",
"crc-32": "1.2.0",
"css-loader": "1.0.1",
@@ -155,7 +155,7 @@
"koa-router": "7.4.0",
"koa-send": "5.0.0",
"koa-slow": "2.1.0",
"koa-views": "6.1.4",
"koa-views": "6.1.5",
"langmap": "0.0.16",
"loader-utils": "1.2.3",
"lookup-dns-cache": "2.1.0",
@@ -169,7 +169,7 @@
"ms": "2.1.1",
"nan": "2.12.1",
"nested-property": "0.0.7",
"nodemailer": "5.0.0",
"nodemailer": "5.1.1",
"nprogress": "0.2.0",
"object-assign-deep": "0.4.0",
"on-build-webpack": "0.1.0",
@@ -178,13 +178,14 @@
"parsimmon": "1.12.0",
"portscanner": "2.2.0",
"postcss-loader": "3.0.0",
"progress-bar-webpack-plugin": "1.11.0",
"prismjs": "1.15.0",
"progress-bar-webpack-plugin": "1.12.0",
"promise-any": "0.2.0",
"promise-limit": "2.7.0",
"promise-sequential": "1.1.1",
"pug": "2.0.3",
"punycode": "2.1.1",
"qrcode": "1.3.2",
"qrcode": "1.3.3",
"randomcolor": "0.5.3",
"ratelimiter": "3.2.0",
"recaptcha-promise": "0.1.3",
@@ -216,7 +217,7 @@
"ts-node": "7.0.1",
"tslint": "5.12.0",
"tslint-sonarts": "1.8.0",
"typescript": "3.2.2",
"typescript": "3.2.4",
"typescript-eslint-parser": "21.0.2",
"uglify-es": "3.3.9",
"url-loader": "1.1.2",
@@ -228,8 +229,9 @@
"vue-cropperjs": "3.0.0",
"vue-i18n": "8.7.0",
"vue-js-modal": "1.3.28",
"vue-loader": "15.4.2",
"vue-loader": "15.5.1",
"vue-marquee-text-component": "1.1.1",
"vue-prism-component": "1.1.1",
"vue-router": "3.0.2",
"vue-sequential-entrance": "1.1.3",
"vue-style-loader": "4.1.2",

View File

@@ -6,10 +6,10 @@
<sequential-entrance animation="entranceFromTop" delay="25">
<div v-for="report in userReports" :key="report.id" class="haexwsjc">
<ui-horizon-group inputs>
<ui-input :value="report.user | acct" type="text">
<ui-input :value="report.user | acct" type="text" readonly>
<span>{{ $t('target') }}</span>
</ui-input>
<ui-input :value="report.reporter | acct" type="text">
<ui-input :value="report.reporter | acct" type="text" readonly>
<span>{{ $t('reporter') }}</span>
</ui-input>
</ui-horizon-group>

View File

@@ -0,0 +1,82 @@
<template>
<div class="kofvwchc">
<div>
<a :href="user | userPage(null, true)">
<mk-avatar class="avatar" :user="user" :disable-link="true"/>
</a>
</div>
<div>
<header>
<b><mk-user-name :user="user"/></b>
<span class="username">@{{ user | acct }}</span>
<span class="is-admin" v-if="user.isAdmin">admin</span>
<span class="is-moderator" v-if="user.isModerator">moderator</span>
<span class="is-verified" v-if="user.isVerified" :title="$t('@.verified-user')"><fa icon="star"/></span>
<span class="is-suspended" v-if="user.isSuspended" :title="$t('@.suspended-user')"><fa :icon="faSnowflake"/></span>
</header>
<div>
<span>{{ $t('users.updatedAt') }}: <mk-time :time="user.updatedAt" mode="detail"/></span>
</div>
<div>
<span>{{ $t('users.createdAt') }}: <mk-time :time="user.createdAt" mode="detail"/></span>
</div>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../i18n';
import { faSnowflake } from '@fortawesome/free-regular-svg-icons';
export default Vue.extend({
i18n: i18n('admin/views/users.vue'),
props: ['user'],
data() {
return {
faSnowflake
};
},
});
</script>
<style lang="stylus" scoped>
.kofvwchc
display flex
padding 16px 0
border-top solid 1px var(--faceDivider)
> div:first-child
> a
> .avatar
width 64px
height 64px
> div:last-child
flex 1
padding-left 16px
@media (max-width 500px)
font-size 14px
> header
> .username
margin-left 8px
opacity 0.7
> .is-admin
> .is-moderator
flex-shrink 0
align-self center
margin 0 0 0 .5em
padding 1px 6px
font-size 80%
border-radius 3px
background var(--noteHeaderAdminBg)
color var(--noteHeaderAdminFg)
> .is-verified
> .is-suspended
margin 0 0 0 .5em
color #4dabf7
</style>

View File

@@ -3,20 +3,27 @@
<ui-card>
<div slot="title"><fa :icon="faTerminal"/> {{ $t('operation') }}</div>
<section class="fit-top">
<ui-input v-model="target" type="text">
<ui-input class="target" v-model="target" type="text">
<span>{{ $t('username-or-userid') }}</span>
</ui-input>
<ui-button @click="resetPassword"><fa :icon="faKey"/> {{ $t('reset-password') }}</ui-button>
<ui-horizon-group>
<ui-button @click="verifyUser" :disabled="verifying"><fa :icon="faCertificate"/> {{ $t('verify') }}</ui-button>
<ui-button @click="unverifyUser" :disabled="unverifying">{{ $t('unverify') }}</ui-button>
</ui-horizon-group>
<ui-horizon-group>
<ui-button @click="suspendUser" :disabled="suspending"><fa :icon="faSnowflake"/> {{ $t('suspend') }}</ui-button>
<ui-button @click="unsuspendUser" :disabled="unsuspending">{{ $t('unsuspend') }}</ui-button>
</ui-horizon-group>
<ui-button @click="showUser"><fa :icon="faSearch"/> {{ $t('lookup') }}</ui-button>
<ui-textarea v-if="user" :value="user | json5" readonly tall style="margin-top:16px;"></ui-textarea>
<div class="user" v-if="user">
<x-user :user='user'/>
<div class="actions">
<ui-button @click="resetPassword"><fa :icon="faKey"/> {{ $t('reset-password') }}</ui-button>
<ui-horizon-group>
<ui-button @click="verifyUser" :disabled="verifying"><fa :icon="faCertificate"/> {{ $t('verify') }}</ui-button>
<ui-button @click="unverifyUser" :disabled="unverifying">{{ $t('unverify') }}</ui-button>
</ui-horizon-group>
<ui-horizon-group>
<ui-button @click="suspendUser" :disabled="suspending"><fa :icon="faSnowflake"/> {{ $t('suspend') }}</ui-button>
<ui-button @click="unsuspendUser" :disabled="unsuspending">{{ $t('unsuspend') }}</ui-button>
</ui-horizon-group>
<ui-button v-if="user.host != null" @click="updateRemoteUser"><fa :icon="faSync"/> {{ $t('update-remote-user') }}</ui-button>
<ui-textarea v-if="user" :value="user | json5" readonly tall style="margin-top:16px;"></ui-textarea>
</div>
</div>
</section>
</ui-card>
@@ -47,29 +54,7 @@
</ui-select>
</ui-horizon-group>
<sequential-entrance animation="entranceFromTop" delay="25">
<div class="kofvwchc" v-for="user in users" :key="user.id">
<div>
<a :href="user | userPage(null, true)">
<mk-avatar class="avatar" :user="user" :disable-link="true"/>
</a>
</div>
<div>
<header>
<b><mk-user-name :user="user"/></b>
<span class="username">@{{ user | acct }}</span>
<span class="is-admin" v-if="user.isAdmin">admin</span>
<span class="is-moderator" v-if="user.isModerator">moderator</span>
<span class="is-verified" v-if="user.isVerified" :title="$t('@.verified-user')"><fa icon="star"/></span>
<span class="is-suspended" v-if="user.isSuspended" :title="$t('@.suspended-user')"><fa :icon="faSnowflake"/></span>
</header>
<div>
<span>{{ $t('users.updatedAt') }}: <mk-time :time="user.updatedAt" mode="detail"/></span>
</div>
<div>
<span>{{ $t('users.createdAt') }}: <mk-time :time="user.createdAt" mode="detail"/></span>
</div>
</div>
</div>
<x-user v-for="user in users" :user='user' :key="user.id"/>
</sequential-entrance>
<ui-button v-if="existMore" @click="fetchUsers">{{ $t('@.load-more') }}</ui-button>
</section>
@@ -81,12 +66,15 @@
import Vue from 'vue';
import i18n from '../../i18n';
import parseAcct from "../../../../misc/acct/parse";
import { faCertificate, faUsers, faTerminal, faSearch, faKey } from '@fortawesome/free-solid-svg-icons';
import { faCertificate, faUsers, faTerminal, faSearch, faKey, faSync } from '@fortawesome/free-solid-svg-icons';
import { faSnowflake } from '@fortawesome/free-regular-svg-icons';
import XUser from './users.user.vue';
export default Vue.extend({
i18n: i18n('admin/views/users.vue'),
components: {
XUser
},
data() {
return {
user: null,
@@ -102,7 +90,7 @@ export default Vue.extend({
offset: 0,
users: [],
existMore: false,
faTerminal, faCertificate, faUsers, faSnowflake, faSearch, faKey
faTerminal, faCertificate, faUsers, faSnowflake, faSearch, faKey, faSync
};
},
@@ -131,6 +119,7 @@ export default Vue.extend({
},
methods: {
/** テキストエリアのユーザーを解決する */
async fetchUser() {
try {
return await this.$root.api('users/show', this.target.startsWith('@') ? parseAcct(this.target) : { userId: this.target });
@@ -149,16 +138,27 @@ export default Vue.extend({
}
},
/** テキストエリアから処理対象ユーザーを設定する */
async showUser() {
this.user = null;
const user = await this.fetchUser();
this.$root.api('admin/show-user', { userId: user.id }).then(info => {
this.user = info;
});
this.target = '';
},
/** 処理対象ユーザーの情報を更新する */
async refreshUser() {
this.$root.api('admin/show-user', { userId: this.user._id }).then(info => {
this.user = info;
});
},
async resetPassword() {
const user = await this.fetchUser();
this.$root.api('admin/reset-password', { userId: user.id }).then(res => {
if (!await this.getConfirmed(this.$t('reset-password-confirm'))) return;
this.$root.api('admin/reset-password', { userId: this.user._id }).then(res => {
this.$root.dialog({
type: 'success',
text: this.$t('password-updated', { password: res.password })
@@ -167,11 +167,12 @@ export default Vue.extend({
},
async verifyUser() {
if (!await this.getConfirmed(this.$t('verify-confirm'))) return;
this.verifying = true;
const process = async () => {
const user = await this.fetchUser();
await this.$root.api('admin/verify-user', { userId: user.id });
await this.$root.api('admin/verify-user', { userId: this.user._id });
this.$root.dialog({
type: 'success',
text: this.$t('verified')
@@ -186,14 +187,17 @@ export default Vue.extend({
});
this.verifying = false;
this.refreshUser();
},
async unverifyUser() {
if (!await this.getConfirmed(this.$t('unverify-confirm'))) return;
this.unverifying = true;
const process = async () => {
const user = await this.fetchUser();
await this.$root.api('admin/unverify-user', { userId: user.id });
await this.$root.api('admin/unverify-user', { userId: this.user._id });
this.$root.dialog({
type: 'success',
text: this.$t('unverified')
@@ -208,14 +212,17 @@ export default Vue.extend({
});
this.unverifying = false;
this.refreshUser();
},
async suspendUser() {
if (!await this.getConfirmed(this.$t('suspend-confirm'))) return;
this.suspending = true;
const process = async () => {
const user = await this.fetchUser();
await this.$root.api('admin/suspend-user', { userId: user.id });
await this.$root.api('admin/suspend-user', { userId: this.user._id });
this.$root.dialog({
type: 'success',
text: this.$t('suspended')
@@ -230,14 +237,17 @@ export default Vue.extend({
});
this.suspending = false;
this.refreshUser();
},
async unsuspendUser() {
if (!await this.getConfirmed(this.$t('unsuspend-confirm'))) return;
this.unsuspending = true;
const process = async () => {
const user = await this.fetchUser();
await this.$root.api('admin/unsuspend-user', { userId: user.id });
await this.$root.api('admin/unsuspend-user', { userId: this.user._id });
this.$root.dialog({
type: 'success',
text: this.$t('unsuspended')
@@ -252,8 +262,32 @@ export default Vue.extend({
});
this.unsuspending = false;
this.refreshUser();
},
async updateRemoteUser() {
this.$root.api('admin/update-remote-user', { userId: this.user._id }).then(res => {
this.$root.dialog({
type: 'success',
text: this.$t('remote-user-updated')
});
});
this.refreshUser();
},
async getConfirmed(text: string): Promise<Boolean> {
const confirm = await this.$root.dialog({
type: 'warning',
showCancelButton: true,
title: 'confirm',
text,
});
return !confirm.canceled;
}
fetchUsers() {
this.$root.api('admin/show-users', {
state: this.state,
@@ -277,42 +311,12 @@ export default Vue.extend({
</script>
<style lang="stylus" scoped>
.kofvwchc
display flex
padding 16px 0
border-top solid 1px var(--faceDivider)
.target
margin-bottom 16px !important
> div:first-child
> a
> .avatar
width 64px
height 64px
.user
margin-top 32px
> div:last-child
flex 1
padding-left 16px
@media (max-width 500px)
font-size 14px
> header
> .username
margin-left 8px
opacity 0.7
> .is-admin
> .is-moderator
flex-shrink 0
align-self center
margin 0 0 0 .5em
padding 1px 6px
font-size 80%
border-radius 3px
background var(--noteHeaderAdminBg)
color var(--noteHeaderAdminFg)
> .is-verified
> .is-suspended
margin 0 0 0 .5em
color #4dabf7
> .actions
margin-left 80px
</style>

View File

@@ -26,3 +26,8 @@
transform: translateY(0);
}
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

View File

@@ -72,47 +72,6 @@ body
code
font-family Consolas, 'Courier New', Courier, Monaco, monospace
.comment
opacity 0.5
.string
color #e96900
.regexp
color #e9003f
.keyword
color #2973b7
&.true
&.false
&.null
&.nil
&.undefined
color #ae81ff
.symbol
color #42b983
.number
.nan
color #ae81ff
.var:not(.keyword)
font-weight bold
font-style italic
//text-decoration underline
.method
font-style italic
color #8964c1
.property
color #a71d5d
.label
color #e9003f
pre
display block

View File

@@ -2,12 +2,32 @@
* Clipboardに値をコピー(TODO: 文字列以外も対応)
*/
export default val => {
const form = document.createElement('textarea');
form.textContent = val;
document.body.appendChild(form);
form.select();
// 空div 生成
const tmp = document.createElement('div');
// 選択用のタグ生成
const pre = document.createElement('pre');
// 親要素のCSSで user-select: none だとコピーできないので書き換える
pre.style.webkitUserSelect = 'auto';
pre.style.userSelect = 'auto';
tmp.appendChild(pre).textContent = val;
// 要素を画面外へ
const s = tmp.style;
s.position = 'fixed';
s.right = '200%';
// body に追加
document.body.appendChild(tmp);
// 要素を選択
document.getSelection().selectAllChildren(tmp);
// クリップボードにコピー
const result = document.execCommand('copy');
document.body.removeChild(form);
// 要素削除
document.body.removeChild(tmp);
return result;
};

View File

@@ -133,6 +133,7 @@ export default prop => ({
case 'deleted': {
Vue.set(this.$_ns_target, 'deletedAt', body.deletedAt);
Vue.set(this.$_ns_target, 'renote', null);
this.$_ns_target.text = null;
this.$_ns_target.tags = [];
this.$_ns_target.fileIds = [];

View File

@@ -0,0 +1,30 @@
<template>
<prism :inline="inline" :language="lang">{{ code }}</prism>
</template>
<script lang="ts">
import Vue from 'vue';
import 'prismjs';
import 'prismjs/themes/prism.css';
import Prism from 'vue-prism-component';
export default Vue.extend({
components: {
Prism
},
props: {
code: {
type: String,
required: true
},
lang: {
type: String,
required: false
},
inline: {
type: Boolean,
required: false
}
}
});
</script>

View File

@@ -0,0 +1,28 @@
<template>
<x-code :code="code" :lang="lang" :inline="inline"/>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
components: {
XCode: () => import('./code-core.vue').then(m => m.default)
},
props: {
code: {
type: String,
required: true
},
lang: {
type: String,
required: false
},
inline: {
type: Boolean,
required: false
}
}
});
</script>

View File

@@ -1,5 +1,6 @@
<template>
<span v-html="compiledFormula"></span>
<div v-if="block" v-html="compiledFormula"></div>
<span v-else v-html="compiledFormula"></span>
</template>
<script lang="ts">
@@ -11,6 +12,10 @@ export default Vue.extend({
formula: {
type: String,
required: true
},
block: {
type: Boolean,
required: true
}
},
computed: {

View File

@@ -1,5 +1,5 @@
<template>
<x-formula :formula="formula"/>
<x-formula :formula="formula" :block="block" />
</template>
<script lang="ts">
@@ -14,6 +14,10 @@ export default Vue.extend({
formula: {
type: String,
required: true
},
block: {
type: Boolean,
required: true
}
}
});

View File

@@ -26,7 +26,7 @@
</section>
<section v-if="myGames.length > 0">
<h2>{{ $t('my-games') }}</h2>
<a class="game" v-for="g in myGames" tabindex="-1" @click.prevent="go(g)" :href="`/reversi/${g.id}`">
<a class="game" v-for="g in myGames" tabindex="-1" @click.prevent="go(g)" :href="`/games/reversi/${g.id}`">
<mk-avatar class="avatar" :user="g.user1"/>
<mk-avatar class="avatar" :user="g.user2"/>
<span><b><mk-user-name :user="g.user1"/></b> vs <b><mk-user-name :user="g.user2"/></b></span>

View File

@@ -5,16 +5,21 @@
<span>{{ $t('click-to-show') }}</span>
</div>
</div>
<a class="gqnyydlzavusgskkfvwvjiattxdzsqlf" v-else :href="image.url" target="_blank" :style="style" :title="image.name" @click.prevent="onClick"></a>
<a class="gqnyydlzavusgskkfvwvjiattxdzsqlf" v-else
:href="image.url"
:style="style"
:title="image.name"
@click.prevent="onClick"
></a>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import ImageViewer from '../../../common/views/components/image-viewer.vue';
import ImageViewer from './image-viewer.vue';
export default Vue.extend({
i18n: i18n('mobile/views/components/media-image.vue'),
i18n: i18n('common/views/components/media-image.vue'),
props: {
image: {
type: Object,
@@ -58,6 +63,7 @@ export default Vue.extend({
<style lang="stylus" scoped>
.gqnyydlzavusgskkfvwvjiattxdzsqlf
display block
cursor zoom-in
overflow hidden
width 100%
height 100%

View File

@@ -7,7 +7,7 @@
<div :data-count="mediaList.filter(media => previewable(media)).length" ref="grid">
<template v-for="media in mediaList">
<mk-media-video :video="media" :key="media.id" v-if="media.type.startsWith('video')"/>
<mk-media-image :image="media" :key="media.id" v-else-if="media.type.startsWith('image')" :raw="raw"/>
<x-image :image="media" :key="media.id" v-else-if="media.type.startsWith('image')" :raw="raw"/>
</template>
</div>
</div>
@@ -17,10 +17,12 @@
<script lang="ts">
import Vue from 'vue';
import XBanner from './media-banner.vue';
import XImage from './media-image.vue';
export default Vue.extend({
components: {
XBanner
XBanner,
XImage
},
props: {
mediaList: {

View File

@@ -6,8 +6,8 @@ import MkUrl from './url.vue';
import MkMention from './mention.vue';
import { concat, sum } from '../../../../../prelude/array';
import MkFormula from './formula.vue';
import MkCode from './code.vue';
import MkGoogle from './google.vue';
import syntaxHighlight from '../../../../../mfm/syntax-highlight';
import { host } from '../../../config';
import { preorderF, countNodesF } from '../../../../../prelude/tree';
@@ -124,6 +124,25 @@ export default Vue.component('misskey-flavored-markdown', {
}, genEl(token.children));
}
case 'spin': {
motionCount++;
const isLong = sumTextsLength(token.children) > 5 || countNodesF(token.children) > 3;
const isMany = motionCount > 3;
return (createElement as any)('span', {
attrs: {
style: (this.$store.state.settings.disableAnimatedMfm || isLong || isMany) ? 'display: inline-block;' : 'display: inline-block; animation: spin 1.5s linear infinite;'
},
}, genEl(token.children));
}
case 'flip': {
return (createElement as any)('span', {
attrs: {
style: 'display: inline-block; transform: scaleX(-1);'
},
}, genEl(token.children));
}
case 'url': {
return [createElement(MkUrl, {
key: Math.random(),
@@ -170,21 +189,22 @@ export default Vue.component('misskey-flavored-markdown', {
}
case 'blockCode': {
return [createElement('pre', {
class: 'code'
}, [
createElement('code', {
domProps: {
innerHTML: syntaxHighlight(token.node.props.code)
}
})
])];
return [createElement(MkCode, {
key: Math.random(),
props: {
code: token.node.props.code,
lang: token.node.props.lang,
}
})];
}
case 'inlineCode': {
return [createElement('code', {
domProps: {
innerHTML: syntaxHighlight(token.node.props.code)
return [createElement(MkCode, {
key: Math.random(),
props: {
code: token.node.props.code,
lang: token.node.props.lang,
inline: true
}
})];
}
@@ -228,12 +248,24 @@ export default Vue.component('misskey-flavored-markdown', {
})];
}
case 'math': {
case 'mathInline': {
//const MkFormula = () => import('./formula.vue').then(m => m.default);
return [createElement(MkFormula, {
key: Math.random(),
props: {
formula: token.node.props.formula
formula: token.node.props.formula,
block: false
}
})];
}
case 'mathBlock': {
//const MkFormula = () => import('./formula.vue').then(m => m.default);
return [createElement(MkFormula, {
key: Math.random(),
props: {
formula: token.node.props.formula,
block: true
}
})];
}

View File

@@ -24,34 +24,10 @@ export default Vue.extend({
background var(--mfmTitleBg)
border-radius 4px
>>> .code
margin 8px 0
>>> .quote
margin 8px
padding 6px 0 6px 12px
color var(--mfmQuote)
border-left solid 3px var(--mfmQuoteLine)
>>> code
padding 4px 8px
margin 0 0.5em
font-size 80%
color #525252
background rgba(0, 0, 0, 0.05)
border-radius 2px
>>> pre > code
padding 16px
margin 0
>>> [data-is-me]:after
content "you"
padding 0 4px
margin-left 4px
font-size 80%
color var(--primaryForeground)
background var(--primary)
border-radius 4px
</style>

View File

@@ -10,6 +10,7 @@ import i18n from '../../../i18n';
import { url } from '../../../config';
import copyToClipboard from '../../../common/scripts/copy-to-clipboard';
import { concat, intersperse } from '../../../../../prelude/array';
import { faCopy } from '@fortawesome/free-regular-svg-icons';
export default Vue.extend({
i18n: i18n('common/views/components/note-menu.vue'),
@@ -30,7 +31,7 @@ export default Vue.extend({
text: this.$t('detail'),
action: this.detail
}], [{
icon: 'align-left',
icon: faCopy,
text: this.$t('copy-content'),
action: this.copyContent
}], [{
@@ -87,10 +88,18 @@ export default Vue.extend({
copyContent() {
copyToClipboard(this.note.text);
this.$root.dialog({
type: 'success',
splash: true
});
},
copyLink() {
copyToClipboard(`${url}/notes/${this.note.id}`);
this.$root.dialog({
type: 'success',
splash: true
});
},
pin() {

View File

@@ -5,7 +5,7 @@
<div class="backdrop" :style="{ 'width': (showResult ? (choice.votes / total * 100) : 0) + '%' }"></div>
<span>
<template v-if="choice.isVoted"><fa icon="check"/></template>
<span>{{ choice.text }}</span>
<mfm :text="choice.text" :should-break="false" :plain-text="true" :custom-emojis="note.emojis"/>
<span class="votes" v-if="showResult">({{ $t('vote-count').replace('{}', choice.votes) }})</span>
</span>
</li>

View File

@@ -71,10 +71,7 @@ export default Vue.extend({
});
this.$root.dialog({
type: 'success',
text: this.$t('list-pushed', {
user: this.user.name,
list: lists.find(l => l.id === listId).title
})
splash: true
});
},

View File

@@ -1,21 +1,23 @@
<template>
<mk-window ref="window" is-modal width="800px" height="500px" @closed="destroyDom">
<span slot="header">
<span :class="$style.title">{{ $t('choose-prompt') }}</span>
<span :class="$style.count" v-if="multiple && files.length > 0">({{ $t('chosen-files', { count: files.length }) }})</span>
<span slot="header" class="jqiaciqv">
<span class="title">{{ $t('choose-prompt') }}</span>
<span class="count" v-if="multiple && files.length > 0">({{ $t('chosen-files', { count: files.length }) }})</span>
</span>
<x-drive
ref="browser"
:class="$style.browser"
:multiple="multiple"
@selected="onSelected"
@change-selection="onChangeSelection"
/>
<div :class="$style.footer">
<button :class="$style.upload" :title="$t('title')" @click="upload"><fa icon="upload"/></button>
<button :class="$style.cancel" @click="cancel">{{ $t('cancel') }}</button>
<button :class="$style.ok" :disabled="multiple && files.length == 0" @click="ok">{{ $t('ok') }}</button>
<div class="rqsvbumu">
<x-drive
ref="browser"
class="browser"
:multiple="multiple"
@selected="onSelected"
@change-selection="onChangeSelection"
/>
<div class="footer">
<button class="upload" :title="$t('title')" @click="upload"><fa icon="upload"/></button>
<ui-button inline @click="cancel" style="margin-right:16px;">{{ $t('cancel') }}</ui-button>
<ui-button inline primary :disabled="multiple && files.length == 0" @click="ok">{{ $t('ok') }}</ui-button>
</div>
</div>
</mk-window>
</template>
@@ -60,120 +62,67 @@ export default Vue.extend({
});
</script>
<style lang="stylus" module>
.title
> [data-icon]
margin-right 4px
<style lang="stylus" scoped>
.jqiaciqv
.title
> [data-icon]
margin-right 4px
.count
margin-left 8px
opacity 0.7
.browser
height calc(100% - 72px)
.footer
height 72px
background var(--primaryLighten95)
.upload
display inline-block
position absolute
top 8px
left 16px
cursor pointer
padding 0
margin 8px 4px 0 0
width 40px
height 40px
font-size 1em
color var(--primaryAlpha05)
background transparent
outline none
border solid 1px transparent
border-radius 4px
&:hover
background transparent
border-color var(--primaryAlpha03)
&:active
color var(--primaryAlpha06)
background transparent
border-color var(--primaryAlpha05)
//box-shadow 0 2px 4px rgba(var(--primaryDarken50), 0.15) inset
&:focus
&:after
content ""
pointer-events none
position absolute
top -5px
right -5px
bottom -5px
left -5px
border 2px solid var(--primaryAlpha03)
border-radius 8px
.ok
.cancel
display block
position absolute
bottom 16px
cursor pointer
padding 0
margin 0
width 120px
height 40px
font-size 1em
outline none
border-radius 4px
&:focus
&:after
content ""
pointer-events none
position absolute
top -5px
right -5px
bottom -5px
left -5px
border 2px solid var(--primaryAlpha03)
border-radius 8px
&:disabled
.count
margin-left 8px
opacity 0.7
cursor default
.ok
right 16px
color var(--primaryForeground)
background linear-gradient(to bottom, var(--primaryLighten25) 0%, var(--primaryLighten10) 100%)
border solid 1px var(--primaryLighten15)
.rqsvbumu
display flex
flex-direction column
height 100%
&:not(:disabled)
font-weight bold
.browser
flex 1
overflow auto
&:hover:not(:disabled)
background linear-gradient(to bottom, var(--primaryLighten8) 0%, var(--primaryDarken8) 100%)
border-color var(--primary)
.footer
padding 16px
background var(--desktopPostFormBg)
text-align right
&:active:not(:disabled)
background var(--primary)
border-color var(--primary)
.upload
display inline-block
position absolute
top 8px
left 16px
cursor pointer
padding 0
margin 8px 4px 0 0
width 40px
height 40px
font-size 1em
color var(--primaryAlpha05)
background transparent
outline none
border solid 1px transparent
border-radius 4px
.cancel
right 148px
color #888
background linear-gradient(to bottom, #ffffff 0%, #f5f5f5 100%)
border solid 1px #e2e2e2
&:hover
background transparent
border-color var(--primaryAlpha03)
&:hover
background linear-gradient(to bottom, #f9f9f9 0%, #ececec 100%)
border-color #dcdcdc
&:active
color var(--primaryAlpha06)
background transparent
border-color var(--primaryAlpha05)
//box-shadow 0 2px 4px rgba(var(--primaryDarken50), 0.15) inset
&:active
background #ececec
border-color #dcdcdc
&:focus
&:after
content ""
pointer-events none
position absolute
top -5px
right -5px
bottom -5px
left -5px
border 2px solid var(--primaryAlpha03)
border-radius 8px
</style>

View File

@@ -1,17 +1,19 @@
<template>
<mk-window ref="window" is-modal width="800px" height="500px" @closed="destroyDom">
<span slot="header">
<span :class="$style.title">{{ $t('choose-prompt') }}</span>
<span>{{ $t('choose-prompt') }}</span>
</span>
<x-drive
ref="browser"
:class="$style.browser"
:multiple="false"
/>
<div :class="$style.footer">
<button :class="$style.cancel" @click="cancel">{{ $t('cancel') }}</button>
<button :class="$style.ok" @click="ok">{{ $t('ok') }}</button>
<div class="hllkpxxu">
<x-drive
ref="browser"
class="browser"
:multiple="false"
/>
<div class="footer">
<ui-button inline @click="cancel" style="margin-right:16px;">{{ $t('cancel') }}</ui-button>
<ui-button inline @click="ok" primary>{{ $t('ok') }}</ui-button>
</div>
</div>
</mk-window>
</template>
@@ -36,79 +38,19 @@ export default Vue.extend({
});
</script>
<style lang="stylus" module>
<style lang="stylus" scoped>
.hllkpxxu
display flex
flex-direction column
height 100%
.browser
flex 1
overflow auto
.title
> [data-icon]
margin-right 4px
.browser
height calc(100% - 72px)
.footer
height 72px
background var(--primaryLighten95)
.ok
.cancel
display block
position absolute
bottom 16px
cursor pointer
padding 0
margin 0
width 120px
height 40px
font-size 1em
outline none
border-radius 4px
&:focus
&:after
content ""
pointer-events none
position absolute
top -5px
right -5px
bottom -5px
left -5px
border 2px solid var(--primaryAlpha03)
border-radius 8px
&:disabled
opacity 0.7
cursor default
.ok
right 16px
color var(--primaryForeground)
background linear-gradient(to bottom, var(--primaryLighten25) 0%, var(--primaryLighten10) 100%)
border solid 1px var(--primaryLighten15)
&:not(:disabled)
font-weight bold
&:hover:not(:disabled)
background linear-gradient(to bottom, var(--primaryLighten8) 0%, var(--primaryDarken8) 100%)
border-color var(--primary)
&:active:not(:disabled)
background var(--primary)
border-color var(--primary)
.cancel
right 148px
color #888
background linear-gradient(to bottom, #ffffff 0%, #f5f5f5 100%)
border solid 1px #e2e2e2
&:hover
background linear-gradient(to bottom, #f9f9f9 0%, #ececec 100%)
border-color #dcdcdc
&:active
background #ececec
border-color #dcdcdc
.footer
padding 16px
background var(--desktopPostFormBg)
text-align right
</style>

View File

@@ -66,6 +66,7 @@ import XFolder from './drive.folder.vue';
import XFile from './drive.file.vue';
import contains from '../../../common/scripts/contains';
import { url } from '../../../config';
import { faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons';
export default Vue.extend({
i18n: i18n('desktop/views/components/drive.vue'),
@@ -149,7 +150,7 @@ export default Vue.extend({
}, {
type: 'item',
text: this.$t('contextmenu.url-upload'),
icon: 'cloud-upload-alt',
icon: faCloudUploadAlt,
action: this.urlUpload
}]);
},

View File

@@ -23,8 +23,8 @@ export default Vue.extend({
computed: {
popout(): string {
return this.game
? `${url}/reversi/${this.game.id}`
: `${url}/reversi`;
? `${url}/games/reversi/${this.game.id}`
: `${url}/games/reversi`;
}
}
});

View File

@@ -9,7 +9,6 @@ import subNoteContent from './sub-note-content.vue';
import window from './window.vue';
import noteFormWindow from './post-form-window.vue';
import renoteFormWindow from './renote-form-window.vue';
import mediaImage from './media-image.vue';
import mediaVideo from './media-video.vue';
import notifications from './notifications.vue';
import noteForm from './post-form.vue';
@@ -32,7 +31,6 @@ Vue.component('mk-sub-note-content', subNoteContent);
Vue.component('mk-window', window);
Vue.component('mk-post-form-window', noteFormWindow);
Vue.component('mk-renote-form-window', renoteFormWindow);
Vue.component('mk-media-image', mediaImage);
Vue.component('mk-media-video', mediaVideo);
Vue.component('mk-notifications', notifications);
Vue.component('mk-post-form', noteForm);

View File

@@ -1,81 +0,0 @@
<template>
<div class="ldwbgwstjsdgcjruamauqdrffetqudry" v-if="image.isSensitive && hide && !$store.state.device.alwaysShowNsfw" @click="hide = false">
<div>
<b><fa icon="exclamation-triangle"/> {{ $t('sensitive') }}</b>
<span>{{ $t('click-to-show') }}</span>
</div>
</div>
<a class="lcjomzwbohoelkxsnuqjiaccdbdfiazy" v-else
:href="image.url"
@click.prevent="onClick"
:style="style"
:title="image.name"
></a>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import ImageViewer from '../../../common/views/components/image-viewer.vue';
export default Vue.extend({
i18n: i18n('desktop/views/components/media-image.vue'),
props: {
image: {
type: Object,
required: true
},
raw: {
default: false
}
},
data() {
return {
hide: true
};
},
computed: {
style(): any {
return {
'background-color': this.image.properties.avgColor && this.image.properties.avgColor.length == 3 ? `rgb(${this.image.properties.avgColor.join(',')})` : 'transparent',
'background-image': this.raw ? `url(${this.image.url})` : `url(${this.image.thumbnailUrl})`
};
}
},
methods: {
onClick() {
this.$root.new(ImageViewer, {
image: this.image
});
}
}
});
</script>
<style lang="stylus" scoped>
.lcjomzwbohoelkxsnuqjiaccdbdfiazy
display block
cursor zoom-in
overflow hidden
width 100%
height 100%
background-position center
background-size contain
background-repeat no-repeat
.ldwbgwstjsdgcjruamauqdrffetqudry
display flex
justify-content center
align-items center
background #111
color #fff
> div
display table-cell
text-align center
font-size 12px
> *
display block
</style>

View File

@@ -222,6 +222,12 @@ export default Vue.extend({
});
}
// keep cw when reply
if (this.$store.state.settings.keepCw && this.reply && this.reply.cw) {
this.useCw = true;
this.cw = this.reply.cw;
}
this.$nextTick(() => {
// 書きかけの投稿を復元
if (!this.instant && !this.mention) {

View File

@@ -31,7 +31,12 @@
<ui-switch v-model="autoPopout">{{ $t('auto-popout') }}
<span slot="desc">{{ $t('auto-popout-desc') }}</span>
</ui-switch>
<ui-switch v-model="deckNav">{{ $t('deck-nav') }}<span slot="desc">{{ $t('deck-nav-desc') }}</span></ui-switch>
<ui-switch v-model="deckNav">{{ $t('deck-nav') }}
<span slot="desc">{{ $t('deck-nav-desc') }}</span>
</ui-switch>
<ui-switch v-model="keepCw">{{ $t('keep-cw') }}
<span slot="desc">{{ $t('keep-cw-desc') }}</span>
</ui-switch>
</section>
<section>
@@ -336,6 +341,11 @@ export default Vue.extend({
set(value) { this.$store.commit('settings/set', { key: 'deckNav', value }); }
},
keepCw: {
get() { return this.$store.state.settings.keepCw; },
set(value) { this.$store.commit('settings/set', { key: 'keepCw', value }); }
},
darkmode: {
get() { return this.$store.state.device.darkmode; },
set(value) { this.$store.commit('device/set', { key: 'darkmode', value }); }

View File

@@ -19,10 +19,10 @@ export default Vue.extend({
methods: {
nav(game, actualNav) {
if (actualNav) {
this.$router.push(`/reversi/${game.id}`);
this.$router.push(`/games/reversi/${game.id}`);
} else {
// TODO: https://github.com/vuejs/vue-router/issues/703
this.$router.push(`/reversi/${game.id}`);
this.$router.push(`/games/reversi/${game.id}`);
}
}
}

View File

@@ -9,7 +9,6 @@
<router-link class="name" :to="friend | userPage" v-user-preview="friend.id">{{ friend.name }}</router-link>
<p class="username">@{{ friend | acct }}</p>
</div>
<mk-follow-button class="follow-button" :user="friend"/>
</div>
</template>
<p class="empty" v-if="!fetching && users.length == 0">{{ $t('no-users') }}</p>
@@ -110,9 +109,4 @@ export default Vue.extend({
color var(--text)
opacity 0.7
> .follow-button
position absolute
top 16px
right 16px
</style>

View File

@@ -50,8 +50,7 @@ export default Vue.extend({
text-align center
line-height 24px
font-size 0.8em
color #71afc7
background #eefaff
color var(--text)
border-radius 4px
> .action-form

View File

@@ -179,6 +179,7 @@ export default define({
this.$root.api('notes/create', {
text: this.text == '' ? undefined : this.text,
fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
visibility: this.$store.state.settings.defaultNoteVisibility
}).then(data => {
this.clear();
}).catch(err => {

View File

@@ -124,7 +124,6 @@ import {
faMapMarker,
faRobot,
faHourglassHalf,
faAlignLeft,
faGavel
} from '@fortawesome/free-solid-svg-icons';
@@ -257,7 +256,6 @@ library.add(
faMapMarker,
faRobot,
faHourglassHalf,
faAlignLeft,
faGavel,
farBell,
@@ -389,6 +387,18 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS]) => void,
});
//#endregion
// Reapply current theme
try {
const themeName = os.store.state.device.darkmode ? os.store.state.device.darkTheme : os.store.state.device.lightTheme;
const themes = os.store.state.device.themes.concat(builtinThemes);
const theme = themes.find(t => t.id == themeName);
if (theme) {
applyTheme(theme);
}
} catch (e) {
console.log(`Cannot reapply theme. ${e}`);
}
//#region shadow
const shadow = '0 3px 8px rgba(0, 0, 0, 0.2)';
const shadowRight = '4px 0 4px rgba(0, 0, 0, 0.1)';

View File

@@ -3,7 +3,6 @@ import Vue from 'vue';
import ui from './ui.vue';
import note from './note.vue';
import notes from './notes.vue';
import mediaImage from './media-image.vue';
import mediaVideo from './media-video.vue';
import notePreview from './note-preview.vue';
import subNoteContent from './sub-note-content.vue';
@@ -24,7 +23,6 @@ import postForm from './post-form.vue';
Vue.component('mk-ui', ui);
Vue.component('mk-note', note);
Vue.component('mk-notes', notes);
Vue.component('mk-media-image', mediaImage);
Vue.component('mk-media-video', mediaVideo);
Vue.component('mk-note-preview', notePreview);
Vue.component('mk-sub-note-content', subNoteContent);

View File

@@ -28,7 +28,7 @@
</div>
</x-draggable>
</div>
<mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="onPollUpdate()"/>
<mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="onPollUpdate()"/>
<mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/>
<footer>
<button class="upload" @click="chooseFile"><fa icon="upload"/></button>
@@ -70,8 +70,7 @@ import extractMentions from '../../../../../misc/extract-mentions';
export default Vue.extend({
i18n: i18n('mobile/views/components/post-form.vue'),
components: {
XDraggable,
MkVisibilityChooser
XDraggable
},
props: {
@@ -211,6 +210,12 @@ export default Vue.extend({
});
}
// keep cw when reply
if (this.$store.state.settings.keepCw && this.reply && this.reply.cw) {
this.useCw = true;
this.cw = this.reply.cw;
}
this.focus();
this.$nextTick(() => {

View File

@@ -20,10 +20,10 @@ export default Vue.extend({
methods: {
nav(game, actualNav) {
if (actualNav) {
this.$router.push(`/reversi/${game.id}`);
this.$router.push(`/games/reversi/${game.id}`);
} else {
// TODO: https://github.com/vuejs/vue-router/issues/703
this.$router.push(`/reversi/${game.id}`);
this.$router.push(`/games/reversi/${game.id}`);
}
}
}

View File

@@ -66,6 +66,7 @@
<section>
<ui-switch v-model="fetchOnScroll">{{ $t('fetch-on-scroll') }}</ui-switch>
<ui-switch v-model="keepCw">{{ $t('keep-cw') }}</ui-switch>
<ui-switch v-model="disableViaMobile">{{ $t('disable-via-mobile') }}</ui-switch>
<ui-switch v-model="loadRawImages">{{ $t('load-raw-images') }}</ui-switch>
<ui-switch v-model="loadRemoteMedia">{{ $t('load-remote-media') }}</ui-switch>
@@ -247,6 +248,11 @@ export default Vue.extend({
set(value) { this.$store.dispatch('settings/set', { key: 'fetchOnScroll', value }); }
},
keepCw: {
get() { return this.$store.state.settings.keepCw; },
set(value) { this.$store.dispatch('settings/set', { key: 'keepCw', value }); }
},
rememberNoteVisibility: {
get() { return this.$store.state.settings.rememberNoteVisibility; },
set(value) { this.$store.dispatch('settings/set', { key: 'rememberNoteVisibility', value }); }

View File

@@ -12,6 +12,7 @@ const defaultSettings = {
mobileHome: [],
deck: null,
deckNav: true,
keepCw: false,
tagTimelines: [],
fetchOnScroll: true,
showMaps: true,

View File

@@ -3,6 +3,8 @@
html
--primary #fb4e4e
--link #fb4e4e
--linkTapHighlight #fb4e4eb3
body
margin 0

View File

@@ -100,20 +100,6 @@ export default class Reversi {
return count(WHITE, this.board);
}
/**
* 黒石の比率
*/
public get blackP() {
return this.blackCount == 0 && this.whiteCount == 0 ? 0 : this.blackCount / (this.blackCount + this.whiteCount);
}
/**
* 白石の比率
*/
public get whiteP() {
return this.blackCount == 0 && this.whiteCount == 0 ? 0 : this.whiteCount / (this.blackCount + this.whiteCount);
}
public transformPosToXy(pos: number): number[] {
const x = pos % this.mapWidth;
const y = Math.floor(pos / this.mapWidth);

View File

@@ -55,6 +55,18 @@ export default (tokens: MfmForest, mentionedRemoteUsers: INote['mentionedRemoteU
return el;
},
spin(token) {
const el = doc.createElement('i');
appendChildren(token.children, el);
return el;
},
flip(token) {
const el = doc.createElement('span');
appendChildren(token.children, el);
return el;
},
blockCode(token) {
const pre = doc.createElement('pre');
const inner = doc.createElement('code');
@@ -87,7 +99,13 @@ export default (tokens: MfmForest, mentionedRemoteUsers: INote['mentionedRemoteU
return el;
},
math(token) {
mathInline(token) {
const el = doc.createElement('code');
el.textContent = token.node.props.formula;
return el;
},
mathBlock(token) {
const el = doc.createElement('code');
el.textContent = token.node.props.formula;
return el;

View File

@@ -91,6 +91,7 @@ const mfm = P.createLanguage({
root: r => P.alt(
r.big,
r.small,
r.spin,
r.bold,
r.strike,
r.italic,
@@ -101,9 +102,11 @@ const mfm = P.createLanguage({
r.hashtag,
r.emoji,
r.blockCode,
r.flip,
r.inlineCode,
r.quote,
r.math,
r.mathInline,
r.mathBlock,
r.search,
r.title,
r.center,
@@ -121,7 +124,8 @@ const mfm = P.createLanguage({
r.mention,
r.hashtag,
r.emoji,
r.math,
r.mathInline,
r.spin,
r.text
).atLeast(1).tryParse(x), {})),
//#endregion
@@ -135,7 +139,16 @@ const mfm = P.createLanguage({
r.mention,
r.hashtag,
r.emoji,
r.math,
r.mathInline,
r.text
).atLeast(1).tryParse(x), {})),
//#endregion
//#region Spin
spin: r =>
P.regexp(/<spin>(.+?)<\/spin>/, 1)
.map(x => createTree('spin', P.alt(
r.emoji,
r.text
).atLeast(1).tryParse(x), {})),
//#endregion
@@ -162,6 +175,7 @@ const mfm = P.createLanguage({
r.hashtag,
r.url,
r.link,
r.flip,
r.emoji,
r.text
).atLeast(1).tryParse(x), {})),
@@ -173,6 +187,7 @@ const mfm = P.createLanguage({
.map(x => createTree('center', P.alt(
r.big,
r.small,
r.spin,
r.bold,
r.strike,
r.italic,
@@ -180,9 +195,10 @@ const mfm = P.createLanguage({
r.mention,
r.hashtag,
r.emoji,
r.math,
r.mathInline,
r.url,
r.link,
r.flip,
r.text
).atLeast(1).tryParse(x), {})),
//#endregion
@@ -216,6 +232,23 @@ const mfm = P.createLanguage({
}),
//#endregion
//#region Flip
flip: r =>
P.regexp(/<flip>(.+?)<\/flip>/, 1)
.map(x => createTree('flip', P.alt(
r.big,
r.small,
r.spin,
r.bold,
r.strike,
r.link,
r.italic,
r.motion,
r.emoji,
r.text
).atLeast(1).tryParse(x), {})),
//#endregion
//#region Inline code
inlineCode: r =>
P.regexp(/`([^´\n]+?)`/, 1)
@@ -224,7 +257,16 @@ const mfm = P.createLanguage({
//#region Italic
italic: r =>
P.alt(P.regexp(/<i>([\s\S]+?)<\/i>/, 1), P.regexp(/(\*|_)([a-zA-Z0-9]+?[\s\S]*?)\1/, 2))
P.alt(
P.regexp(/<i>([\s\S]+?)<\/i>/, 1),
P((input, i) => {
const text = input.substr(i);
const match = text.match(/^(\*|_)([a-zA-Z0-9]+?[\s\S]*?)\1/);
if (!match) return P.makeFailure(i, 'not a italic');
if (input[i - 1] != null && input[i - 1].match(/[a-z0-9]/i)) return P.makeFailure(i, 'not a italic');
return P.makeSuccess(i + match[0].length, match[2]);
})
)
.map(x => createTree('italic', P.alt(
r.bold,
r.strike,
@@ -232,6 +274,7 @@ const mfm = P.createLanguage({
r.hashtag,
r.url,
r.link,
r.flip,
r.emoji,
r.text
).atLeast(1).tryParse(x), {})),
@@ -252,6 +295,7 @@ const mfm = P.createLanguage({
return createTree('link', P.alt(
r.big,
r.small,
r.spin,
r.bold,
r.strike,
r.italic,
@@ -265,10 +309,16 @@ const mfm = P.createLanguage({
}),
//#endregion
//#region Math
math: r =>
//#region Math (inline)
mathInline: r =>
P.regexp(/\\\((.+?)\\\)/, 1)
.map(x => createLeaf('math', { formula: x })),
.map(x => createLeaf('mathInline', { formula: x })),
//#endregion
//#region Math (block)
mathBlock: r =>
P.regexp(/\\\[([\s\S]+?)\\\]/, 1)
.map(x => createLeaf('mathBlock', { formula: x.trim() })),
//#endregion
//#region Mention
@@ -295,6 +345,7 @@ const mfm = P.createLanguage({
.map(x => createTree('motion', P.alt(
r.bold,
r.small,
r.spin,
r.strike,
r.italic,
r.mention,
@@ -302,7 +353,8 @@ const mfm = P.createLanguage({
r.emoji,
r.url,
r.link,
r.math,
r.flip,
r.mathInline,
r.text
).atLeast(1).tryParse(x), {})),
//#endregion
@@ -340,6 +392,7 @@ const mfm = P.createLanguage({
r.hashtag,
r.url,
r.link,
r.flip,
r.emoji,
r.text
).atLeast(1).tryParse(x), {})),
@@ -349,18 +402,20 @@ const mfm = P.createLanguage({
title: r =>
newline.then(P((input, i) => {
const text = input.substr(i);
const match = text.match(/^((【|\[)(.+?)(】|]))(\n|$)/);
const match = text.match(/^([【\[]([^【\[】\]\n]+?)[】\]])(\n|$)/);
if (!match) return P.makeFailure(i, 'not a title');
const q = match[1].trim().substring(1, match[1].length - 1);
const q = match[2].trim();
const contents = P.alt(
r.big,
r.small,
r.spin,
r.bold,
r.strike,
r.italic,
r.motion,
r.url,
r.link,
r.flip,
r.mention,
r.hashtag,
r.emoji,

View File

@@ -1,343 +0,0 @@
import { capitalize, toUpperCase } from '../prelude/string';
function escape(text: string) {
return text
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;');
}
// 文字数が多い順にソートします
// そうしないと、「function」という文字列が与えられたときに「func」が先にマッチしてしまう可能性があるためです
const _keywords = [
'true',
'false',
'null',
'nil',
'undefined',
'void',
'var',
'const',
'let',
'mut',
'dim',
'if',
'then',
'else',
'switch',
'match',
'case',
'default',
'for',
'each',
'in',
'while',
'loop',
'continue',
'break',
'do',
'goto',
'next',
'end',
'sub',
'throw',
'try',
'catch',
'finally',
'enum',
'delegate',
'function',
'func',
'fun',
'fn',
'return',
'yield',
'async',
'await',
'require',
'include',
'import',
'imports',
'export',
'exports',
'from',
'as',
'using',
'use',
'internal',
'module',
'namespace',
'where',
'select',
'struct',
'union',
'new',
'delete',
'this',
'super',
'base',
'class',
'interface',
'abstract',
'static',
'public',
'private',
'protected',
'virtual',
'partial',
'override',
'extends',
'implements',
'constructor'
];
const keywords = _keywords
.concat(_keywords.map(capitalize))
.concat(_keywords.map(toUpperCase))
.sort((a, b) => b.length - a.length);
const symbols = [
'=',
'+',
'-',
'*',
'/',
'%',
'~',
'^',
'&',
'|',
'>',
'<',
'!',
'?'
];
type Token = {
html: string
next: number
};
type Element = (code: string, i: number, source: string) => (Token | null);
const elements: Element[] = [
// comment
code => {
if (code.substr(0, 2) != '//') return null;
const match = code.match(/^\/\/(.+?)(\n|$)/);
if (!match) return null;
const comment = match[0];
return {
html: `<span class="comment">${escape(comment)}</span>`,
next: comment.length
};
},
// block comment
code => {
const match = code.match(/^\/\*([\s\S]+?)\*\//);
if (!match) return null;
return {
html: `<span class="comment">${escape(match[0])}</span>`,
next: match[0].length
};
},
// string
code => {
if (!/^['"`]/.test(code)) return null;
const begin = code[0];
let str = begin;
let thisIsNotAString = false;
for (let i = 1; i < code.length; i++) {
const char = code[i];
if (char == '\\') {
str += char;
str += code[i + 1] || '';
i++;
continue;
} else if (char == begin) {
str += char;
break;
} else if (char == '\n' || i == (code.length - 1)) {
thisIsNotAString = true;
break;
} else {
str += char;
}
}
if (thisIsNotAString) {
return null;
} else {
return {
html: `<span class="string">${escape(str)}</span>`,
next: str.length
};
}
},
// regexp
code => {
if (code[0] != '/') return null;
let regexp = '';
let thisIsNotARegexp = false;
for (let i = 1; i < code.length; i++) {
const char = code[i];
if (char == '\\') {
regexp += char;
regexp += code[i + 1] || '';
i++;
continue;
} else if (char == '/') {
break;
} else if (char == '\n' || i == (code.length - 1)) {
thisIsNotARegexp = true;
break;
} else {
regexp += char;
}
}
if (thisIsNotARegexp) return null;
if (regexp == '') return null;
if (regexp.startsWith(' ') && regexp.endsWith(' ')) return null;
return {
html: `<span class="regexp">/${escape(regexp)}/</span>`,
next: regexp.length + 2
};
},
// label
code => {
if (code[0] != '@') return null;
const match = code.match(/^@([a-zA-Z_-]+?)\n/);
if (!match) return null;
const label = match[0];
return {
html: `<span class="label">${label}</span>`,
next: label.length
};
},
// number
(code, i, source) => {
const prev = source[i - 1];
if (prev && /[a-zA-Z]/.test(prev)) return null;
if (!/^[\-\+]?[0-9\.]+/.test(code)) return null;
const match = code.match(/^[\-\+]?[0-9\.]+/)[0];
if (match) {
return {
html: `<span class="number">${match}</span>`,
next: match.length
};
} else {
return null;
}
},
// nan
(code, i, source) => {
const prev = source[i - 1];
if (prev && /[a-zA-Z]/.test(prev)) return null;
if (code.substr(0, 3) == 'NaN') {
return {
html: `<span class="nan">NaN</span>`,
next: 3
};
} else {
return null;
}
},
// method
code => {
const match = code.match(/^([a-zA-Z_-]+?)\(/);
if (!match) return null;
if (match[1] == '-') return null;
return {
html: `<span class="method">${match[1]}</span>`,
next: match[1].length
};
},
// property
(code, i, source) => {
const prev = source[i - 1];
if (prev != '.') return null;
const match = code.match(/^[a-zA-Z0-9_-]+/);
if (!match) return null;
return {
html: `<span class="property">${match[0]}</span>`,
next: match[0].length
};
},
// keyword
(code, i, source) => {
const prev = source[i - 1];
if (prev && /[a-zA-Z]/.test(prev)) return null;
const match = keywords.filter(k => code.substr(0, k.length) == k)[0];
if (match) {
if (/^[a-zA-Z]/.test(code.substr(match.length))) return null;
return {
html: `<span class="keyword ${match}">${match}</span>`,
next: match.length
};
} else {
return null;
}
},
// symbol
code => {
const match = symbols.filter(s => code[0] == s)[0];
if (match) {
return {
html: `<span class="symbol">${match}</span>`,
next: 1
};
} else {
return null;
}
}
];
// TODO: specify lang
export default (source: string, lang?: string): string => {
let code = source;
let html = '';
let i = 0;
function push(token: Token) {
html += token.html;
code = code.substr(token.next);
i += token.next;
}
while (code != '') {
const parsed = elements.some(el => {
const e = el(code, i, source);
if (e) {
push(e);
return true;
} else {
return false;
}
});
if (!parsed) {
push({
html: escape(code[0]),
next: 1
});
}
}
return html;
};

View File

@@ -4,6 +4,7 @@ import db from '../db/mongodb';
const PollVote = db.get<IPollVote>('pollVotes');
PollVote.createIndex('userId');
PollVote.createIndex('noteId');
PollVote.createIndex(['userId', 'noteId'], { unique: true });
export default PollVote;
export interface IPollVote {

View File

@@ -0,0 +1,5 @@
export type IIdentifier = {
type: string;
name: string;
value: string;
};

View File

@@ -19,6 +19,7 @@ import getDriveFileUrl from '../../../misc/get-drive-file-url';
import { IEmoji } from '../../../models/emoji';
import { ITag } from './tag';
import Following from '../../../models/following';
import { IIdentifier } from './identifier';
const log = debug('misskey:activitypub');
@@ -137,9 +138,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
const host = toUnicode(new URL(object.id).hostname.toLowerCase());
const fields = await extractFields(person.attachment).catch(e => {
console.log(`cat not extract fields: ${e}`);
});
const { fields, services } = analyzeAttachments(person.attachment);
const isBot = object.type == 'Service';
@@ -171,7 +170,8 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
uri: person.id,
url: person.url,
fields,
isBot: isBot,
...services,
isBot,
isCat: (person as any).isCat === true
}) as IRemoteUser;
} catch (e) {
@@ -332,9 +332,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
const emojiNames = emojis.map(emoji => emoji.name);
const fields = await extractFields(person.attachment).catch(e => {
console.log(`cat not extract fields: ${e}`);
});
const { fields, services } = analyzeAttachments(person.attachment);
const updates = {
lastFetchedAt: new Date(),
@@ -350,6 +348,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
url: person.url,
endpoints: person.endpoints,
fields,
...services,
isBot: object.type == 'Service',
isCat: (person as any).isCat === true,
isLocked: person.manuallyApprovesFollowers,
@@ -413,16 +412,61 @@ export async function resolvePerson(uri: string, verifier?: string, resolver?: R
return await createPerson(uri, resolver);
}
export async function extractFields(attachments: ITag[]) {
if (!attachments) return [];
const isPropertyValue = (x: {
type: string,
name?: string,
value?: string
}) =>
x &&
x.type === 'PropertyValue' &&
typeof x.name === 'string' &&
typeof x.value === 'string';
return attachments.filter(a => a.type === 'PropertyValue' && a.name && a.value)
.map(a => {
return {
name: a.name,
value: htmlToMFM(a.value)
};
});
const services: {
[x: string]: (id: string, username: string) => any
} = {
'misskey:authentication:twitter': (userId, screenName) => ({ userId, screenName }),
'misskey:authentication:github': (id, login) => ({ id, login }),
'misskey:authentication:discord': (id, name) => $discord(id, name)
};
const $discord = (id: string, name: string) => {
if (typeof name !== 'string')
name = 'unknown#0000';
const [username, discriminator] = name.split('#');
return { id, username, discriminator };
};
function addService(target: { [x: string]: any }, source: IIdentifier) {
const service = services[source.name];
if (typeof source.value !== 'string')
source.value = 'unknown';
const [id, username] = source.value.split('@');
if (service)
target[source.name.split(':')[2]] = service(id, username);
}
export function analyzeAttachments(attachments: ITag[]) {
const fields: {
name: string,
value: string
}[] = [];
const services: { [x: string]: any } = {};
if (Array.isArray(attachments))
for (const attachment of attachments.filter(isPropertyValue))
if (isPropertyValue(attachment.identifier))
addService(services, attachment.identifier);
else
fields.push({
name: attachment.name,
value: htmlToMFM(attachment.value)
});
return { fields, services };
}
export async function updateFeatured(userId: mongo.ObjectID) {

View File

@@ -1,4 +1,5 @@
import { IIcon } from './icon';
import { IIdentifier } from './identifier';
/***
* tag (ActivityPub)
@@ -10,4 +11,5 @@ export type ITag = {
value?: string;
updated?: Date;
icon?: IIcon;
identifier?: IIdentifier;
};

View File

@@ -98,7 +98,7 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
if (text == null) text = '';
const url = `${config.url}/notes/${note._id}`;
// TODO: i18n
text += `\n\n[リモートで投票を見る](${url})`;
text += `\n[リモートで結果を表示](${url})`;
question = `${config.url}/questions/${note._id}`;
}
@@ -109,8 +109,10 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
// Provides choices as text for AP
if (note.poll != null) {
const cs = note.poll.choices.map(c => `${c.id}: ${c.text}`);
apText += '\n';
apText += '\n----------------------------------------\n';
apText += cs.join('\n');
apText += '\n----------------------------------------\n';
apText += '番号を返信して投票';
}
if (quote) {

View File

@@ -7,6 +7,7 @@ import parse from '../../../mfm/parse';
import DriveFile from '../../../models/drive-file';
import { getEmojis } from './note';
import renderEmoji from './emoji';
import { IIdentifier } from '../models/identifier';
export default async (user: ILocalUser) => {
const id = `${config.url}/users/${user._id}`;
@@ -20,14 +21,20 @@ export default async (user: ILocalUser) => {
type: string,
name: string,
value: string,
verified_at?: string
verified_at?: string,
identifier?: IIdentifier
}[] = [];
if (user.twitter) {
attachment.push({
type: 'PropertyValue',
name: 'Twitter',
value: `<a href="https://twitter.com/intent/user?user_id=${user.twitter.userId}" rel="me nofollow noopener" target="_blank"><span>@${user.twitter.screenName}</span></a>`
value: `<a href="https://twitter.com/intent/user?user_id=${user.twitter.userId}" rel="me nofollow noopener" target="_blank"><span>@${user.twitter.screenName}</span></a>`,
identifier: {
type: 'PropertyValue',
name: 'misskey:authentication:twitter',
value: `${user.twitter.userId}@${user.twitter.screenName}`
}
});
}
@@ -35,7 +42,12 @@ export default async (user: ILocalUser) => {
attachment.push({
type: 'PropertyValue',
name: 'GitHub',
value: `<a href="https://github.com/${user.github.login}" rel="me nofollow noopener" target="_blank"><span>@${user.github.login}</span></a>`
value: `<a href="https://github.com/${user.github.login}" rel="me nofollow noopener" target="_blank"><span>@${user.github.login}</span></a>`,
identifier: {
type: 'PropertyValue',
name: 'misskey:authentication:github',
value: `${user.github.id}@${user.github.login}`
}
});
}
@@ -43,7 +55,12 @@ export default async (user: ILocalUser) => {
attachment.push({
type: 'PropertyValue',
name: 'Discord',
value: `<a href="https://discordapp.com/users/${user.discord.id}" rel="me nofollow noopener" target="_blank"><span>${user.discord.username}#${user.discord.discriminator}</span></a>`
value: `<a href="https://discordapp.com/users/${user.discord.id}" rel="me nofollow noopener" target="_blank"><span>${user.discord.username}#${user.discord.discriminator}</span></a>`,
identifier: {
type: 'PropertyValue',
name: 'misskey:authentication:discord',
value: `${user.discord.id}@${user.discord.username}#${user.discord.discriminator}`
}
});
}

View File

@@ -6,7 +6,7 @@ import call from './call';
import { IUser } from '../../models/user';
import { IApp } from '../../models/app';
export default async (endpoint: IEndpoint, ctx: Koa.Context) => {
export default async (endpoint: IEndpoint, ctx: Koa.BaseContext) => {
const body = ctx.is('multipart/form-data') ? (ctx.req as any).body : ctx.request.body;
const reply = (x?: any, y?: any) => {

View File

@@ -3,10 +3,9 @@ import { default as User, IUser } from '../../models/user';
import AccessToken from '../../models/access-token';
import isNativeToken from './common/is-native-token';
export default (token: string) => new Promise<[IUser, IApp]>(async (resolve, reject) => {
export default async (token: string): Promise<[IUser, IApp]> => {
if (token == null) {
resolve([null, null]);
return;
return [null, null];
}
if (isNativeToken(token)) {
@@ -15,17 +14,17 @@ export default (token: string) => new Promise<[IUser, IApp]>(async (resolve, rej
.findOne({ token });
if (user === null) {
return reject('user not found');
throw 'user not found';
}
resolve([user, null]);
return [user, null];
} else {
const accessToken = await AccessToken.findOne({
hash: token.toLowerCase()
});
if (accessToken === null) {
return reject('invalid signature');
throw 'invalid signature';
}
const app = await App
@@ -34,6 +33,6 @@ export default (token: string) => new Promise<[IUser, IApp]>(async (resolve, rej
const user = await User
.findOne({ _id: accessToken.userId });
resolve([user, app]);
return [user, app];
}
});
};

View File

@@ -4,37 +4,37 @@ import { IUser } from '../../models/user';
import { IApp } from '../../models/app';
import endpoints from './endpoints';
export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any) => new Promise<any>(async (ok, rej) => {
export default async (endpoint: string, user: IUser, app: IApp, data: any, file?: any) => {
const isSecure = user != null && app == null;
const ep = endpoints.find(e => e.name === endpoint);
if (ep == null) {
return rej('ENDPOINT_NOT_FOUND');
throw 'ENDPOINT_NOT_FOUND';
}
if (ep.meta.secure && !isSecure) {
return rej('ACCESS_DENIED');
throw 'ACCESS_DENIED';
}
if (ep.meta.requireCredential && user == null) {
return rej('CREDENTIAL_REQUIRED');
throw 'CREDENTIAL_REQUIRED';
}
if (ep.meta.requireCredential && user.isSuspended) {
return rej('YOUR_ACCOUNT_HAS_BEEN_SUSPENDED');
throw 'YOUR_ACCOUNT_HAS_BEEN_SUSPENDED';
}
if (ep.meta.requireAdmin && !user.isAdmin) {
return rej('YOU_ARE_NOT_ADMIN');
throw 'YOU_ARE_NOT_ADMIN';
}
if (ep.meta.requireModerator && !user.isAdmin && !user.isModerator) {
return rej('YOU_ARE_NOT_MODERATOR');
throw 'YOU_ARE_NOT_MODERATOR';
}
if (app && ep.meta.kind && !app.permission.some(p => p === ep.meta.kind)) {
return rej('PERMISSION_DENIED');
throw 'PERMISSION_DENIED';
}
if (ep.meta.requireCredential && ep.meta.limit) {
@@ -42,7 +42,7 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any)
await limiter(ep, user); // Rate limit
} catch (e) {
// drop request if limit exceeded
return rej('RATE_LIMIT_EXCEEDED');
throw 'RATE_LIMIT_EXCEEDED';
}
}
@@ -61,16 +61,15 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any)
}
} catch (e) {
if (e && e.name == 'INVALID_PARAM') {
rej({
throw {
code: e.name,
param: e.param,
reason: e.message
});
};
} else {
rej(e);
throw e;
}
return;
}
ok(res);
});
return res;
};

View File

@@ -1,5 +1,6 @@
import * as mongo from 'mongodb';
import Note from "../../../models/note";
import User, { isRemoteUser, isLocalUser } from "../../../models/user";
/**
* Get valied note for API processing
@@ -16,3 +17,44 @@ export async function getValiedNote(noteId: mongo.ObjectID) {
return note;
}
/**
* Get user for API processing
*/
export async function getUser(userId: mongo.ObjectID) {
const user = await User.findOne({
_id: userId
});
if (user == null) {
throw 'user not found';
}
return user;
}
/**
* Get remote user for API processing
*/
export async function getRemoteUser(userId: mongo.ObjectID) {
const user = await getUser(userId);
if (!isRemoteUser(user)) {
throw 'user is not a remote user';
}
return user;
}
/**
* Get local user for API processing
*/
export async function getLocalUser(userId: mongo.ObjectID) {
const user = await getUser(userId);
if (!isLocalUser(user)) {
throw 'user is not a local user';
}
return user;
}

View File

@@ -3,7 +3,7 @@ import * as Koa from 'koa';
import config from '../../../config';
import { ILocalUser } from '../../../models/user';
export default function(ctx: Koa.Context, user: ILocalUser, redirect = false) {
export default function(ctx: Koa.BaseContext, user: ILocalUser, redirect = false) {
if (redirect) {
//#region Cookie
const expires = 1000 * 60 * 60 * 24 * 365; // One Year

View File

@@ -0,0 +1,36 @@
import * as mongo from 'mongodb';
import $ from 'cafy';
import ID, { transform } from '../../../../misc/cafy-id';
import define from '../../define';
import { getRemoteUser } from '../../common/getters';
import { updatePerson } from '../../../../remote/activitypub/models/person';
export const meta = {
desc: {
'ja-JP': '指定されたリモートユーザーの情報を更新します。',
'en-US': 'Update specified remote user information.'
},
requireCredential: true,
requireModerator: true,
params: {
userId: {
validator: $.type(ID),
transform: transform,
desc: {
'ja-JP': '対象のユーザーID',
'en-US': 'The user ID which you want to update'
}
},
}
};
export default define(meta, (ps) => new Promise((res, rej) => {
updatePersonById(ps.userId).then(() => res(), e => rej(e));
}));
async function updatePersonById(userId: mongo.ObjectID) {
const user = await getRemoteUser(userId);
await updatePerson(user.uri);
}

View File

@@ -1,6 +1,6 @@
import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id';
import Note from '../../../../models/note';
import { getFriendIds } from '../../common/get-friends';
import { getFriendIds, getFriends } from '../../common/get-friends';
import { packMany } from '../../../../models/note';
import define from '../../define';
import read from '../../../../services/note/read';
@@ -47,8 +47,28 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
return rej('cannot set sinceId and untilId');
}
// フォローを取得
const followings = await getFriends(user._id);
const visibleQuery = [{
visibility: { $in: [ 'public', 'home' ] }
}, {
// myself (for specified/private)
userId: user._id
}, {
// to me (for specified)
visibleUserIds: { $in: [ user._id ] }
}, {
visibility: 'followers',
userId: { $in: followings.map(f => f.id) }
}];
const query = {
deletedAt: null,
$and: [{
deletedAt: null,
}, {
$or: visibleQuery,
}],
$or: [{
mentions: user._id

View File

@@ -1,6 +1,8 @@
import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id';
import Note, { packMany } from '../../../../models/note';
import define from '../../define';
import Mute from '../../../../models/mute';
import { getFriends } from '../../common/get-friends';
export const meta = {
desc: {
@@ -33,13 +35,47 @@ export const meta = {
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
const [followings, mutedUserIds] = await Promise.all([
// フォローを取得
// Fetch following
user ? getFriends(user._id) : [],
const notes = await Note.find({
replyId: ps.noteId
}, {
limit: ps.limit,
skip: ps.offset
});
// ミュートしているユーザーを取得
user ? (await Mute.find({
muterId: user._id
})).map(m => m.muteeId) : null
]);
const visibleQuery = user == null ? [{
visibility: { $in: [ 'public', 'home' ] }
}] : [{
visibility: { $in: [ 'public', 'home' ] }
}, {
// myself (for specified/private)
userId: user._id
}, {
// to me (for specified)
visibleUserIds: { $in: [ user._id ] }
}, {
visibility: 'followers',
userId: { $in: followings.map(f => f.id) }
}];
const q = {
replyId: ps.noteId,
$or: visibleQuery
} as any;
if (mutedUserIds && mutedUserIds.length > 0) {
q['userId'] = {
$nin: mutedUserIds
};
}
const notes = await Note.find(q, {
limit: ps.limit,
skip: ps.offset
});
res(await packMany(notes, user));
}));

View File

@@ -141,7 +141,7 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
const visibleQuery = user == null ? [{
visibility: { $in: [ 'public', 'home' ] }
}] : [{
visibility: { $in: [ 'public', 'home' ] }
visibility: { $in: [ 'public', 'home', 'followers' ] }
}, {
// myself (for specified/private)
userId: user._id

View File

@@ -4,6 +4,7 @@ import Note, { packMany } from '../../../../models/note';
import User from '../../../../models/user';
import define from '../../define';
import { countIf } from '../../../../prelude/array';
import Following from '../../../../models/following';
export const meta = {
desc: {
@@ -160,13 +161,20 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
return rej('user not found');
}
const isFollowing = me == null ? false : ((await Following.findOne({
followerId: me._id,
followeeId: user._id
})) != null);
//#region Construct query
const sort = { } as any;
const visibleQuery = me == null ? [{
visibility: { $in: [ 'public', 'home' ] }
visibility: { $in: ['public', 'home'] }
}] : [{
visibility: { $in: [ 'public', 'home' ] }
visibility: {
$in: isFollowing ? ['public', 'home', 'followers'] : ['public', 'home']
}
}, {
// myself (for specified/private)
userId: me._id

View File

@@ -2,6 +2,7 @@ import $ from 'cafy'; import ID, { transform } from '../../../../misc/cafy-id';
import define from '../../define';
import User from '../../../../models/user';
import AbuseUserReport from '../../../../models/abuse-user-report';
import { publishAdminStream } from '../../../../stream';
export const meta = {
desc: {
@@ -33,10 +34,6 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
// Lookup user
const user = await User.findOne({
_id: ps.userId
}, {
fields: {
_id: true
}
});
if (user === null) {
@@ -51,12 +48,31 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
return rej('cannot report admin');
}
await AbuseUserReport.insert({
const report = await AbuseUserReport.insert({
createdAt: new Date(),
userId: user._id,
reporterId: me._id,
comment: ps.comment
});
// Publish event to moderators
setTimeout(async () => {
const moderators = await User.find({
$or: [{
isAdmin: true
}, {
isModerator: true
}]
});
for (const moderator of moderators) {
publishAdminStream(moderator._id, 'newAbuseUserReport', {
id: report._id,
userId: report.userId,
reporterId: report.reporterId,
comment: report.comment
});
}
}, 1);
res();
}));

View File

@@ -7,7 +7,7 @@ import { publishMainStream } from '../../../stream';
import signin from '../common/signin';
import config from '../../../config';
export default async (ctx: Koa.Context) => {
export default async (ctx: Koa.BaseContext) => {
ctx.set('Access-Control-Allow-Origin', config.url);
ctx.set('Access-Control-Allow-Credentials', 'true');

View File

@@ -9,7 +9,7 @@ import RegistrationTicket from '../../../models/registration-tickets';
import usersChart from '../../../chart/users';
import fetchMeta from '../../../misc/fetch-meta';
export default async (ctx: Koa.Context) => {
export default async (ctx: Koa.BaseContext) => {
const body = ctx.request.body as any;
const instance = await fetchMeta();

View File

@@ -10,11 +10,11 @@ import uuid = require('uuid');
import signin from '../common/signin';
import fetchMeta from '../../../misc/fetch-meta';
function getUserToken(ctx: Koa.Context) {
function getUserToken(ctx: Koa.BaseContext) {
return ((ctx.headers['cookie'] || '').match(/i=(!\w+)/) || [null, null])[1];
}
function compareOrigin(ctx: Koa.Context) {
function compareOrigin(ctx: Koa.BaseContext) {
function normalizeUrl(url: string) {
return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : '';
}

View File

@@ -10,11 +10,11 @@ import uuid = require('uuid');
import signin from '../common/signin';
import fetchMeta from '../../../misc/fetch-meta';
function getUserToken(ctx: Koa.Context) {
function getUserToken(ctx: Koa.BaseContext) {
return ((ctx.headers['cookie'] || '').match(/i=(!\w+)/) || [null, null])[1];
}
function compareOrigin(ctx: Koa.Context) {
function compareOrigin(ctx: Koa.BaseContext) {
function normalizeUrl(url: string) {
return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : '';
}

View File

@@ -9,11 +9,11 @@ import config from '../../../config';
import signin from '../common/signin';
import fetchMeta from '../../../misc/fetch-meta';
function getUserToken(ctx: Koa.Context) {
function getUserToken(ctx: Koa.BaseContext) {
return ((ctx.headers['cookie'] || '').match(/i=(!\w+)/) || [null, null])[1];
}
function compareOrigin(ctx: Koa.Context) {
function compareOrigin(ctx: Koa.BaseContext) {
function normalizeUrl(url: string) {
return url.endsWith('/') ? url.substr(0, url.length - 1) : url;
}

View File

@@ -0,0 +1,16 @@
import autobind from 'autobind-decorator';
import Channel from '../channel';
export default class extends Channel {
public readonly chName = 'admin';
public static shouldShare = true;
public static requireCredential = true;
@autobind
public async init(params: any) {
// Subscribe admin stream
this.subscriber.on(`adminStream:${this.user._id}`, data => {
this.send(data);
});
}
}

View File

@@ -11,6 +11,7 @@ import messagingIndex from './messaging-index';
import drive from './drive';
import hashtag from './hashtag';
import apLog from './ap-log';
import admin from './admin';
import gamesReversi from './games/reversi';
import gamesReversiGame from './games/reversi-game';
@@ -28,6 +29,7 @@ export default {
drive,
hashtag,
apLog,
admin,
gamesReversi,
gamesReversiGame
};

View File

@@ -19,6 +19,11 @@ export default class extends Channel {
switch (type) {
case 'notification': {
if (mutedUserIds.includes(body.userId)) return;
if (body.note && body.note.isHidden) return;
break;
}
case 'mention': {
if (body.isHidden) return;
break;
}
}

View File

@@ -7,12 +7,12 @@ import DriveFileWebpublic, { getDriveFileWebpublicBucket } from '../../models/dr
const assets = `${__dirname}/../../server/file/assets/`;
const commonReadableHandlerGenerator = (ctx: Koa.Context) => (e: Error): void => {
const commonReadableHandlerGenerator = (ctx: Koa.BaseContext) => (e: Error): void => {
console.error(e);
ctx.status = 500;
};
export default async function(ctx: Koa.Context) {
export default async function(ctx: Koa.BaseContext) {
// Validate id
if (!mongodb.ObjectID.isValid(ctx.params.id)) {
ctx.throw(400, 'incorrect id');
@@ -26,13 +26,13 @@ export default async function(ctx: Koa.Context) {
if (file == null) {
ctx.status = 404;
await send(ctx, '/dummy.png', { root: assets });
await send(ctx as any, '/dummy.png', { root: assets });
return;
}
if (file.metadata.deletedAt) {
ctx.status = 410;
await send(ctx, '/tombstone.png', { root: assets });
await send(ctx as any, '/tombstone.png', { root: assets });
return;
}

View File

@@ -26,7 +26,7 @@ import User from '../models/user';
const app = new Koa();
app.proxy = true;
if (process.env.NODE_ENV != 'production') {
if (!['production', 'test'].includes(process.env.NODE_ENV)) {
// Logger
app.use(logger());
@@ -101,6 +101,19 @@ function createServer() {
}
}
// For testing
export const startServer = () => {
const server = createServer();
// Init stream server
require('./api/streaming')(server);
// Listen
server.listen(config.port);
return server;
};
export default () => new Promise(resolve => {
const server = createServer();

View File

@@ -160,7 +160,7 @@ const extractPropDefRef = (props: any[]) => {
const router = new Router();
router.get('/assets/*', async ctx => {
await send(ctx, ctx.params[0], {
await send(ctx as any, ctx.params[0], {
root: `${__dirname}/../../docs/assets/`,
maxage: ms('1 days')
});

View File

@@ -51,7 +51,7 @@ const router = new Router();
//#region static assets
router.get('/assets/*', async ctx => {
await send(ctx, ctx.path, {
await send(ctx as any, ctx.path, {
root: client,
maxage: ms('7 days'),
immutable: true
@@ -60,21 +60,21 @@ router.get('/assets/*', async ctx => {
// Apple touch icon
router.get('/apple-touch-icon.png', async ctx => {
await send(ctx, '/assets/apple-touch-icon.png', {
await send(ctx as any, '/assets/apple-touch-icon.png', {
root: client
});
});
// ServiceWorker
router.get(/^\/sw\.(.+?)\.js$/, async ctx => {
await send(ctx, `/assets/sw.${ctx.params[0]}.js`, {
await send(ctx as any, `/assets/sw.${ctx.params[0]}.js`, {
root: client
});
});
// Manifest
router.get('/manifest.json', async ctx => {
await send(ctx, '/assets/manifest.json', {
await send(ctx as any, '/assets/manifest.json', {
root: client
});
});

View File

@@ -3,7 +3,7 @@ import * as request from 'request-promise-native';
import summaly from 'summaly';
import fetchMeta from '../../misc/fetch-meta';
module.exports = async (ctx: Koa.Context) => {
module.exports = async (ctx: Koa.BaseContext) => {
const meta = await fetchMeta();
try {

View File

@@ -1,5 +1,5 @@
import es from '../../db/elasticsearch';
import Note, { pack, INote } from '../../models/note';
import Note, { pack, INote, IChoice } from '../../models/note';
import User, { isLocalUser, IUser, isRemoteUser, IRemoteUser, ILocalUser } from '../../models/user';
import { publishMainStream, publishHomeTimelineStream, publishLocalTimelineStream, publishHybridTimelineStream, publishGlobalTimelineStream, publishUserListStream, publishHashtagStream } from '../../stream';
import Following from '../../models/following';
@@ -25,7 +25,7 @@ import notesChart from '../../chart/notes';
import perUserNotesChart from '../../chart/per-user-notes';
import activeUsersChart from '../../chart/active-users';
import { erase } from '../../prelude/array';
import { erase, concat } from '../../prelude/array';
import insertNoteUnread from './unread';
import registerInstance from '../register-instance';
import Instance from '../../models/instance';
@@ -157,7 +157,11 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
if (!tags || !emojis || !mentionedUsers) {
const tokens = data.text ? parse(data.text) : [];
const cwTokens = data.cw ? parse(data.cw) : [];
const combinedTokens = tokens.concat(cwTokens);
const choiceTokens = data.poll && data.poll.choices
? concat((data.poll.choices as IChoice[]).map(choice => parse(choice.text)))
: [];
const combinedTokens = tokens.concat(cwTokens).concat(choiceTokens);
tags = data.apHashtags || extractHashtags(combinedTokens);
@@ -373,8 +377,10 @@ async function publish(user: IUser, note: INote, noteObj: any, reply: INote, ren
if (note.visibility == 'specified') {
for (const u of visibleUsers) {
publishHomeTimelineStream(u._id, detailPackedNote);
publishHybridTimelineStream(u._id, detailPackedNote);
if (!u._id.equals(user._id)) {
publishHomeTimelineStream(u._id, detailPackedNote);
publishHybridTimelineStream(u._id, detailPackedNote);
}
}
}
} else {

View File

@@ -30,12 +30,25 @@ export default async function(user: IUser, note: INote) {
text: null,
tags: [],
fileIds: [],
renoteId: null,
poll: null,
geo: null,
cw: null
}
});
if (note.renoteId) {
Note.update({ _id: note.renoteId }, {
$inc: {
renoteCount: -1,
score: -1
},
$pull: {
_quoteIds: note._id
}
});
}
publishNoteStream(note._id, 'deleted', {
deletedAt: deletedAt
});

View File

@@ -87,6 +87,10 @@ class Publisher {
public publishApLogStream = (log: any): void => {
this.publish('apLog', null, log);
}
public publishAdminStream = (userId: ID, type: string, value?: any): void => {
this.publish(`adminStream:${userId}`, type, typeof value === 'undefined' ? null : value);
}
}
const publisher = new Publisher();
@@ -107,3 +111,4 @@ export const publishHybridTimelineStream = publisher.publishHybridTimelineStream
export const publishGlobalTimelineStream = publisher.publishGlobalTimelineStream;
export const publishHashtagStream = publisher.publishHashtagStream;
export const publishApLogStream = publisher.publishApLogStream;
export const publishAdminStream = publisher.publishAdminStream;

View File

@@ -24,9 +24,7 @@ if (!acct.match(/^\w+@\w/)) {
console.log(`resync ${acct}`);
main(acct).then(() => {
console.log('success');
process.exit(0);
console.log('Done');
}).catch(e => {
console.warn(e);
process.exit(1);
});

View File

@@ -1,12 +1,18 @@
/*
* Tests of API
*
* How to run the tests:
* > mocha test/api.ts --require ts-node/register
*
* To specify test:
* > mocha test/api.ts --require ts-node/register -g 'test name'
*/
import * as http from 'http';
import * as fs from 'fs';
import * as assert from 'chai';
import { async, _signup, _request, _uploadFile, _post, _react, resetDb } from './utils';
assert.use(require('chai-http'));
const expect = assert.expect;
//#region process
@@ -25,87 +31,20 @@ const db = require('../built/db/mongodb').default;
const server = http.createServer(app.callback());
//#region Utilities
const async = (fn: Function) => (done: Function) => {
fn().then(() => {
done();
}, (err: Error) => {
done(err);
});
};
const request = async (endpoint: string, params: any, me?: any): Promise<ChaiHttp.Response> => {
const auth = me ? {
i: me.token
} : {};
const res = await assert.request(server)
.post(endpoint)
.send(Object.assign(auth, params));
return res;
};
const signup = async (params?: any): Promise<any> => {
const q = Object.assign({
username: 'test',
password: 'test'
}, params);
const res = await request('/signup', q);
return res.body;
};
const post = async (user: any, params?: any): Promise<any> => {
const q = Object.assign({
text: 'test'
}, params);
const res = await request('/notes/create', q, user);
return res.body.createdNote;
};
const react = async (user: any, note: any, reaction: string): Promise<any> => {
await request('/notes/reactions/create', {
noteId: note.id,
reaction: reaction
}, user);
};
const uploadFile = async (user: any): Promise<any> => {
const res = await assert.request(server)
.post('/drive/files/create')
.field('i', user.token)
.attach('file', fs.readFileSync(__dirname + '/resources/Lenna.png'), 'Lenna.png');
return res.body;
};
const request = _request(server);
const signup = _signup(request);
const post = _post(request);
const react = _react(request);
const uploadFile = _uploadFile(server);
//#endregion
describe('API', () => {
// Reset database each test
beforeEach(() => new Promise((res) => {
// APIがなにかレスポンスを返した後に、後処理を行う場合があり、
// レスポンスを受け取ってすぐデータベースをリセットすると
// その後処理と競合し(テスト自体は合格するものの)エラーがコンソールに出力され
// 見た目的に気持ち悪くなるので、後処理が終るのを待つために500msくらい待ってから
// データベースをリセットするようにする
setTimeout(async () => {
await Promise.all([
db.get('users').drop(),
db.get('notes').drop(),
db.get('driveFiles.files').drop(),
db.get('driveFiles.chunks').drop(),
db.get('driveFolders').drop(),
db.get('apps').drop(),
db.get('accessTokens').drop(),
db.get('authSessions').drop()
]);
beforeEach(resetDb(db));
res();
}, 500);
}));
after(() => {
server.close();
});
describe('signup', () => {
it('不正なユーザー名でアカウントが作成できない', async(async () => {
@@ -1231,4 +1170,54 @@ describe('API', () => {
expect(res).have.status(400);
}));
});
describe('notes/replies', () => {
it('自分に閲覧権限のない投稿は含まれない', async(async () => {
const alice = await signup({ username: 'alice' });
const bob = await signup({ username: 'bob' });
const carol = await signup({ username: 'carol' });
const alicePost = await post(alice, {
text: 'foo'
});
await post(bob, {
replyId: alicePost.id,
text: 'bar',
visibility: 'specified',
visibleUserIds: [alice.id]
});
const res = await request('/notes/replies', {
noteId: alicePost.id
}, carol);
expect(res).have.status(200);
expect(res.body).be.a('array');
expect(res.body).length(0);
}));
});
describe('notes/timeline', () => {
it('フォロワー限定投稿が含まれる', async(async () => {
const alice = await signup({ username: 'alice' });
const bob = await signup({ username: 'bob' });
await request('/following/create', {
userId: alice.id
}, bob);
const alicePost = await post(alice, {
text: 'foo',
visibility: 'followers'
});
const res = await request('/notes/timeline', {}, bob);
expect(res).have.status(200);
expect(res.body).be.a('array');
expect(res.body).length(1);
expect(res.body[0].id).equals(alicePost.id);
}));
});
});

View File

@@ -1,7 +1,11 @@
/*
* Tests of MFM
*
* How to run the tests:
* > mocha test/mfm.ts --require ts-node/register
*
* To specify test:
* > mocha test/mfm.ts --require ts-node/register -g 'test name'
*/
import * as assert from 'assert';
@@ -148,9 +152,19 @@ describe('MFM', () => {
it('can be analyzed', () => {
const tokens = analyze('@himawari @hima_sub@namori.net お腹ペコい :cat: #yryr');
assert.deepStrictEqual(tokens, [
leaf('mention', { acct: '@himawari', canonical: '@himawari', username: 'himawari', host: null }),
leaf('mention', {
acct: '@himawari',
canonical: '@himawari',
username: 'himawari',
host: null
}),
text(' '),
leaf('mention', { acct: '@hima_sub@namori.net', canonical: '@hima_sub@namori.net', username: 'hima_sub', host: 'namori.net' }),
leaf('mention', {
acct: '@hima_sub@namori.net',
canonical: '@hima_sub@namori.net',
username: 'hima_sub',
host: 'namori.net'
}),
text(' お腹ペコい '),
leaf('emoji', { name: 'cat' }),
text(' '),
@@ -230,6 +244,24 @@ describe('MFM', () => {
]);
});
it('flip', () => {
const tokens = analyze('<flip>foo</flip>');
assert.deepStrictEqual(tokens, [
tree('flip', [
text('foo')
], {}),
]);
});
it('spin', () => {
const tokens = analyze('<spin>:foo:</spin>');
assert.deepStrictEqual(tokens, [
tree('spin', [
leaf('emoji', { name: 'foo' })
], {}),
]);
});
describe('motion', () => {
it('by triple brackets', () => {
const tokens = analyze('(((foo)))');
@@ -276,7 +308,12 @@ describe('MFM', () => {
it('local', () => {
const tokens = analyze('@himawari foo');
assert.deepStrictEqual(tokens, [
leaf('mention', { acct: '@himawari', canonical: '@himawari', username: 'himawari', host: null }),
leaf('mention', {
acct: '@himawari',
canonical: '@himawari',
username: 'himawari',
host: null
}),
text(' foo')
]);
});
@@ -284,7 +321,12 @@ describe('MFM', () => {
it('remote', () => {
const tokens = analyze('@hima_sub@namori.net foo');
assert.deepStrictEqual(tokens, [
leaf('mention', { acct: '@hima_sub@namori.net', canonical: '@hima_sub@namori.net', username: 'hima_sub', host: 'namori.net' }),
leaf('mention', {
acct: '@hima_sub@namori.net',
canonical: '@hima_sub@namori.net',
username: 'hima_sub',
host: 'namori.net'
}),
text(' foo')
]);
});
@@ -292,7 +334,12 @@ describe('MFM', () => {
it('remote punycode', () => {
const tokens = analyze('@hima_sub@xn--q9j5bya.xn--zckzah foo');
assert.deepStrictEqual(tokens, [
leaf('mention', { acct: '@hima_sub@xn--q9j5bya.xn--zckzah', canonical: '@hima_sub@なもり.テスト', username: 'hima_sub', host: 'xn--q9j5bya.xn--zckzah' }),
leaf('mention', {
acct: '@hima_sub@xn--q9j5bya.xn--zckzah',
canonical: '@hima_sub@なもり.テスト',
username: 'hima_sub',
host: 'xn--q9j5bya.xn--zckzah'
}),
text(' foo')
]);
});
@@ -305,11 +352,26 @@ describe('MFM', () => {
const tokens2 = analyze('@a\n@b\n@c');
assert.deepStrictEqual(tokens2, [
leaf('mention', { acct: '@a', canonical: '@a', username: 'a', host: null }),
leaf('mention', {
acct: '@a',
canonical: '@a',
username: 'a',
host: null
}),
text('\n'),
leaf('mention', { acct: '@b', canonical: '@b', username: 'b', host: null }),
leaf('mention', {
acct: '@b',
canonical: '@b',
username: 'b',
host: null
}),
text('\n'),
leaf('mention', { acct: '@c', canonical: '@c', username: 'c', host: null })
leaf('mention', {
acct: '@c',
canonical: '@c',
username: 'c',
host: null
})
]);
const tokens3 = analyze('**x**@a');
@@ -317,24 +379,31 @@ describe('MFM', () => {
tree('bold', [
text('x')
], {}),
leaf('mention', { acct: '@a', canonical: '@a', username: 'a', host: null })
leaf('mention', {
acct: '@a',
canonical: '@a',
username: 'a',
host: null
})
]);
const tokens4 = analyze('@\n@v\n@veryverylongusername' /* \n@toolongtobeasamention */);
const tokens4 = analyze('@\n@v\n@veryverylongusername');
assert.deepStrictEqual(tokens4, [
text('@\n'),
leaf('mention', { acct: '@v', canonical: '@v', username: 'v', host: null }),
leaf('mention', {
acct: '@v',
canonical: '@v',
username: 'v',
host: null
}),
text('\n'),
leaf('mention', { acct: '@veryverylongusername', canonical: '@veryverylongusername', username: 'veryverylongusername', host: null }),
// text('\n@toolongtobeasamention')
leaf('mention', {
acct: '@veryverylongusername',
canonical: '@veryverylongusername',
username: 'veryverylongusername',
host: null
}),
]);
/*
const tokens5 = analyze('@domain_is@valid.example.com\n@domain_is@.invalid\n@domain_is@invali.d\n@domain_is@invali.d\n@domain_is@-invalid.com\n@domain_is@invalid-.com');
assert.deepStrictEqual([
leaf('mention', { acct: '@domain_is@valid.example.com', canonical: '@domain_is@valid.example.com', username: 'domain_is', host: 'valid.example.com' }),
text('\n@domain_is@.invalid\n@domain_is@invali.d\n@domain_is@invali.d\n@domain_is@-invalid.com\n@domain_is@invalid-.com')
], tokens5);
*/
});
});
@@ -832,15 +901,26 @@ describe('MFM', () => {
});
});
it('math', () => {
it('mathInline', () => {
const fomula = 'x = {-b \\pm \\sqrt{b^2-4ac} \\over 2a}';
const text = `\\(${fomula}\\)`;
const tokens = analyze(text);
const content = `\\(${fomula}\\)`;
const tokens = analyze(content);
assert.deepStrictEqual(tokens, [
leaf('math', { formula: fomula })
leaf('mathInline', { formula: fomula })
]);
});
describe('mathBlock', () => {
it('simple', () => {
const fomula = 'x = {-b \\pm \\sqrt{b^2-4ac} \\over 2a}';
const content = `\\[\n${fomula}\n\\]`;
const tokens = analyze(content);
assert.deepStrictEqual(tokens, [
leaf('mathBlock', { formula: fomula })
]);
});
});
it('search', () => {
const tokens1 = analyze('a b c 検索');
assert.deepStrictEqual(tokens1, [
@@ -890,6 +970,20 @@ describe('MFM', () => {
text('after')
]);
});
it('ignore multiple title blocks', () => {
const tokens = analyze('【foo】bar【baz】');
assert.deepStrictEqual(tokens, [
text('【foo】bar【baz】')
]);
});
it('disallow linebreak in title', () => {
const tokens = analyze('【foo\nbar】');
assert.deepStrictEqual(tokens, [
text('【foo\nbar】')
]);
});
});
describe('center', () => {
@@ -962,6 +1056,13 @@ describe('MFM', () => {
text('*foo_'),
]);
});
it('ignore snake_case string', () => {
const tokens = analyze('foo_bar_baz');
assert.deepStrictEqual(tokens, [
text('foo_bar_baz'),
]);
});
});
});

148
test/streaming.ts Normal file
View File

@@ -0,0 +1,148 @@
/*
* Tests of streaming API
*
* How to run the tests:
* > mocha test/streaming.ts --require ts-node/register
*
* To specify test:
* > mocha test/streaming.ts --require ts-node/register -g 'test name'
*/
import * as http from 'http';
import * as WebSocket from 'ws';
import * as assert from 'assert';
import { _signup, _request, _uploadFile, _post, _react, resetDb } from './utils';
//#region process
Error.stackTraceLimit = Infinity;
// During the test the env variable is set to test
process.env.NODE_ENV = 'test';
// Display detail of unhandled promise rejection
process.on('unhandledRejection', console.dir);
//#endregion
const app = require('../built/server/api').default;
const server = require('../built/server').startServer();
const db = require('../built/db/mongodb').default;
const apiServer = http.createServer(app.callback());
//#region Utilities
const request = _request(apiServer);
const signup = _signup(request);
const post = _post(request);
//#endregion
describe('Streaming', () => {
// Reset database each test
beforeEach(resetDb(db));
after(() => {
server.close();
});
it('投稿がタイムラインに流れる', () => new Promise(async done => {
const post = {
text: 'foo'
};
const me = await signup();
const ws = new WebSocket(`ws://localhost/streaming?i=${me.token}`);
ws.on('open', () => {
ws.on('message', data => {
const msg = JSON.parse(data.toString());
if (msg.type == 'channel' && msg.body.id == 'a') {
if (msg.body.type == 'note') {
assert.deepStrictEqual(msg.body.body.text, post.text);
ws.close();
done();
}
} else if (msg.type == 'connected' && msg.body.id == 'a') {
request('/notes/create', post, me);
}
});
ws.send(JSON.stringify({
type: 'connect',
body: {
channel: 'homeTimeline',
id: 'a',
pong: true
}
}));
});
}));
it('mention event', () => new Promise(async done => {
const alice = await signup({ username: 'alice' });
const bob = await signup({ username: 'bob' });
const aliceNote = {
text: 'foo @bob bar'
};
const ws = new WebSocket(`ws://localhost/streaming?i=${bob.token}`);
ws.on('open', () => {
ws.on('message', data => {
const msg = JSON.parse(data.toString());
if (msg.type == 'channel' && msg.body.id == 'a') {
if (msg.body.type == 'mention') {
assert.deepStrictEqual(msg.body.body.text, aliceNote.text);
ws.close();
done();
}
} else if (msg.type == 'connected' && msg.body.id == 'a') {
request('/notes/create', aliceNote, alice);
}
});
ws.send(JSON.stringify({
type: 'connect',
body: {
channel: 'main',
id: 'a',
pong: true
}
}));
});
}));
it('renote event', () => new Promise(async done => {
const alice = await signup({ username: 'alice' });
const bob = await signup({ username: 'bob' });
const bobNote = await post(bob, {
text: 'foo'
});
const ws = new WebSocket(`ws://localhost/streaming?i=${bob.token}`);
ws.on('open', () => {
ws.on('message', data => {
const msg = JSON.parse(data.toString());
if (msg.type == 'channel' && msg.body.id == 'a') {
if (msg.body.type == 'renote') {
assert.deepStrictEqual(msg.body.body.renoteId, bobNote.id);
ws.close();
done();
}
} else if (msg.type == 'connected' && msg.body.id == 'a') {
request('/notes/create', {
renoteId: bobNote.id
}, alice);
}
});
ws.send(JSON.stringify({
type: 'connect',
body: {
channel: 'main',
id: 'a',
pong: true
}
}));
});
}));
});

83
test/utils.ts Normal file
View File

@@ -0,0 +1,83 @@
import * as fs from 'fs';
import * as http from 'http';
import * as assert from 'chai';
assert.use(require('chai-http'));
export const async = (fn: Function) => (done: Function) => {
fn().then(() => {
done();
}, (err: Error) => {
done(err);
});
};
export const _request = (server: http.Server) => async (endpoint: string, params: any, me?: any): Promise<ChaiHttp.Response> => {
const auth = me ? {
i: me.token
} : {};
const res = await assert.request(server)
.post(endpoint)
.send(Object.assign(auth, params));
return res;
};
export const _signup = (request: ReturnType<typeof _request>) => async (params?: any): Promise<any> => {
const q = Object.assign({
username: 'test',
password: 'test'
}, params);
const res = await request('/signup', q);
return res.body;
};
export const _post = (request: ReturnType<typeof _request>) => async (user: any, params?: any): Promise<any> => {
const q = Object.assign({
text: 'test'
}, params);
const res = await request('/notes/create', q, user);
return res.body.createdNote;
};
export const _react = (request: ReturnType<typeof _request>) => async (user: any, note: any, reaction: string): Promise<any> => {
await request('/notes/reactions/create', {
noteId: note.id,
reaction: reaction
}, user);
};
export const _uploadFile = (server: http.Server) => async (user: any): Promise<any> => {
const res = await assert.request(server)
.post('/drive/files/create')
.field('i', user.token)
.attach('file', fs.readFileSync(__dirname + '/resources/Lenna.png'), 'Lenna.png');
return res.body;
};
export const resetDb = (db: any) => () => new Promise(res => {
// APIがなにかレスポンスを返した後に、後処理を行う場合があり、
// レスポンスを受け取ってすぐデータベースをリセットすると
// その後処理と競合し(テスト自体は合格するものの)エラーがコンソールに出力され
// 見た目的に気持ち悪くなるので、後処理が終るのを待つために500msくらい待ってから
// データベースをリセットするようにする
setTimeout(async () => {
await Promise.all([
db.get('users').drop(),
db.get('notes').drop(),
db.get('driveFiles.files').drop(),
db.get('driveFiles.chunks').drop(),
db.get('driveFolders').drop(),
db.get('apps').drop(),
db.get('accessTokens').drop(),
db.get('authSessions').drop()
]);
res();
}, 500);
});

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