Compare commits

..

42 Commits

Author SHA1 Message Date
1a2a190828 10.89.0 2019-02-23 11:29:29 +09:00
251cf1d76f Update dependencies 🚀 2019-02-23 11:29:17 +09:00
52774bbe64 Introduce OpenAPI specs (#4351)
* wip

* wip

* wip

* Update index.ts

* Update gen-openapi-spec.ts

* Update api.ja-JP.md

* Fix

* Improve doc

* Update gen-openapi-spec.ts

* Update redoc.html

* Improve doc

* Update gen-openapi-spec.ts

* Improve doc

* Update CHANGELOG.md
2019-02-23 11:20:58 +09:00
68a6758302 投稿を削除したときにお気に入りからも削除するように 2019-02-22 14:54:52 +09:00
13e43a4f74 Refactor 2019-02-22 14:53:03 +09:00
b7d62d09ec Refactor 2019-02-22 14:46:49 +09:00
321ec18173 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-02-22 14:22:54 +09:00
a44ac3306e Fix error response format 2019-02-22 14:22:40 +09:00
951288ecf0 docs to run in production mode (#4347)
* run in production mode from systemd

* NODE_ENV=production npm run build

* npm start
2019-02-22 14:18:05 +09:00
cc8a7dd588 Fix bug 2019-02-22 14:09:24 +09:00
813c52f51e Remove unused imports 2019-02-22 14:06:17 +09:00
be3298639d Refactor 2019-02-22 14:02:56 +09:00
e8d2959717 New Crowdin translations (#4337)
* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)
2019-02-22 13:46:51 +09:00
e7680e08eb Update README.md [AUTOGEN] (#4344) 2019-02-22 13:46:26 +09:00
bd792d7661 Fix type annotation 2019-02-22 13:39:06 +09:00
4920983f23 Refactoring 2019-02-22 13:38:12 +09:00
2756f553c6 Improve error handling of API (#4345)
* wip

* wip

* wip

* Update attached_notes.ts

* wip

* Refactor

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update call.ts

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* ✌️

* Fix
2019-02-22 11:46:58 +09:00
fc52e95ad0 Update CHANGELOG.md 2019-02-22 02:10:32 +09:00
5d1d6bc028 Fix i18n 2019-02-22 02:08:45 +09:00
b106acac91 Fix #4340 2019-02-22 01:56:07 +09:00
a5071db864 Update note-detail.vue (#4342) 2019-02-21 23:20:55 +09:00
bae874eb45 /api/v1/instance/peers 復活 (#4339)
* /api/v1/instance/peers 復活

* use Instance
2019-02-21 23:20:25 +09:00
60da17940d 10.88.0 2019-02-21 06:06:12 +09:00
385eeed732 New Crowdin translations (#4332)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)
2019-02-21 06:05:35 +09:00
2e908758d8 🎨 2019-02-21 06:04:42 +09:00
a164f8ad95 Fix bug 2019-02-21 05:53:10 +09:00
372138dfea アカウント削除時にサインイン履歴も削除するように 2019-02-21 01:49:00 +09:00
922a657c7e アカウント削除時にメッセージも削除するように 2019-02-21 01:47:10 +09:00
3409a51cca Resolve #2017 2019-02-21 01:30:21 +09:00
7174a55846 🎨 2019-02-21 00:37:58 +09:00
6656a59402 Fix error 2019-02-21 00:32:49 +09:00
7612ead551 Fix #4331 2019-02-21 00:30:53 +09:00
fa78fe665d メディアビュー機能を削除 2019-02-21 00:13:46 +09:00
c89aa7eb95 デッキでメディア投稿のみ表示するオプションが機能していない問題を修正 2019-02-21 00:12:09 +09:00
43f4c5b7cd Fix #1537 2019-02-21 00:04:23 +09:00
2b6c566386 Fix template styling 2019-02-21 00:02:23 +09:00
91ef328b6b 「みつける」でミュートしているユーザーが含まれる問題を修正 2019-02-20 22:34:52 +09:00
5a9d9dc41d Fix indent 2019-02-20 22:33:22 +09:00
a48e503caa Fix indent 2019-02-20 22:33:13 +09:00
fe00cb9ad5 ハイライトでミュートしているユーザーの投稿が含まれる問題を修正 2019-02-20 22:31:21 +09:00
ed0fdaddbd Fix #4333 2019-02-20 22:21:12 +09:00
893795a31d Update some logs 2019-02-20 22:05:34 +09:00
247 changed files with 3693 additions and 2759 deletions

View File

@ -1,6 +1,24 @@
ChangeLog
=========
10.89.0
----------
* APIのエラーの形式を統一
* APIドキュメント刷新
* /api/v1/instance/peers 復活
* 「返信が遷移後も残り続ける問題を修正」([9beddc9](https://github.com/syuilo/misskey/commit/9beddc941a716f1322ae0b7d71d159edd642a399)) によって遷移前に返信が表示されなくなった問題を修正
* デッキモードにてユーザーのプロフィールを連続で見たとき、アクティビティや画像が前のユーザーのもののまま表示される問題を修正
10.88.0
----------
* アカウントの削除を試験的に実装
* デッキでメディア投稿のみ表示するオプションが機能していない問題を修正
* デッキでユーザーを表示したときにタイムラインが残存する問題を修正
* モバイルのユーザーページで、ユーザーAのタイムラインから他のユーザーBを選択してユーザーBのタイムラインに移動したとき、ユーザーAのタイムラインが残る問題を修正
* ハイライトでミュートしているユーザーの投稿が含まれる問題を修正
* 「みつける」でミュートしているユーザーが含まれる問題を修正
* デザインの調整
10.87.5
----------
* モバイル版でも連携サービスを表示するように

View File

@ -106,7 +106,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/3?token-time=2145916800&token-hash=c8HeVqLtmdgH-gSBJg8i10gmOcwllM87MDHeznl3el0%3D" alt="Melilot" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/3?token-time=2145916800&token-hash=LtV2lRi3L2jOWMLwccr9qWYfPrFlzIo2jYZHKzHEb6k%3D" alt="Xeltica" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/3384329/8b713330cb27404ea6e9fac50ff96efe/1?token-time=2145916800&token-hash=Ch3iF81ZGP0LMo894Y9ajpLisgtE91SnxtZE7fxsgrM%3D" alt="べすれい" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=1FlxS9MEgmNGH_RHUVHbO5hIXB5I1z0lvA33CTvYvjA%3D" alt="gutfuckllc" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/weepjp">weep</a></td>
@ -114,7 +113,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td>
<td><a href="https://www.patreon.com/Xeltica">Xeltica</a></td>
<td><a href="https://www.patreon.com/user?u=3384329">べすれい</a></td>
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
</tr></table>
<table><tr>
@ -152,7 +150,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, 15 Feb 2019 19:12:06 UTC
**Last updated:** Thu, 21 Feb 2019 16:10:06 UTC
<!-- PATREON_END -->
:four_leaf_clover: Copyright

BIN
assets/api-doc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -55,14 +55,9 @@ As root:
*6.* Build Misskey
----------------------------------------------------------------
Before build, you need to set `NODE_ENV` to `production`. like this:
* Linux: `export NODE_ENV=production`
* Windows (PowerShell): `$env:NODE_ENV="production"`
* Windows (CMD): `set NODE_ENV=production`
Build misskey with the following:
`npm run build`
`NODE_ENV=production npm run build`
If you're on Debian, you will need to install the `build-essential`, `python` package.
@ -71,14 +66,14 @@ If you're still encountering errors about some modules, use node-gyp:
1. `npm install -g node-gyp`
2. `node-gyp configure`
3. `node-gyp build`
4. `npm run build`
4. `NODE_ENV=production npm run build`
*7.* That is it.
----------------------------------------------------------------
Well done! Now, you have an environment that run to Misskey.
### Launch normally
Just `npm start`. GLHF!
Just `NODE_ENV=production npm start`. GLHF!
### Launch with systemd
@ -94,6 +89,7 @@ Type=simple
User=misskey
ExecStart=/usr/bin/npm start
WorkingDirectory=/home/misskey/misskey
Environment="NODE_ENV=production"
TimeoutSec=60
StandardOutput=syslog
StandardError=syslog
@ -113,7 +109,7 @@ You can check if the service is running with `systemctl status misskey`.
1. `git fetch`
2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
3. `npm install`
4. `npm run build`
4. `NODE_ENV=production npm run build`
5. Check [ChangeLog](../CHANGELOG.md) for migration information
6. Restart your Misskey process to apply changes
7. Enjoy

View File

@ -57,7 +57,7 @@ En root :
Construisez Misskey comme ceci :
`npm run build`
`NODE_ENV=production npm run build`
Si vous êtes sous Debian, vous serez amené à installer les paquets `build-essential` et `python`.
@ -66,14 +66,14 @@ Si vous rencontrez des erreurs concernant certains modules, utilisez node-gyp:
1. `npm install -g node-gyp`
2. `node-gyp configure`
3. `node-gyp build`
4. `npm run build`
4. `NODE_ENV=production npm run build`
*7.* C'est tout.
----------------------------------------------------------------
Excellent ! Maintenant, vous avez un environnement prêt pour lancer Misskey
### Lancement conventionnel
Lancez tout simplement `npm start`. Bonne chance et amusez-vous bien !
Lancez tout simplement `NODE_ENV=production npm start`. Bonne chance et amusez-vous bien !
### Démarrage avec systemd
@ -89,6 +89,7 @@ Type=simple
User=misskey
ExecStart=/usr/bin/npm start
WorkingDirectory=/home/misskey/misskey
Environment="NODE_ENV=production"
TimeoutSec=60
StandardOutput=syslog
StandardError=syslog
@ -108,7 +109,7 @@ Vous pouvez vérifier si le service a démarré en utilisant la commande `system
1. `git fetch`
2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
3. `npm install`
4. `npm run build`
4. `NODE_ENV=production npm run build`
5. Consultez [ChangeLog](../CHANGELOG.md) pour les information de migration.
----------------------------------------------------------------

View File

@ -62,14 +62,9 @@ adduser --disabled-password --disabled-login misskey
*6.* Misskeyのビルド
----------------------------------------------------------------
ビルドする前に、`NODE_ENV``production`にする必要があります。例:
* Linux: `export NODE_ENV=production`
* Windows (PowerShell): `$env:NODE_ENV="production"`
* Windows (CMD): `set NODE_ENV=production`
次のコマンドでMisskeyをビルドしてください:
`npm run build`
`NODE_ENV=production npm run build`
Debianをお使いであれば、`build-essential`パッケージをインストールする必要があります。
@ -77,14 +72,14 @@ Debianをお使いであれば、`build-essential`パッケージをインスト
1. `npm install -g node-gyp`
2. `node-gyp configure`
3. `node-gyp build`
4. `npm run build`
4. `NODE_ENV=production npm run build`
*7.* 以上です!
----------------------------------------------------------------
お疲れ様でした。これでMisskeyを動かす準備は整いました。
### 通常起動
`npm start`するだけです。GLHF!
`NODE_ENV=production npm start`するだけです。GLHF!
### systemdを用いた起動
1. systemdサービスのファイルを作成: `/etc/systemd/system/misskey.service`
@ -99,6 +94,7 @@ Type=simple
User=misskey
ExecStart=/usr/bin/npm start
WorkingDirectory=/home/misskey/misskey
Environment="NODE_ENV=production"
TimeoutSec=60
StandardOutput=syslog
StandardError=syslog
@ -119,7 +115,7 @@ CentOSで1024以下のポートを使用してMisskeyを使用する場合は`Ex
1. `git fetch`
2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
3. `npm install`
4. `npm run build`
4. `NODE_ENV=production npm run build`
5. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する
なにか問題が発生した場合は、`npm run clean`すると直る場合があります。

View File

@ -144,6 +144,7 @@ common:
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
renoted-by: "{user}がRenote"
no-notes: "投稿がありません"
error:
title: "問題が発生しました"
retry: "やり直す"
@ -537,6 +538,10 @@ common/views/components/profile-editor.vue:
mute-list: "ミュート"
blocking-list: "ブロック"
export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
enter-password: "パスワードを入力してください"
danger-zone: "危険な設定"
delete-account: "アカウントを削除"
account-deleted: "アカウントが削除されました。データが消えるまで時間がかかる場合があります。"
common/views/components/user-list-editor.vue:
users: "ユーザー"
rename: "リスト名を変更"
@ -785,8 +790,6 @@ desktop/views/components/renote-form.vue:
failure: "Renoteに失敗しました"
desktop/views/components/renote-form-window.vue:
title: "この投稿をRenoteしますか"
desktop/views/components/timeline.core.vue:
empty: "投稿がありません"
desktop/views/pages/user-following-or-followers.vue:
following: "{user}のフォロー"
followers: "{user}のフォロワー"
@ -1364,7 +1367,6 @@ desktop/views/pages/user/user.timeline.vue:
with-replies: "投稿と返信"
with-media: "メディア"
my-posts: "私の投稿"
empty: "このユーザーはまだ何も投稿していないようです。"
desktop/views/widgets/messaging.vue:
title: "メッセージ"
desktop/views/widgets/notifications.vue:
@ -1462,8 +1464,6 @@ mobile/views/components/sub-note-content.vue:
deleted: "この投稿は削除されました"
media-count: "{}つのメディア"
poll: "アンケート"
mobile/views/components/timeline.vue:
empty: "投稿がありません"
mobile/views/components/ui.header.vue:
welcome-back: "おかえりなさい、"
adjective: "さん"
@ -1480,9 +1480,6 @@ mobile/views/components/ui.nav.vue:
settings: "設定"
admin: "管理"
about: "Misskeyについて"
mobile/views/components/user-timeline.vue:
no-notes: "このユーザーは投稿していないようです。"
no-notes-with-media: "メディア付き投稿はありません。"
mobile/views/pages/favorites.vue:
title: "お気に入り"
mobile/views/pages/user-lists.vue:
@ -1501,8 +1498,6 @@ mobile/views/pages/home.vue:
global: "グローバル"
mentions: "あなた宛て"
messages: "メッセージ"
mobile/views/pages/home.timeline.vue:
empty: "投稿がありません"
mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
mobile/views/pages/welcome.vue:
@ -1595,8 +1590,6 @@ mobile/views/pages/user/home.vue:
frequently-replied-users: "よく話すユーザー"
followers-you-know: "知り合いのフォロワー"
last-used-at: "最終ログイン"
mobile/views/pages/user/home.notes.vue:
no-notes: "投稿はありません"
mobile/views/pages/user/home.photos.vue:
no-photos: "写真はありません"
deck:
@ -1624,7 +1617,6 @@ deck:
description: "サーバーの運営者により、このタイムラインは使用できない状態に設定されています。"
deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
deck/deck.user-column.vue:
follows-you: "フォローされています"

View File

@ -144,6 +144,7 @@ common:
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
renoted-by: "{user}がRenote"
no-notes: "投稿がありません"
error:
title: "問題が発生しました"
retry: "Erneut versuchen"
@ -537,6 +538,10 @@ common/views/components/profile-editor.vue:
mute-list: "ミュート"
blocking-list: "ブロック"
export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
enter-password: "パスワードを入力してください"
danger-zone: "危険な設定"
delete-account: "アカウントを削除"
account-deleted: "アカウントが削除されました。データが消えるまで時間がかかる場合があります。"
common/views/components/user-list-editor.vue:
users: "ユーザー"
rename: "リスト名を変更"
@ -785,8 +790,6 @@ desktop/views/components/renote-form.vue:
failure: "Weitersagen fehlgeschlagen"
desktop/views/components/renote-form-window.vue:
title: "Bist du dir sicher, dass du das reposten willst?"
desktop/views/components/timeline.core.vue:
empty: "投稿がありません"
desktop/views/pages/user-following-or-followers.vue:
following: "{user}のフォロー"
followers: "{user}のフォロワー"
@ -1364,7 +1367,6 @@ desktop/views/pages/user/user.timeline.vue:
with-replies: "投稿と返信"
with-media: "メディア"
my-posts: "私の投稿"
empty: "このユーザーはまだ何も投稿していないようです。"
desktop/views/widgets/messaging.vue:
title: "メッセージ"
desktop/views/widgets/notifications.vue:
@ -1462,8 +1464,6 @@ mobile/views/components/sub-note-content.vue:
deleted: "この投稿は削除されました"
media-count: "{}つのメディア"
poll: "アンケート"
mobile/views/components/timeline.vue:
empty: "投稿がありません"
mobile/views/components/ui.header.vue:
welcome-back: "おかえりなさい、"
adjective: "さん"
@ -1480,9 +1480,6 @@ mobile/views/components/ui.nav.vue:
settings: "設定"
admin: "管理"
about: "Misskeyについて"
mobile/views/components/user-timeline.vue:
no-notes: "このユーザーは投稿していないようです。"
no-notes-with-media: "メディア付き投稿はありません。"
mobile/views/pages/favorites.vue:
title: "Favoriten"
mobile/views/pages/user-lists.vue:
@ -1501,8 +1498,6 @@ mobile/views/pages/home.vue:
global: "グローバル"
mentions: "あなた宛て"
messages: "メッセージ"
mobile/views/pages/home.timeline.vue:
empty: "投稿がありません"
mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
mobile/views/pages/welcome.vue:
@ -1595,8 +1590,6 @@ mobile/views/pages/user/home.vue:
frequently-replied-users: "よく話すユーザー"
followers-you-know: "知り合いのフォロワー"
last-used-at: "最終ログイン"
mobile/views/pages/user/home.notes.vue:
no-notes: "投稿はありません"
mobile/views/pages/user/home.photos.vue:
no-photos: "写真はありません"
deck:
@ -1624,7 +1617,6 @@ deck:
description: "サーバーの運営者により、このタイムラインは使用できない状態に設定されています。"
deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
deck/deck.user-column.vue:
follows-you: "フォローされています"

View File

@ -8,12 +8,12 @@ common:
about: "Thank you for finding Misskey. Misskey is a <b>decentralized microblogging platform</b> 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?"
intro:
title: "What is Misskey?"
about: "Misskey is an open-source, <b>decentralized microblogging service</b>. Sophisticated, fully customizable UI, varieties of reactions for posts, free file storage providing an integrated management system and other advanced features are available. In addition, Misskey connects to a network system called the “Fediverse”, that enables us to communicate with users on other SNSs. For example, when you post something, it will be sent not only to Misskey, but also to Mastodon, Osada and Pleroma. Just imagine that the planet is sending a radio transmission to another planet to communicate."
about: "Misskey is an open-source, <b>decentralized microblogging software</b>. It has a sophisticated, fully customizable user interface, a variety of ways for expressing a reaction to posts, free file storage providing an integrated management system, and other advanced features are available. In addition, Misskey connects to a network system called the “Fediverse”, which enables us to communicate with users on other SNSs. For example, when you post something, it will be sent not only to Misskey users, but also those on Mastodon and Pleroma. Just imagine that the planet is sending a radio transmission to another planet, in order to communicate."
features: "Features"
rich-contents: "Post"
rich-contents-desc: "Just post your idea, hot topics, and anything you want to share. You may want to decorate your words, attach your favorite pictures, send files, including videos, or create a poll - those are some of the things you can do with Misskey!"
reaction: "Reactions"
reaction-desc: "Easiest way to tell your emotions. Misskey allows you to add various type of reactions to others post. The emotional experience on Misskey will never be on other SNSs which only able to push “likes”."
reaction-desc: "The easiest way to express your emotions. Misskey allows you to add various kinds of reactions to other's posts. The emotional experience on Misskey will never be on other SNSs, which are only able to push “likes”."
ui: "Interface"
ui-desc: "No UI fits for everyone. Therefore, Misskey has a highly customizable UI for your taste. Make your original home by editing, adjusting layouts of timeline and placing selectable widgets you can easily customize."
drive: "Drive"
@ -60,7 +60,7 @@ common:
messaging: "Talk"
deck: "Deck"
timeline: "Timeline"
explore: "Discover"
explore: "Explore"
following: "Following"
followers: "Followers"
empty-timeline-info:
@ -144,6 +144,7 @@ common:
is-remote-post: "These post contents are mirrored."
view-on-remote: "For completion, view it remotely."
renoted-by: "Renoted by {user}"
no-notes: "Without any notes"
error:
title: "Something happened :("
retry: "Retry"
@ -537,6 +538,10 @@ common/views/components/profile-editor.vue:
mute-list: "List of muted accounts"
blocking-list: "List of blocked accounts"
export-requested: "You have requested an export. This may take a while. After the export is complete, the resulting file will be added to the drive."
enter-password: "Please enter your password"
danger-zone: "Cautious options"
delete-account: "Remove the account"
account-deleted: "The account has been deleted. It may take some time until all of the data disappears."
common/views/components/user-list-editor.vue:
users: "User"
rename: "Rename list"
@ -785,8 +790,6 @@ desktop/views/components/renote-form.vue:
failure: "Failed to Repost"
desktop/views/components/renote-form-window.vue:
title: "Do you want to Repost it?"
desktop/views/components/timeline.core.vue:
empty: "Without any notes"
desktop/views/pages/user-following-or-followers.vue:
following: "{user}'s following"
followers: "{user}'s follower"
@ -1353,7 +1356,7 @@ desktop/views/pages/user/user.header.vue:
following: "Following"
followers: "Followers"
is-bot: "This account is a Bot"
no-description: "自己紹介はありません"
no-description: "The user has not written their profile introduction"
years-old: "{age} years old"
year: "/"
month: "/"
@ -1364,7 +1367,6 @@ desktop/views/pages/user/user.timeline.vue:
with-replies: "Posts and replies"
with-media: "Media"
my-posts: "My posts"
empty: "This user doesn't seem to have posted anything yet."
desktop/views/widgets/messaging.vue:
title: "Message"
desktop/views/widgets/notifications.vue:
@ -1462,8 +1464,6 @@ mobile/views/components/sub-note-content.vue:
deleted: "This post has been deleted"
media-count: "{} media attached"
poll: "Poll"
mobile/views/components/timeline.vue:
empty: "No notes"
mobile/views/components/ui.header.vue:
welcome-back: "Welcome back, "
adjective: "Sir"
@ -1480,9 +1480,6 @@ mobile/views/components/ui.nav.vue:
settings: "Settings"
admin: "Admin"
about: "About Misskey"
mobile/views/components/user-timeline.vue:
no-notes: "It seems this user hasn't posted anything yet."
no-notes-with-media: "There are no notes with media attachments"
mobile/views/pages/favorites.vue:
title: "Favorites"
mobile/views/pages/user-lists.vue:
@ -1501,8 +1498,6 @@ mobile/views/pages/home.vue:
global: "Global"
mentions: "Mentions"
messages: "Messages"
mobile/views/pages/home.timeline.vue:
empty: "Without any notes"
mobile/views/pages/tag.vue:
no-posts-found: "No posts contains \"{q}\" found."
mobile/views/pages/welcome.vue:
@ -1595,8 +1590,6 @@ mobile/views/pages/user/home.vue:
frequently-replied-users: "Frequent mentions"
followers-you-know: "Followers you know"
last-used-at: "Last active:"
mobile/views/pages/user/home.notes.vue:
no-notes: "No notes"
mobile/views/pages/user/home.photos.vue:
no-photos: "No photos"
deck:
@ -1624,7 +1617,6 @@ deck:
description: "This timeline has been disabled by the server administrator."
deck/deck.tl-column.vue:
is-media-only: "Only media posts"
is-media-view: "Media view"
edit: "Options"
deck/deck.user-column.vue:
follows-you: "Follows you"

View File

@ -144,6 +144,7 @@ common:
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
renoted-by: "{user}がRenote"
no-notes: "投稿がありません"
error:
title: "問題が発生しました"
retry: "やり直す"
@ -537,6 +538,10 @@ common/views/components/profile-editor.vue:
mute-list: "ミュート"
blocking-list: "ブロック"
export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
enter-password: "パスワードを入力してください"
danger-zone: "危険な設定"
delete-account: "アカウントを削除"
account-deleted: "アカウントが削除されました。データが消えるまで時間がかかる場合があります。"
common/views/components/user-list-editor.vue:
users: "Usuarios"
rename: "リスト名を変更"
@ -785,8 +790,6 @@ desktop/views/components/renote-form.vue:
failure: "La publicación ha fallado"
desktop/views/components/renote-form-window.vue:
title: "¿Seguro qué quieres volver a publicarlo?"
desktop/views/components/timeline.core.vue:
empty: "投稿がありません"
desktop/views/pages/user-following-or-followers.vue:
following: "{user} sigue a"
followers: "Seguidores de {user}"
@ -1364,7 +1367,6 @@ desktop/views/pages/user/user.timeline.vue:
with-replies: "投稿と返信"
with-media: "メディア"
my-posts: "私の投稿"
empty: "このユーザーはまだ何も投稿していないようです。"
desktop/views/widgets/messaging.vue:
title: "メッセージ"
desktop/views/widgets/notifications.vue:
@ -1462,8 +1464,6 @@ mobile/views/components/sub-note-content.vue:
deleted: "この投稿は削除されました"
media-count: "{}つのメディア"
poll: "アンケート"
mobile/views/components/timeline.vue:
empty: "投稿がありません"
mobile/views/components/ui.header.vue:
welcome-back: "おかえりなさい、"
adjective: "さん"
@ -1480,9 +1480,6 @@ mobile/views/components/ui.nav.vue:
settings: "設定"
admin: "管理"
about: "Misskeyについて"
mobile/views/components/user-timeline.vue:
no-notes: "このユーザーは投稿していないようです。"
no-notes-with-media: "メディア付き投稿はありません。"
mobile/views/pages/favorites.vue:
title: "お気に入り"
mobile/views/pages/user-lists.vue:
@ -1501,8 +1498,6 @@ mobile/views/pages/home.vue:
global: "グローバル"
mentions: "あなた宛て"
messages: "メッセージ"
mobile/views/pages/home.timeline.vue:
empty: "投稿がありません"
mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
mobile/views/pages/welcome.vue:
@ -1595,8 +1590,6 @@ mobile/views/pages/user/home.vue:
frequently-replied-users: "よく話すユーザー"
followers-you-know: "知り合いのフォロワー"
last-used-at: "最終ログイン"
mobile/views/pages/user/home.notes.vue:
no-notes: "投稿はありません"
mobile/views/pages/user/home.photos.vue:
no-photos: "写真はありません"
deck:
@ -1624,7 +1617,6 @@ deck:
description: "サーバーの運営者により、このタイムラインは使用できない状態に設定されています。"
deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
deck/deck.user-column.vue:
follows-you: "フォローされています"

View File

@ -144,6 +144,7 @@ common:
is-remote-post: "Ceci est une publication distante."
view-on-remote: " Consulter le profil complet"
renoted-by: "Renoté par {user}"
no-notes: "投稿がありません"
error:
title: "Une erreur est survenue"
retry: "Réessayer"
@ -537,6 +538,10 @@ common/views/components/profile-editor.vue:
mute-list: "Liste des comptes mis en sourdine"
blocking-list: "Liste des comptes bloqués"
export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
enter-password: "パスワードを入力してください"
danger-zone: "危険な設定"
delete-account: "アカウントを削除"
account-deleted: "アカウントが削除されました。データが消えるまで時間がかかる場合があります。"
common/views/components/user-list-editor.vue:
users: "Utilisateur·rice"
rename: "Renommer la liste"
@ -785,8 +790,6 @@ desktop/views/components/renote-form.vue:
failure: "La renote a échoué"
desktop/views/components/renote-form-window.vue:
title: "Êtes vous sûr de vouloir renote cette note?"
desktop/views/components/timeline.core.vue:
empty: "Sans aucune note"
desktop/views/pages/user-following-or-followers.vue:
following: "{user} suit"
followers: "Abonné·e·s de {user}"
@ -1364,7 +1367,6 @@ desktop/views/pages/user/user.timeline.vue:
with-replies: "Publications et réponses"
with-media: "Média"
my-posts: "Mes Messages"
empty: "Cet·te utilisateur·rice n'a rien posté encore."
desktop/views/widgets/messaging.vue:
title: "Messagerie"
desktop/views/widgets/notifications.vue:
@ -1462,8 +1464,6 @@ mobile/views/components/sub-note-content.vue:
deleted: "cette publication a été supprimée"
media-count: "{} médias attachés"
poll: "Sondage"
mobile/views/components/timeline.vue:
empty: "Pas de notes"
mobile/views/components/ui.header.vue:
welcome-back: "Content de vous revoir ! "
adjective: "M."
@ -1480,9 +1480,6 @@ mobile/views/components/ui.nav.vue:
settings: "Réglages"
admin: "Admin"
about: "À propos de Misskey"
mobile/views/components/user-timeline.vue:
no-notes: "Il semble que cet·te utilisateur·rice na rien publié pour le moment."
no-notes-with-media: "Aucune note comprenant des médias"
mobile/views/pages/favorites.vue:
title: "Favoris"
mobile/views/pages/user-lists.vue:
@ -1501,8 +1498,6 @@ mobile/views/pages/home.vue:
global: "Global"
mentions: "Mentions"
messages: "Messages"
mobile/views/pages/home.timeline.vue:
empty: "Sans aucune note"
mobile/views/pages/tag.vue:
no-posts-found: "Aucune publication ayant pour hashtag « {q} » na été trouvée."
mobile/views/pages/welcome.vue:
@ -1595,8 +1590,6 @@ mobile/views/pages/user/home.vue:
frequently-replied-users: "Mentions fréquentes"
followers-you-know: "Abonné·e·s que vous connaissez"
last-used-at: "Dernière connexion il y a"
mobile/views/pages/user/home.notes.vue:
no-notes: "Pas de notes"
mobile/views/pages/user/home.photos.vue:
no-photos: "Pas de photos"
deck:
@ -1624,7 +1617,6 @@ deck:
description: "Ce fil a été désactivé par l'administrateur du serveur."
deck/deck.tl-column.vue:
is-media-only: "Les publications médias uniquement"
is-media-view: "Vue média"
edit: "Option"
deck/deck.user-column.vue:
follows-you: "Vous suit"

View File

@ -144,6 +144,7 @@ common:
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
renoted-by: "{user}がRenote"
no-notes: "投稿がありません"
error:
title: "問題が発生しました"
retry: "やり直す"
@ -537,6 +538,10 @@ common/views/components/profile-editor.vue:
mute-list: "ミュート"
blocking-list: "ブロック"
export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
enter-password: "パスワードを入力してください"
danger-zone: "危険な設定"
delete-account: "アカウントを削除"
account-deleted: "アカウントが削除されました。データが消えるまで時間がかかる場合があります。"
common/views/components/user-list-editor.vue:
users: "ユーザー"
rename: "リスト名を変更"
@ -785,8 +790,6 @@ desktop/views/components/renote-form.vue:
failure: "Renoteに失敗しました"
desktop/views/components/renote-form-window.vue:
title: "この投稿をRenoteしますか"
desktop/views/components/timeline.core.vue:
empty: "投稿がありません"
desktop/views/pages/user-following-or-followers.vue:
following: "{user}のフォロー"
followers: "{user}のフォロワー"
@ -1364,7 +1367,6 @@ desktop/views/pages/user/user.timeline.vue:
with-replies: "投稿と返信"
with-media: "メディア"
my-posts: "私の投稿"
empty: "このユーザーはまだ何も投稿していないようです。"
desktop/views/widgets/messaging.vue:
title: "メッセージ"
desktop/views/widgets/notifications.vue:
@ -1462,8 +1464,6 @@ mobile/views/components/sub-note-content.vue:
deleted: "この投稿は削除されました"
media-count: "{}つのメディア"
poll: "アンケート"
mobile/views/components/timeline.vue:
empty: "投稿がありません"
mobile/views/components/ui.header.vue:
welcome-back: "おかえりなさい、"
adjective: "さん"
@ -1480,9 +1480,6 @@ mobile/views/components/ui.nav.vue:
settings: "設定"
admin: "管理"
about: "Misskeyについて"
mobile/views/components/user-timeline.vue:
no-notes: "このユーザーは投稿していないようです。"
no-notes-with-media: "メディア付き投稿はありません。"
mobile/views/pages/favorites.vue:
title: "お気に入り"
mobile/views/pages/user-lists.vue:
@ -1501,8 +1498,6 @@ mobile/views/pages/home.vue:
global: "グローバル"
mentions: "あなた宛て"
messages: "メッセージ"
mobile/views/pages/home.timeline.vue:
empty: "投稿がありません"
mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
mobile/views/pages/welcome.vue:
@ -1595,8 +1590,6 @@ mobile/views/pages/user/home.vue:
frequently-replied-users: "よく話すユーザー"
followers-you-know: "知り合いのフォロワー"
last-used-at: "最終ログイン"
mobile/views/pages/user/home.notes.vue:
no-notes: "投稿はありません"
mobile/views/pages/user/home.photos.vue:
no-photos: "写真はありません"
deck:
@ -1624,7 +1617,6 @@ deck:
description: "サーバーの運営者により、このタイムラインは使用できない状態に設定されています。"
deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
deck/deck.user-column.vue:
follows-you: "フォローされています"

View File

@ -154,6 +154,7 @@ common:
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
renoted-by: "{user}がRenote"
no-notes: "投稿がありません"
error:
title: "問題が発生しました"
@ -588,6 +589,10 @@ common/views/components/profile-editor.vue:
mute-list: "ミュート"
blocking-list: "ブロック"
export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
enter-password: "パスワードを入力してください"
danger-zone: "危険な設定"
delete-account: "アカウントを削除"
account-deleted: "アカウントが削除されました。データが消えるまで時間がかかる場合があります。"
common/views/components/user-list-editor.vue:
users: "ユーザー"
@ -879,9 +884,6 @@ desktop/views/components/renote-form.vue:
desktop/views/components/renote-form-window.vue:
title: "この投稿をRenoteしますか"
desktop/views/components/timeline.core.vue:
empty: "投稿がありません"
desktop/views/pages/user-following-or-followers.vue:
following: "{user}のフォロー"
followers: "{user}のフォロワー"
@ -1518,7 +1520,6 @@ desktop/views/pages/user/user.timeline.vue:
with-replies: "投稿と返信"
with-media: "メディア"
my-posts: "私の投稿"
empty: "このユーザーはまだ何も投稿していないようです。"
desktop/views/widgets/messaging.vue:
title: "メッセージ"
@ -1638,9 +1639,6 @@ mobile/views/components/sub-note-content.vue:
media-count: "{}つのメディア"
poll: "アンケート"
mobile/views/components/timeline.vue:
empty: "投稿がありません"
mobile/views/components/ui.header.vue:
welcome-back: "おかえりなさい、"
adjective: "さん"
@ -1659,10 +1657,6 @@ mobile/views/components/ui.nav.vue:
admin: "管理"
about: "Misskeyについて"
mobile/views/components/user-timeline.vue:
no-notes: "このユーザーは投稿していないようです。"
no-notes-with-media: "メディア付き投稿はありません。"
mobile/views/pages/favorites.vue:
title: "お気に入り"
@ -1687,9 +1681,6 @@ mobile/views/pages/home.vue:
mentions: "あなた宛て"
messages: "メッセージ"
mobile/views/pages/home.timeline.vue:
empty: "投稿がありません"
mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
@ -1796,9 +1787,6 @@ mobile/views/pages/user/home.vue:
followers-you-know: "知り合いのフォロワー"
last-used-at: "最終ログイン"
mobile/views/pages/user/home.notes.vue:
no-notes: "投稿はありません"
mobile/views/pages/user/home.photos.vue:
no-photos: "写真はありません"
@ -1828,7 +1816,6 @@ deck:
deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
deck/deck.user-column.vue:

View File

@ -144,6 +144,7 @@ common:
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "ちゃんとした情報見せてや!"
renoted-by: "{user}がRenote"
no-notes: "投稿がありません"
error:
title: "問題が起こったわ"
retry: "もっぺん"
@ -537,6 +538,10 @@ common/views/components/profile-editor.vue:
mute-list: "ミュート"
blocking-list: "ブロック"
export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
enter-password: "パスワードを入力してください"
danger-zone: "危険な設定"
delete-account: "アカウントを削除"
account-deleted: "アカウントが削除されました。データが消えるまで時間がかかる場合があります。"
common/views/components/user-list-editor.vue:
users: "ユーザー"
rename: "リスト名を変更"
@ -785,8 +790,6 @@ desktop/views/components/renote-form.vue:
failure: "Renoteでけへん"
desktop/views/components/renote-form-window.vue:
title: "この投稿をRenoteしてもええか"
desktop/views/components/timeline.core.vue:
empty: "投稿がありません"
desktop/views/pages/user-following-or-followers.vue:
following: "{user}のフォロー"
followers: "{user}のフォロワー"
@ -1364,7 +1367,6 @@ desktop/views/pages/user/user.timeline.vue:
with-replies: "投稿と返信"
with-media: "メディア"
my-posts: "私の投稿"
empty: "このユーザーはまだ何も投稿しとらんようや。"
desktop/views/widgets/messaging.vue:
title: "メッセージ"
desktop/views/widgets/notifications.vue:
@ -1462,8 +1464,6 @@ mobile/views/components/sub-note-content.vue:
deleted: "この投稿なんか無くなってもうたわ"
media-count: "{}つのメディア"
poll: "アンケート"
mobile/views/components/timeline.vue:
empty: "投稿はあらへん"
mobile/views/components/ui.header.vue:
welcome-back: "おかえり、"
adjective: "はん"
@ -1480,9 +1480,6 @@ mobile/views/components/ui.nav.vue:
settings: "設定"
admin: "管理"
about: "Misskeyってなんや"
mobile/views/components/user-timeline.vue:
no-notes: "このユーザーは投稿しとらんようや。"
no-notes-with-media: "メディア付き投稿はあらへん。"
mobile/views/pages/favorites.vue:
title: "お気に入り"
mobile/views/pages/user-lists.vue:
@ -1501,8 +1498,6 @@ mobile/views/pages/home.vue:
global: "グローバル"
mentions: "あんた宛て"
messages: "メッセージ"
mobile/views/pages/home.timeline.vue:
empty: "投稿がありません"
mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿はあらへんかった。"
mobile/views/pages/welcome.vue:
@ -1595,8 +1590,6 @@ mobile/views/pages/user/home.vue:
frequently-replied-users: "よく話すユーザー"
followers-you-know: "知っとるフォロワー"
last-used-at: "最後いつ来た?"
mobile/views/pages/user/home.notes.vue:
no-notes: "投稿はあらへん"
mobile/views/pages/user/home.photos.vue:
no-photos: "写真はあらへんで"
deck:
@ -1624,7 +1617,6 @@ deck:
description: "サーバーの運営者により、このタイムラインは使用できない状態に設定されています。"
deck/deck.tl-column.vue:
is-media-only: "メディア投稿だけや"
is-media-view: "メディアビュー"
edit: "オプション"
deck/deck.user-column.vue:
follows-you: "フォローされています"

View File

@ -144,6 +144,7 @@ common:
is-remote-post: "이 글 정보는 복사본입니다."
view-on-remote: "정확한 정보 보기"
renoted-by: "{user}이(가) 리노트"
no-notes: "글이 없습니다"
error:
title: "오류가 발생했습니다"
retry: "다시 시도"
@ -537,6 +538,10 @@ common/views/components/profile-editor.vue:
mute-list: "뮤트"
blocking-list: "차단"
export-requested: "내보내기를 요청하였습니다. 이 작업은 시간이 걸릴 수 있습니다. 내보내기가 완료되면 드라이브에 파일이 추가됩니다."
enter-password: "비밀번호를 입력하여 주십시오"
danger-zone: "위험한 설정"
delete-account: "계정 삭제"
account-deleted: "계정이 삭제되었습니다. 데이터가 사라질 때까지 시간이 걸릴 수 있습니다."
common/views/components/user-list-editor.vue:
users: "사용자"
rename: "리스트 이름 바꾸기"
@ -785,8 +790,6 @@ desktop/views/components/renote-form.vue:
failure: "리노트에 실패하였습니다"
desktop/views/components/renote-form-window.vue:
title: "이 글을 리노트하시겠습니까?"
desktop/views/components/timeline.core.vue:
empty: "글이 없습니다"
desktop/views/pages/user-following-or-followers.vue:
following: "{user}의 팔로잉"
followers: "{user}의 팔로워"
@ -1364,7 +1367,6 @@ desktop/views/pages/user/user.timeline.vue:
with-replies: "글과 답글"
with-media: "미디어"
my-posts: "내 글"
empty: "이 사용자는 아직 아무것도 게시하지 않은 것 같습니다."
desktop/views/widgets/messaging.vue:
title: "메시지"
desktop/views/widgets/notifications.vue:
@ -1462,8 +1464,6 @@ mobile/views/components/sub-note-content.vue:
deleted: "이 글은 삭제되었습니다"
media-count: "{}개의 미디어"
poll: "투표"
mobile/views/components/timeline.vue:
empty: "글이 없습니다"
mobile/views/components/ui.header.vue:
welcome-back: "돌아오신 걸 환영합니다."
adjective: "님"
@ -1480,9 +1480,6 @@ mobile/views/components/ui.nav.vue:
settings: "설정"
admin: "관리"
about: "Misskey에 대하여"
mobile/views/components/user-timeline.vue:
no-notes: "이 사용자는 작성한 글이 없는 것 같습니다."
no-notes-with-media: "미디어가 첨부된 글이 없습니다."
mobile/views/pages/favorites.vue:
title: "즐겨찾기"
mobile/views/pages/user-lists.vue:
@ -1501,8 +1498,6 @@ mobile/views/pages/home.vue:
global: "글로벌"
mentions: "받은 멘션"
messages: "메시지"
mobile/views/pages/home.timeline.vue:
empty: "글이 없습니다"
mobile/views/pages/tag.vue:
no-posts-found: "해시태그 \"{q}\"가 붙은 글을 찾을 수 없습니다."
mobile/views/pages/welcome.vue:
@ -1595,8 +1590,6 @@ mobile/views/pages/user/home.vue:
frequently-replied-users: "자주 언급되는 사용자"
followers-you-know: "아는 사람의 팔로워"
last-used-at: "마지막 로그인"
mobile/views/pages/user/home.notes.vue:
no-notes: "글이 없습니다"
mobile/views/pages/user/home.photos.vue:
no-photos: "사진이 없습니다"
deck:
@ -1624,7 +1617,6 @@ deck:
description: "서버 운영자에 의해 이 타임라인이 사용할 수 없도록 설정되어 있습니다."
deck/deck.tl-column.vue:
is-media-only: "미디어가 달린 글만"
is-media-view: "미디어 보기"
edit: "옵션"
deck/deck.user-column.vue:
follows-you: "당신을 팔로우합니다"

View File

@ -144,6 +144,7 @@ common:
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
renoted-by: "{user}がRenote"
no-notes: "投稿がありません"
error:
title: "問題が発生しました"
retry: "やり直す"
@ -537,6 +538,10 @@ common/views/components/profile-editor.vue:
mute-list: "ミュート"
blocking-list: "ブロック"
export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
enter-password: "パスワードを入力してください"
danger-zone: "危険な設定"
delete-account: "アカウントを削除"
account-deleted: "アカウントが削除されました。データが消えるまで時間がかかる場合があります。"
common/views/components/user-list-editor.vue:
users: "ユーザー"
rename: "リスト名を変更"
@ -785,8 +790,6 @@ desktop/views/components/renote-form.vue:
failure: "Renote mislukt"
desktop/views/components/renote-form-window.vue:
title: "Weet je zeker dat je deze notitie wilt renoten?"
desktop/views/components/timeline.core.vue:
empty: "投稿がありません"
desktop/views/pages/user-following-or-followers.vue:
following: "{user}のフォロー"
followers: "{user}のフォロワー"
@ -1364,7 +1367,6 @@ desktop/views/pages/user/user.timeline.vue:
with-replies: "Berichten en antwoorden"
with-media: "Media"
my-posts: "私の投稿"
empty: "Deze gebruiker heeft nog niks geplaatst."
desktop/views/widgets/messaging.vue:
title: "Gesprekken"
desktop/views/widgets/notifications.vue:
@ -1462,8 +1464,6 @@ mobile/views/components/sub-note-content.vue:
deleted: "この投稿は削除されました"
media-count: "{} media"
poll: "Peiling"
mobile/views/components/timeline.vue:
empty: "Geen notities"
mobile/views/components/ui.header.vue:
welcome-back: "おかえりなさい、"
adjective: "さん"
@ -1480,9 +1480,6 @@ mobile/views/components/ui.nav.vue:
settings: "Instellingen"
admin: "管理"
about: "Over Misskey"
mobile/views/components/user-timeline.vue:
no-notes: "Het lijkt erop dat deze gebruiker nog niks heeft geplaatst"
no-notes-with-media: "Er zijn geen notities met bijgevoegde media"
mobile/views/pages/favorites.vue:
title: "お気に入り"
mobile/views/pages/user-lists.vue:
@ -1501,8 +1498,6 @@ mobile/views/pages/home.vue:
global: "グローバル"
mentions: "あなた宛て"
messages: "メッセージ"
mobile/views/pages/home.timeline.vue:
empty: "投稿がありません"
mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
mobile/views/pages/welcome.vue:
@ -1595,8 +1590,6 @@ mobile/views/pages/user/home.vue:
frequently-replied-users: "よく話すユーザー"
followers-you-know: "Volgers die je kent"
last-used-at: "Laatst actief"
mobile/views/pages/user/home.notes.vue:
no-notes: "Geen notities"
mobile/views/pages/user/home.photos.vue:
no-photos: "Geen foto's"
deck:
@ -1624,7 +1617,6 @@ deck:
description: "サーバーの運営者により、このタイムラインは使用できない状態に設定されています。"
deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
deck/deck.user-column.vue:
follows-you: "フォローされています"

View File

@ -144,6 +144,7 @@ common:
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
renoted-by: "{user}がRenote"
no-notes: "投稿がありません"
error:
title: "問題が発生しました"
retry: "やり直す"
@ -537,6 +538,10 @@ common/views/components/profile-editor.vue:
mute-list: "ミュート"
blocking-list: "ブロック"
export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
enter-password: "パスワードを入力してください"
danger-zone: "危険な設定"
delete-account: "アカウントを削除"
account-deleted: "アカウントが削除されました。データが消えるまで時間がかかる場合があります。"
common/views/components/user-list-editor.vue:
users: "ユーザー"
rename: "リスト名を変更"
@ -785,8 +790,6 @@ desktop/views/components/renote-form.vue:
failure: "Renoteに失敗しました"
desktop/views/components/renote-form-window.vue:
title: "この投稿をRenoteしますか"
desktop/views/components/timeline.core.vue:
empty: "投稿がありません"
desktop/views/pages/user-following-or-followers.vue:
following: "{user}のフォロー"
followers: "{user}のフォロワー"
@ -1364,7 +1367,6 @@ desktop/views/pages/user/user.timeline.vue:
with-replies: "Innlegg og svar"
with-media: "Media"
my-posts: "私の投稿"
empty: "このユーザーはまだ何も投稿していないようです。"
desktop/views/widgets/messaging.vue:
title: "Melding"
desktop/views/widgets/notifications.vue:
@ -1462,8 +1464,6 @@ mobile/views/components/sub-note-content.vue:
deleted: "この投稿は削除されました"
media-count: "{}つのメディア"
poll: "アンケート"
mobile/views/components/timeline.vue:
empty: "投稿がありません"
mobile/views/components/ui.header.vue:
welcome-back: "おかえりなさい、"
adjective: "Mr."
@ -1480,9 +1480,6 @@ mobile/views/components/ui.nav.vue:
settings: "Innstillinger"
admin: "Admin"
about: "Misskeyについて"
mobile/views/components/user-timeline.vue:
no-notes: "このユーザーは投稿していないようです。"
no-notes-with-media: "メディア付き投稿はありません。"
mobile/views/pages/favorites.vue:
title: "Favoritter"
mobile/views/pages/user-lists.vue:
@ -1501,8 +1498,6 @@ mobile/views/pages/home.vue:
global: "Globalt"
mentions: "あなた宛て"
messages: "メッセージ"
mobile/views/pages/home.timeline.vue:
empty: "投稿がありません"
mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
mobile/views/pages/welcome.vue:
@ -1595,8 +1590,6 @@ mobile/views/pages/user/home.vue:
frequently-replied-users: "よく話すユーザー"
followers-you-know: "知り合いのフォロワー"
last-used-at: "最終ログイン"
mobile/views/pages/user/home.notes.vue:
no-notes: "投稿はありません"
mobile/views/pages/user/home.photos.vue:
no-photos: "写真はありません"
deck:
@ -1624,7 +1617,6 @@ deck:
description: "サーバーの運営者により、このタイムラインは使用できない状態に設定されています。"
deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
deck/deck.user-column.vue:
follows-you: "フォローされています"

View File

@ -144,6 +144,7 @@ common:
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "Dla dopełnienia, zobacz to zdalnie."
renoted-by: "{user} udostępnił(a)"
no-notes: "投稿がありません"
error:
title: "Coś poszło nie tak"
retry: "Ponów próbę"
@ -537,6 +538,10 @@ common/views/components/profile-editor.vue:
mute-list: "ミュート"
blocking-list: "ブロック"
export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
enter-password: "パスワードを入力してください"
danger-zone: "危険な設定"
delete-account: "アカウントを削除"
account-deleted: "アカウントが削除されました。データが消えるまで時間がかかる場合があります。"
common/views/components/user-list-editor.vue:
users: "Użytkownicy"
rename: "Zmień nazwę listy"
@ -785,8 +790,6 @@ desktop/views/components/renote-form.vue:
failure: "Nie udało się udostępnić"
desktop/views/components/renote-form-window.vue:
title: "Czy na pewno chcesz udostępnić ten wpis?"
desktop/views/components/timeline.core.vue:
empty: "投稿がありません"
desktop/views/pages/user-following-or-followers.vue:
following: "{user}のフォロー"
followers: "{user}のフォロワー"
@ -1364,7 +1367,6 @@ desktop/views/pages/user/user.timeline.vue:
with-replies: "Wpisy i odpowiedzi"
with-media: "Multimedia"
my-posts: "Moje wpisy"
empty: "Ten użytkownik nie umieścił jeszcze niczego."
desktop/views/widgets/messaging.vue:
title: "Wiadomości"
desktop/views/widgets/notifications.vue:
@ -1462,8 +1464,6 @@ mobile/views/components/sub-note-content.vue:
deleted: "ten wpis został usunięty"
media-count: "{}zawartości multimedialnej"
poll: "Ankieta"
mobile/views/components/timeline.vue:
empty: "Brak wpisów"
mobile/views/components/ui.header.vue:
welcome-back: "Witaj ponownie, "
adjective: "さん"
@ -1480,9 +1480,6 @@ mobile/views/components/ui.nav.vue:
settings: "Ustawienia"
admin: "Admin"
about: "O Misskey"
mobile/views/components/user-timeline.vue:
no-notes: "Wygląda na to, że ten użytkownik nie opublikował jeszcze niczego"
no-notes-with-media: "Brak wpisów z zawartością multimedialną"
mobile/views/pages/favorites.vue:
title: "Ulubione"
mobile/views/pages/user-lists.vue:
@ -1501,8 +1498,6 @@ mobile/views/pages/home.vue:
global: "Globalne"
mentions: "Wspomnienia"
messages: "Wiadomości"
mobile/views/pages/home.timeline.vue:
empty: "投稿がありません"
mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
mobile/views/pages/welcome.vue:
@ -1595,8 +1590,6 @@ mobile/views/pages/user/home.vue:
frequently-replied-users: "よく話すユーザー"
followers-you-know: "Śledzący których znasz"
last-used-at: "Ostatnio aktywny"
mobile/views/pages/user/home.notes.vue:
no-notes: "Brak wpisów"
mobile/views/pages/user/home.photos.vue:
no-photos: "Brak zdjęć"
deck:
@ -1624,7 +1617,6 @@ deck:
description: "サーバーの運営者により、このタイムラインは使用できない状態に設定されています。"
deck/deck.tl-column.vue:
is-media-only: "Tylko wpisy z zawartością multimedialną"
is-media-view: "Widok multimediów"
edit: "Opcje"
deck/deck.user-column.vue:
follows-you: "フォローされています"

View File

@ -144,6 +144,7 @@ common:
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
renoted-by: "{user}がRenote"
no-notes: "投稿がありません"
error:
title: "問題が発生しました"
retry: "やり直す"
@ -537,6 +538,10 @@ common/views/components/profile-editor.vue:
mute-list: "ミュート"
blocking-list: "ブロック"
export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
enter-password: "パスワードを入力してください"
danger-zone: "危険な設定"
delete-account: "アカウントを削除"
account-deleted: "アカウントが削除されました。データが消えるまで時間がかかる場合があります。"
common/views/components/user-list-editor.vue:
users: "ユーザー"
rename: "リスト名を変更"
@ -785,8 +790,6 @@ desktop/views/components/renote-form.vue:
failure: "Renoteに失敗しました"
desktop/views/components/renote-form-window.vue:
title: "この投稿をRenoteしますか"
desktop/views/components/timeline.core.vue:
empty: "投稿がありません"
desktop/views/pages/user-following-or-followers.vue:
following: "{user}のフォロー"
followers: "{user}のフォロワー"
@ -1364,7 +1367,6 @@ desktop/views/pages/user/user.timeline.vue:
with-replies: "投稿と返信"
with-media: "メディア"
my-posts: "私の投稿"
empty: "このユーザーはまだ何も投稿していないようです。"
desktop/views/widgets/messaging.vue:
title: "メッセージ"
desktop/views/widgets/notifications.vue:
@ -1462,8 +1464,6 @@ mobile/views/components/sub-note-content.vue:
deleted: "この投稿は削除されました"
media-count: "{}つのメディア"
poll: "アンケート"
mobile/views/components/timeline.vue:
empty: "投稿がありません"
mobile/views/components/ui.header.vue:
welcome-back: "おかえりなさい、"
adjective: "さん"
@ -1480,9 +1480,6 @@ mobile/views/components/ui.nav.vue:
settings: "設定"
admin: "管理"
about: "Misskeyについて"
mobile/views/components/user-timeline.vue:
no-notes: "このユーザーは投稿していないようです。"
no-notes-with-media: "メディア付き投稿はありません。"
mobile/views/pages/favorites.vue:
title: "お気に入り"
mobile/views/pages/user-lists.vue:
@ -1501,8 +1498,6 @@ mobile/views/pages/home.vue:
global: "グローバル"
mentions: "あなた宛て"
messages: "メッセージ"
mobile/views/pages/home.timeline.vue:
empty: "投稿がありません"
mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
mobile/views/pages/welcome.vue:
@ -1595,8 +1590,6 @@ mobile/views/pages/user/home.vue:
frequently-replied-users: "よく話すユーザー"
followers-you-know: "Seguidores que você conhece"
last-used-at: "Ativo pela última vez"
mobile/views/pages/user/home.notes.vue:
no-notes: "Nenhuma mensagem"
mobile/views/pages/user/home.photos.vue:
no-photos: "Sem fotos"
deck:
@ -1624,7 +1617,6 @@ deck:
description: "サーバーの運営者により、このタイムラインは使用できない状態に設定されています。"
deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
deck/deck.user-column.vue:
follows-you: "フォローされています"

View File

@ -144,6 +144,7 @@ common:
is-remote-post: "この投稿情報はコピーです。"
view-on-remote: "正確な情報を見る"
renoted-by: "{user}がRenote"
no-notes: "投稿がありません"
error:
title: "Что-то пошло не так :("
retry: "Повторить"
@ -537,6 +538,10 @@ common/views/components/profile-editor.vue:
mute-list: "ミュート"
blocking-list: "ブロック"
export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
enter-password: "パスワードを入力してください"
danger-zone: "危険な設定"
delete-account: "アカウントを削除"
account-deleted: "アカウントが削除されました。データが消えるまで時間がかかる場合があります。"
common/views/components/user-list-editor.vue:
users: "ユーザー"
rename: "リスト名を変更"
@ -785,8 +790,6 @@ desktop/views/components/renote-form.vue:
failure: "Renoteに失敗しました"
desktop/views/components/renote-form-window.vue:
title: "この投稿をRenoteしますか"
desktop/views/components/timeline.core.vue:
empty: "投稿がありません"
desktop/views/pages/user-following-or-followers.vue:
following: "{user}のフォロー"
followers: "{user}のフォロワー"
@ -1364,7 +1367,6 @@ desktop/views/pages/user/user.timeline.vue:
with-replies: "投稿と返信"
with-media: "メディア"
my-posts: "私の投稿"
empty: "このユーザーはまだ何も投稿していないようです。"
desktop/views/widgets/messaging.vue:
title: "メッセージ"
desktop/views/widgets/notifications.vue:
@ -1462,8 +1464,6 @@ mobile/views/components/sub-note-content.vue:
deleted: "この投稿は削除されました"
media-count: "{}つのメディア"
poll: "アンケート"
mobile/views/components/timeline.vue:
empty: "投稿がありません"
mobile/views/components/ui.header.vue:
welcome-back: "おかえりなさい、"
adjective: "さん"
@ -1480,9 +1480,6 @@ mobile/views/components/ui.nav.vue:
settings: "設定"
admin: "管理"
about: "Misskeyについて"
mobile/views/components/user-timeline.vue:
no-notes: "このユーザーは投稿していないようです。"
no-notes-with-media: "メディア付き投稿はありません。"
mobile/views/pages/favorites.vue:
title: "お気に入り"
mobile/views/pages/user-lists.vue:
@ -1501,8 +1498,6 @@ mobile/views/pages/home.vue:
global: "グローバル"
mentions: "あなた宛て"
messages: "メッセージ"
mobile/views/pages/home.timeline.vue:
empty: "投稿がありません"
mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
mobile/views/pages/welcome.vue:
@ -1595,8 +1590,6 @@ mobile/views/pages/user/home.vue:
frequently-replied-users: "よく話すユーザー"
followers-you-know: "知り合いのフォロワー"
last-used-at: "最終ログイン"
mobile/views/pages/user/home.notes.vue:
no-notes: "投稿はありません"
mobile/views/pages/user/home.photos.vue:
no-photos: "写真はありません"
deck:
@ -1624,7 +1617,6 @@ deck:
description: "サーバーの運営者により、このタイムラインは使用できない状態に設定されています。"
deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
edit: "オプション"
deck/deck.user-column.vue:
follows-you: "フォローされています"

View File

@ -144,6 +144,7 @@ common:
is-remote-post: "该投稿已被复制."
view-on-remote: "查看准确的信息"
renoted-by: "由 {user} Renote"
no-notes: "没有帖子"
error:
title: "出现问题"
retry: "重试"
@ -537,6 +538,10 @@ common/views/components/profile-editor.vue:
mute-list: "屏蔽列表"
blocking-list: "黑名单"
export-requested: "导出请求已提交。可能需要花一些时间。导出的文件将保存到网盘中。"
enter-password: "请输入您的密码"
danger-zone: "危险选项"
delete-account: "删除帐户"
account-deleted: "帐户已被删除。 数据会在一段时间之后清除。"
common/views/components/user-list-editor.vue:
users: "用户"
rename: "重命名列表"
@ -785,8 +790,6 @@ desktop/views/components/renote-form.vue:
failure: "重新发送失败"
desktop/views/components/renote-form-window.vue:
title: "您是否要重新发送?"
desktop/views/components/timeline.core.vue:
empty: "没有帖子"
desktop/views/pages/user-following-or-followers.vue:
following: "{user}的正在关注"
followers: "{user}的关注者"
@ -1364,7 +1367,6 @@ desktop/views/pages/user/user.timeline.vue:
with-replies: "帖子与回复"
with-media: "媒体"
my-posts: "我的帖子"
empty: "看起来这个用户还没有发布什么呢。"
desktop/views/widgets/messaging.vue:
title: "信息"
desktop/views/widgets/notifications.vue:
@ -1462,8 +1464,6 @@ mobile/views/components/sub-note-content.vue:
deleted: "帖子已删除"
media-count: "附加{}媒体"
poll: "投票"
mobile/views/components/timeline.vue:
empty: "无帖子"
mobile/views/components/ui.header.vue:
welcome-back: "欢迎回来!"
adjective: "先生"
@ -1480,9 +1480,6 @@ mobile/views/components/ui.nav.vue:
settings: "设置"
admin: "管理"
about: "关于 Misskey"
mobile/views/components/user-timeline.vue:
no-notes: "看起来该用户还没有发表任何东西哎。"
no-notes-with-media: "媒体附件没有备注"
mobile/views/pages/favorites.vue:
title: "收藏"
mobile/views/pages/user-lists.vue:
@ -1501,8 +1498,6 @@ mobile/views/pages/home.vue:
global: "Global"
mentions: "Mentions"
messages: "信息"
mobile/views/pages/home.timeline.vue:
empty: "没有帖子"
mobile/views/pages/tag.vue:
no-posts-found: "没有找到带有主题标签“{q}”的帖子"
mobile/views/pages/welcome.vue:
@ -1595,8 +1590,6 @@ mobile/views/pages/user/home.vue:
frequently-replied-users: "活跃用户"
followers-you-know: "您可能认识的关注者"
last-used-at: "上次登录:"
mobile/views/pages/user/home.notes.vue:
no-notes: "没有帖子"
mobile/views/pages/user/home.photos.vue:
no-photos: "没有图片"
deck:
@ -1624,7 +1617,6 @@ deck:
description: "服务器管理员已禁用时间线。"
deck/deck.tl-column.vue:
is-media-only: "只有媒体的帖子"
is-media-view: "媒体视图"
edit: "选项"
deck/deck.user-column.vue:
follows-you: "关注您"

View File

@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "10.87.5",
"version": "10.89.0",
"codename": "nighthike",
"repository": {
"type": "git",
@ -95,14 +95,14 @@
"@types/websocket": "0.0.40",
"@types/ws": "6.0.1",
"animejs": "3.0.1",
"apexcharts": "3.3.0",
"apexcharts": "3.4.1",
"autobind-decorator": "2.4.0",
"autosize": "4.0.2",
"autwh": "0.1.0",
"bcryptjs": "2.4.3",
"bee-queue": "1.2.2",
"bootstrap-vue": "2.0.0-rc.11",
"cafy": "14.0.1",
"cafy": "15.1.0",
"chai": "4.2.0",
"chai-http": "4.2.1",
"chalk": "2.4.2",
@ -228,20 +228,20 @@
"uuid": "3.3.2",
"v-animate-css": "0.0.3",
"video-thumbnail-generator": "1.1.3",
"vue": "2.6.6",
"vue": "2.6.7",
"vue-color": "2.7.0",
"vue-content-loading": "1.5.3",
"vue-cropperjs": "3.0.0",
"vue-i18n": "8.8.1",
"vue-i18n": "8.8.2",
"vue-js-modal": "1.3.28",
"vue-loader": "15.6.2",
"vue-loader": "15.6.4",
"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",
"vue-svg-inline-loader": "1.2.10",
"vue-template-compiler": "2.6.6",
"vue-template-compiler": "2.6.7",
"vuedraggable": "2.17.0",
"vuewordcloud": "18.7.11",
"vuex": "3.1.0",
@ -251,7 +251,7 @@
"webpack": "4.28.4",
"webpack-cli": "3.2.1",
"websocket": "1.0.28",
"ws": "6.1.3",
"ws": "6.1.4",
"xev": "2.0.1"
}
}

View File

@ -128,15 +128,16 @@ export default Vue.extend({
},
close() {
this.$el.style.pointerEvents = 'none';
(this.$refs.bg as any).style.pointerEvents = 'none';
(this.$refs.main as any).style.pointerEvents = 'none';
anime({
targets: this.$refs.bg,
opacity: 0,
duration: 300,
easing: 'linear'
});
(this.$refs.main as any).style.pointerEvents = 'none';
anime({
targets: this.$refs.main,
opacity: 0,

View File

@ -14,8 +14,12 @@ export default Vue.extend({
<style lang="stylus" scoped>
.zxrjzpcj
display inline-block
padding 6px 8px 6px 6px
margin-top 4px
margin-bottom 4px
border-radius 32px
white-space nowrap
&:hover
text-decoration none

View File

@ -101,6 +101,13 @@
<ui-button @click="doExport()"><fa :icon="faDownload"/> {{ $t('export') }}</ui-button>
</div>
</section>
<section>
<details>
<summary>{{ $t('danger-zone') }}</summary>
<ui-button @click="deleteAccount()">{{ $t('delete-account') }}</ui-button>
</details>
</section>
</ui-card>
</template>
@ -283,6 +290,25 @@ export default Vue.extend({
type: 'info',
text: this.$t('export-requested')
});
},
async deleteAccount() {
const { canceled: canceled, result: password } = await this.$root.dialog({
title: this.$t('enter-password'),
input: {
type: 'password'
}
});
if (canceled) return;
this.$root.api('i/delete-account', {
password
}).then(() => {
this.$root.dialog({
type: 'success',
text: this.$t('account-deleted')
});
});
}
}
});

View File

@ -4,7 +4,7 @@
<div class="newer-indicator" :style="{ top: $store.state.uiHeaderHeight + 'px' }" v-show="queue.length > 0"></div>
<slot name="empty" v-if="notes.length == 0 && !fetching && inited"></slot>
<div class="empty" v-if="notes.length == 0 && !fetching && inited">{{ $t('@.no-notes') }}</div>
<mk-error v-if="!fetching && !inited" @retry="init()"/>
@ -209,6 +209,11 @@ export default Vue.extend({
> *
transition transform .3s ease, opacity .3s ease
> .empty
padding 16px
text-align center
color var(--text)
> .placeholder
padding 32px
opacity 0.3

View File

@ -1,5 +1,5 @@
<template>
<x-notes ref="timeline" :make-promise="makePromise" :media-view="mediaView" @inited="() => $emit('loaded')"/>
<x-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
</template>
<script lang="ts">
@ -22,11 +22,6 @@ export default Vue.extend({
type: Boolean,
required: false,
default: false
},
mediaView: {
type: Boolean,
required: false,
default: false
}
},

View File

@ -1,5 +1,5 @@
<template>
<x-notes ref="timeline" :make-promise="makePromise" :media-view="mediaView" @inited="() => $emit('loaded')"/>
<x-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
</template>
<script lang="ts">
@ -22,11 +22,6 @@ export default Vue.extend({
type: Boolean,
required: false,
default: false
},
mediaView: {
type: Boolean,
required: false,
default: false
}
},

View File

@ -1,6 +1,6 @@
<template>
<div class="eamppglmnmimdhrlzhplwpvyeaqmmhxu">
<slot name="empty" v-if="notes.length == 0 && !fetching && inited"></slot>
<div class="empty" v-if="notes.length == 0 && !fetching && inited">{{ $t('@.no-notes') }}</div>
<mk-error v-if="!fetching && !inited" @retry="init()"/>
@ -17,7 +17,6 @@
:note="note"
:key="note.id"
@update:note="onNoteUpdated(i, $event)"
:media-view="mediaView"
:compact="true"
:mini="true"/>
<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
@ -56,11 +55,6 @@ export default Vue.extend({
props: {
makePromise: {
required: true
},
mediaView: {
type: Boolean,
required: false,
default: false
}
},
@ -91,6 +85,9 @@ export default Vue.extend({
watch: {
queue(q) {
this.count(q.length);
},
makePromise() {
this.init();
}
},
@ -115,12 +112,12 @@ export default Vue.extend({
},
reload() {
this.queue = [];
this.notes = [];
this.init();
},
init() {
this.queue = [];
this.notes = [];
this.fetching = true;
this.makePromise().then(x => {
if (Array.isArray(x)) {
@ -204,6 +201,11 @@ export default Vue.extend({
> *
transition transform .3s ease, opacity .3s ease
> .empty
padding 16px
text-align center
color var(--text)
> .placeholder
padding 16px
opacity 0.3

View File

@ -12,25 +12,21 @@
<div class="editor" style="padding:12px" v-if="edit">
<ui-switch v-model="column.isMediaOnly" @change="onChangeSettings">{{ $t('is-media-only') }}</ui-switch>
<ui-switch v-model="column.isMediaView" @change="onChangeSettings">{{ $t('is-media-view') }}</ui-switch>
</div>
<x-list-tl v-if="column.type == 'list'"
:list="column.list"
:media-only="column.isMediaOnly"
:media-view="column.isMediaView"
ref="tl"
/>
<x-hashtag-tl v-else-if="column.type == 'hashtag'"
:tag-tl="$store.state.settings.tagTimelines.find(x => x.id == column.tagTlId)"
:media-only="column.isMediaOnly"
:media-view="column.isMediaView"
ref="tl"
/>
<x-tl v-else
:src="column.type"
:media-only="column.isMediaOnly"
:media-view="column.isMediaView"
ref="tl"
/>
</x-column>

View File

@ -6,7 +6,7 @@
</p>
<p class="desc">{{ $t('disabled-timeline.description') }}</p>
</div>
<x-notes v-else ref="timeline" :make-promise="makePromise" :media-view="mediaView" @inited="() => $emit('loaded')"/>
<x-notes v-else ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
</template>
<script lang="ts">
@ -34,11 +34,6 @@ export default Vue.extend({
type: Boolean,
required: false,
default: false
},
mediaView: {
type: Boolean,
required: false,
default: false
}
},
@ -73,16 +68,18 @@ export default Vue.extend({
watch: {
mediaOnly() {
this.fetch();
(this.$refs.timeline as any).reload();
}
},
created() {
this.makePromise = cursor => this.$root.api(this.endpoint, {
limit: fetchLimit + 1,
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
untilId: cursor ? cursor : undefined,
...this.baseQuery, ...this.query
withFiles: this.mediaOnly,
includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
}).then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();

View File

@ -61,7 +61,26 @@ export default Vue.extend({
return {
withFiles: false,
images: [],
makePromise: cursor => this.$root.api('users/notes', {
makePromise: null,
chart: null as ApexCharts
};
},
watch: {
user() {
this.fetch();
this.genPromiseMaker();
}
},
created() {
this.fetch();
this.genPromiseMaker();
},
methods: {
genPromiseMaker() {
this.makePromise = cursor => this.$root.api('users/notes', {
userId: this.user.id,
limit: fetchLimit + 1,
untilId: cursor ? cursor : undefined,
@ -82,15 +101,9 @@ export default Vue.extend({
cursor: null
};
}
})
};
},
});
},
created() {
this.fetch();
},
methods: {
fetch() {
const image = [
'image/jpeg',
@ -144,7 +157,9 @@ export default Vue.extend({
]);
}
const chart = new ApexCharts(this.$refs.chart, {
if (this.chart) this.chart.destroy();
this.chart = new ApexCharts(this.$refs.chart, {
chart: {
type: 'bar',
stacked: true,
@ -190,7 +205,7 @@ export default Vue.extend({
}
});
chart.render();
this.chart.render();
});
},
}

View File

@ -8,9 +8,6 @@
<router-link to="/explore">{{ $t('@.empty-timeline-info.explore') }}</router-link>
</div>
</template>
<template #empty>
<fa :icon="['far', 'comments']"/>{{ $t('empty') }}
</template>
</mk-notes>
</div>
</template>

View File

@ -9,7 +9,6 @@
<span :data-active="mode == 'my-posts'" @click="mode = 'my-posts'"><fa icon="user"/> {{ $t('my-posts') }}</span>
</header>
</template>
<template #empty><fa :icon="['far', 'comments']"/>{{ $t('empty') }}</template>
</mk-notes>
</div>
</template>

View File

@ -122,6 +122,10 @@ export default Vue.extend({
};
},
mounted() {
this.fetchReplies();
},
watch: {
note() {
this.fetchReplies();

View File

@ -1,6 +1,6 @@
<template>
<div class="ivaojijs">
<slot name="empty" v-if="notes.length == 0 && !fetching && inited"></slot>
<div class="empty" v-if="notes.length == 0 && !fetching && inited">{{ $t('@.no-notes') }}</div>
<mk-error v-if="!fetching && !inited" @retry="init()"/>
@ -197,6 +197,11 @@ export default Vue.extend({
@media (min-width 500px)
box-shadow 0 8px 32px rgba(#000, 0.1)
> .empty
padding 16px
text-align center
color var(--text)
.transition
.mk-notes-enter
.mk-notes-leave-to

View File

@ -1,11 +1,6 @@
<template>
<div class="mk-user-timeline">
<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')">
<template #empty>
<fa :icon="['far', 'comments']"/>
{{ withMedia ? this.$t('no-notes-with-media') : this.$t('no-notes') }}
</template>
</mk-notes>
<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
</div>
</template>

View File

@ -7,11 +7,7 @@
</div>
</ui-container>
<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')">
<template #empty>
<fa :icon="['far', 'comments']"/>{{ $t('empty') }}
</template>
</mk-notes>
<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
</div>
</template>

View File

@ -4,7 +4,7 @@
<div v-if="!fetching && notes.length > 0">
<mk-note-card v-for="note in notes" :key="note.id" :note="note"/>
</div>
<p class="empty" v-if="!fetching && notes.length == 0">{{ $t('no-notes') }}</p>
<p class="empty" v-if="!fetching && notes.length == 0">{{ $t('@.no-notes') }}</p>
</div>
</template>

View File

@ -1,7 +1,7 @@
<template>
<mk-ui>
<template #header v-if="!fetching"><img :src="avator" alt="">
<mk-user-name :user="user"/>
<template #header v-if="!fetching">
<img :src="avator" alt=""><mk-user-name :user="user"/>
</template>
<div class="wwtwuxyh" v-if="!fetching">
<div class="is-suspended" v-if="user.isSuspended"><p><fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}</p></div>
@ -23,7 +23,7 @@
</div>
<div class="description">
<mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
<x-integrations :user="user" style="margin:24px 0;"/>
<x-integrations :user="user" style="margin:20px 0;"/>
</div>
<div class="fields" v-if="user.fields">
<dl class="field" v-for="(field, i) in user.fields" :key="i">
@ -68,9 +68,9 @@
</nav>
<main>
<template v-if="$route.name == 'user'">
<x-home v-if="page == 'home'" :user="user"/>
<mk-user-timeline v-if="page == 'notes'" :user="user" key="tl"/>
<mk-user-timeline v-if="page == 'media'" :user="user" :with-media="true" key="media"/>
<x-home v-if="page == 'home'" :user="user" :key="user.id"/>
<mk-user-timeline v-if="page == 'notes'" :user="user" :key="`tl:${user.id}`"/>
<mk-user-timeline v-if="page == 'media'" :user="user" :with-media="true" :key="`media:${user.id}`"/>
</template>
<router-view :user="user"></router-view>
</main>
@ -251,9 +251,6 @@ export default Vue.extend({
@media (max-width 450px)
font-size 15px
@media (max-width 400px)
font-size 14px
> .fields
margin 8px 0
@ -285,6 +282,9 @@ export default Vue.extend({
> .info
margin 8px 0
@media (max-width 450px)
font-size 15px
> p
display inline
margin 0 16px 0 0

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>Misskey API</title>
<!-- needed for adaptive design -->
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<!--
ReDoc doesn't change outer page styles
-->
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url='/api.json'></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
</body>
</html>

View File

@ -74,7 +74,7 @@ APIはすべてリクエストのパラメータ・レスポンスともにJSON
ストリーミングAPIも提供しています。
APIリファレンスもご確認ください。
[APIリファレンス](/api-doc)もご確認ください。
### レートリミット
Misskey APIにはレートリミットがあり、短時間のうちに多数のリクエストを送信すると、一定時間APIを利用することができなくなることがあります。

View File

@ -1,40 +0,0 @@
@import "../style"
#url
padding 8px 12px 8px 8px
font-family Consolas, 'Courier New', Courier, Monaco, monospace
color #fff
background #222e40
border-radius 4px
overflow auto
white-space nowrap
> .method
display inline-block
margin 0 8px 0 0
padding 0 6px
color #fff
background #17afc7
border-radius 4px
user-select none
pointer-events none
> .host
opacity 0.7
#stability
padding 8px 12px
color #fff
border-radius 4px
&.deprecated
background #f42443
&.experimental
background #f2781a
&.stable
background #3dcc90
> b
margin-left 4px

View File

@ -1,81 +0,0 @@
extends ../../base
include ../mixins
block meta
link(rel="stylesheet" href="/docs/assets/api/endpoints/style.css")
block main
h1= title
p#url
span.method POST
span.host
= endpointUrl.host
| /
span.path= endpointUrl.path
- var stability = endpoint.stability || 'experimental';
p#stability(class=stability)
| Stability:
b= stability
if endpoint.desc
p#desc= endpoint.desc[lang] || endpoint.desc['ja-JP']
if endpoint.requireCredential
div.ui.info: p
i.fas.fa-id-card-alt(style="margin-right: 4px")
= i18n('docs.api.endpoints.require-credential')
if endpoint.kind
div.ui.info: p
i.fas.fa-unlock-alt(style="margin-right: 4px")
!= i18n('docs.api.endpoints.require-permission').replace('{permission}', `<code>${endpoint.kind}</code>`)
if endpoint.limit
div.ui.info.warn: p
i.far.fa-clock(style="margin-right: 4px")
b!= i18n('docs.api.endpoints.has-limit')
if endpoint.limit.duration
!= i18n('docs.api.endpoints.duration-limit').replace('{duration}', endpoint.limit.duration).replace('{max}', endpoint.limit.max)
if endpoint.limit.minInterval
!= i18n('docs.api.endpoints.min-interval-limit').replace('{interval}', endpoint.limit.minInterval)
if params && Object.keys(params).length > 0
section
h2= i18n('docs.api.endpoints.params')
+propTable(params)
if paramDefs
each paramDef in paramDefs
section(id= paramDef.name)
h3= paramDef.name
+propTable(paramDef.params)
if params && Object.keys(params).length == 0
section
h2= i18n('docs.api.endpoints.params')
p= i18n('docs.api.endpoints.no-params')
if res
section
h2= i18n('docs.api.endpoints.res')
if resProps
+propTable(resProps)
if resDefs
each resDef in resDefs
section(id= resDef.name)
h3= resDef.name
+propTable(resDef.props)
else
if res.type.startsWith('entity')
a(href=`/docs/${lang}/api/entities/${kebab(res.entity)}`)= res.entity
block footer
div.ui.info: p
i.fas.fa-info-circle(style="margin-right: 4px")
= i18n('docs.api.endpoints.generated')
p
= i18n('docs.api.endpoints.show-src')
a(href=src target="_blank")= i18n('docs.api.endpoints.show-src-link')

View File

@ -1,90 +0,0 @@
name: "DriveFile"
desc:
ja-JP: "ドライブのファイル。"
en-US: "A file of Drive."
props:
id:
type: "id"
optional: false
desc:
ja-JP: "ファイルID"
en-US: "The ID of this file"
createdAt:
type: "date"
optional: false
desc:
ja-JP: "アップロード日時"
en-US: "The upload date of this file"
userId:
type: "id(User)"
optional: false
desc:
ja-JP: "所有者ID"
en-US: "The ID of the owner of this file"
user:
type: "entity(User)"
optional: true
desc:
ja-JP: "所有者"
en-US: "The owner of this file"
name:
type: "string"
optional: false
desc:
ja-JP: "ファイル名"
en-US: "The name of this file"
md5:
type: "string"
optional: false
desc:
ja-JP: "ファイルのMD5ハッシュ値"
en-US: "The md5 hash value of this file"
type:
type: "string"
optional: false
desc:
ja-JP: "ファイルの種類"
en-US: "The type of this file"
datasize:
type: "number"
optional: false
desc:
ja-JP: "ファイルサイズ(bytes)"
en-US: "The size of this file (bytes)"
url:
type: "string"
optional: false
desc:
ja-JP: "ファイルのURL"
en-US: "The URL of this file"
folderId:
type: "id(DriveFolder)"
optional: true
desc:
ja-JP: "フォルダID"
en-US: "The ID of the folder of this file"
folder:
type: "entity(DriveFolder)"
optional: true
desc:
ja-JP: "フォルダ"
en-US: "The folder of this file"
isSensitive:
type: "boolean"
optional: true
desc:
ja-JP: "このメディアが「閲覧注意」(NSFW)かどうか"
en-US: "Whether this media is NSFW"

View File

@ -1,41 +0,0 @@
name: "DriveFolder"
desc:
ja-JP: "ドライブのフォルダを表します。"
en-US: "A folder of Drive."
props:
id:
type: "id"
optional: false
desc:
ja-JP: "フォルダID"
en-US: "The ID of this folder"
createdAt:
type: "date"
optional: false
desc:
ja-JP: "作成日時"
en-US: "The created date of this folder"
userId:
type: "id(User)"
optional: false
desc:
ja-JP: "所有者ID"
en-US: "The ID of the owner of this folder"
parentId:
type: "entity(DriveFolder)"
optional: false
desc:
ja-JP: "親フォルダのID (ルートなら null)"
en-US: "The ID of parent folder"
name:
type: "string"
optional: false
desc:
ja-JP: "フォルダ名"
en-US: "The name of this folder"

View File

@ -1,211 +0,0 @@
name: "Note"
desc:
ja-JP: "投稿。"
en-US: "A note."
props:
id:
type: "id"
optional: false
desc:
ja-JP: "投稿ID"
en-US: "The ID of this note"
createdAt:
type: "date"
optional: false
desc:
ja-JP: "投稿日時"
en-US: "The posted date of this note"
viaMobile:
type: "boolean"
optional: true
desc:
ja-JP: "モバイル端末から投稿したか否か(自己申告であることに留意)"
en-US: "Whether this note sent via a mobile device"
localOnly:
type: "boolean"
optional: true
desc:
ja-JP: "ローカルのみに公開する投稿か否か"
en-US: "Whether this note is no federation"
text:
type: "string"
optional: true
desc:
ja-JP: "投稿の本文"
en-US: "The text of this note"
fileIds:
type: "id(DriveFile)[]"
optional: true
desc:
ja-JP: "添付されているファイルのID (なければレスポンスでは空配列)"
en-US: "The IDs of the attached files (empty array for response if no files is attached)"
files:
type: "entity(DriveFile)[]"
optional: true
desc:
ja-JP: "添付されているファイル"
en-US: "The attached files"
userId:
type: "id(User)"
optional: false
desc:
ja-JP: "投稿者ID"
en-US: "The ID of author of this note"
user:
type: "entity(User)"
optional: true
desc:
ja-JP: "投稿者"
en-US: "The author of this note"
myReaction:
type: "string"
optional: true
desc:
ja-JP: "この投稿に対する自分の<a href='/docs/api/reactions'>リアクション</a>"
en-US: "The your <a href='/docs/api/reactions'>reaction</a> of this note"
renoteCount:
type: "number"
optional: false
desc:
ja-JP: "この投稿がRenoteされた数"
en-US: "The number of renotes for this post"
repliesCount:
type: "number"
optional: false
desc:
ja-JP: "この投稿に返信された数"
en-US: "The number of replies to this post"
reactionCounts:
type: "object"
optional: false
desc:
ja-JP: "<a href='/docs/api/reactions'>リアクション</a>をキーとし、この投稿に対するそのリアクションの数を値としたオブジェクト"
replyId:
type: "id(Note)"
optional: true
desc:
ja-JP: "返信した投稿のID"
en-US: "The ID of the replyed note"
reply:
type: "entity(Note)"
optional: true
desc:
ja-JP: "返信した投稿"
en-US: "The replyed note"
renoteId:
type: "id(Note)"
optional: true
desc:
ja-JP: "引用した投稿のID"
en-US: "The ID of the quoted note"
renote:
type: "entity(Note)"
optional: true
desc:
ja-JP: "引用した投稿"
en-US: "The quoted note"
poll:
type: "object"
optional: true
desc:
ja-JP: "投票"
en-US: "The poll"
props:
choices:
type: "object[]"
optional: false
desc:
ja-JP: "投票の選択肢"
en-US: "The choices of this poll"
props:
id:
type: "number"
optional: false
desc:
ja-JP: "選択肢ID"
en-US: "The ID of this choice"
isVoted:
type: "boolean"
optional: true
desc:
ja-JP: "自分がこの選択肢に投票したかどうか"
en-US: "Whether you voted to this choice"
text:
type: "string"
optional: false
desc:
ja-JP: "選択肢本文"
en-US: "The text of this choice"
votes:
type: "number"
optional: false
desc:
ja-JP: "この選択肢に投票された数"
en-US: "The number voted for this choice"
geo:
type: "object"
optional: true
desc:
ja-JP: "位置情報"
en-US: "Geo location"
props:
coordinates:
type: "number[]"
optional: false
desc:
ja-JP: "座標。最初に経度:-180〜180で表す。最後に緯度-90〜90で表す。"
altitude:
type: "number"
optional: false
desc:
ja-JP: "高度。メートル単位で表す。"
accuracy:
type: "number"
optional: false
desc:
ja-JP: "緯度、経度の精度。メートル単位で表す。"
altitudeAccuracy:
type: "number"
optional: false
desc:
ja-JP: "高度の精度。メートル単位で表す。"
heading:
type: "number"
optional: false
desc:
ja-JP: "方角。0〜360の角度で表す。0が北、90が東、180が南、270が西。"
speed:
type: "number"
optional: false
desc:
ja-JP: "速度。メートル / 秒数で表す。"

View File

@ -1 +0,0 @@
@import "../style"

View File

@ -1,174 +0,0 @@
name: "User"
desc:
ja-JP: "ユーザー。"
en-US: "A user."
props:
id:
type: "id"
optional: false
desc:
ja-JP: "ユーザーID"
en-US: "The ID of this user"
createdAt:
type: "date"
optional: false
desc:
ja-JP: "アカウント作成日時"
en-US: "The registered date of this user"
username:
type: "string"
optional: false
desc:
ja-JP: "ユーザー名"
en-US: "The username of this user"
description:
type: "string"
optional: false
desc:
ja-JP: "アカウントの説明(自己紹介)"
en-US: "The description of this user"
avatarId:
type: "id(DriveFile)"
optional: true
desc:
ja-JP: "アバターのID"
en-US: "The ID of the avatar of this user"
avatarUrl:
type: "string"
optional: false
desc:
ja-JP: "アバターのURL"
en-US: "The URL of the avatar of this user"
bannerId:
type: "id(DriveFile)"
optional: true
desc:
ja-JP: "バナーのID"
en-US: "The ID of the banner of this user"
bannerUrl:
type: "string"
optional: false
desc:
ja-JP: "バナーのURL"
en-US: "The URL of the banner of this user"
followersCount:
type: "number"
optional: false
desc:
ja-JP: "フォロワーの数"
en-US: "The number of the followers for this user"
followingCount:
type: "number"
optional: false
desc:
ja-JP: "フォローしているユーザーの数"
en-US: "The number of the following users for this user"
isFollowing:
type: "boolean"
optional: true
desc:
ja-JP: "自分がこのユーザーをフォローしているか"
isFollowed:
type: "boolean"
optional: true
desc:
ja-JP: "自分がこのユーザーにフォローされているか"
isMuted:
type: "boolean"
optional: true
desc:
ja-JP: "自分がこのユーザーをミュートしているか"
en-US: "Whether you muted this user"
notesCount:
type: "number"
optional: false
desc:
ja-JP: "投稿の数"
en-US: "The number of the notes of this user"
pinnedNotes:
type: "entity(Note)[]"
optional: true
desc:
ja-JP: "ピン留めされた投稿"
en-US: "The pinned note of this user"
pinnedNoteIds:
type: "id(Note)[]"
optional: true
desc:
ja-JP: "ピン留めされた投稿のID"
en-US: "The ID of the pinned note of this user"
host:
type: "string | null"
optional: false
desc:
ja-JP: "ホスト (例: example.com:3000)"
en-US: "Host (e.g. example.com:3000)"
twitter:
type: "object"
optional: true
desc:
ja-JP: "連携されているTwitterアカウント情報"
en-US: "The info of the connected twitter account of this user"
props:
userId:
type: "string"
optional: false
desc:
ja-JP: "ユーザーID"
en-US: "The user ID"
screenName:
type: "string"
optional: false
desc:
ja-JP: "ユーザー名"
en-US: "The screen name of this user"
isBot:
type: "boolean"
optional: true
desc:
ja-JP: "botか否か(自己申告であることに留意)"
en-US: "Whether is bot or not"
profile:
type: "object"
optional: false
desc:
ja-JP: "プロフィール"
en-US: "The profile of this user"
props:
location:
type: "string"
optional: true
desc:
ja-JP: "場所"
en-US: "The location of this user"
birthday:
type: "string"
optional: true
desc:
ja-JP: "誕生日 (YYYY-MM-DD)"
en-US: "The birthday of this user (YYYY-MM-DD)"

View File

@ -1,20 +0,0 @@
extends ../../base
include ../mixins
block meta
link(rel="stylesheet" href="/docs/assets/api/entities/style.css")
block main
h1= name
p#desc= desc[lang] || desc['ja-JP']
section
h2= i18n('docs.api.entities.properties')
+propTable(props)
if propDefs
each propDef in propDefs
section(id= propDef.name)
h3= propDef.name
+propTable(propDef.props)

View File

@ -1,34 +0,0 @@
mixin type(prop)
i= prop.type
if prop.kind == 'id'
if prop.entity
| (
a(href=`/docs/${lang}/api/entities/${kebab(prop.entity)}`)= prop.entity
| ID)
else
| (ID)
else if prop.kind == 'entity'
| (
a(href=`/docs/${lang}/api/entities/${kebab(prop.entity)}`)= prop.entity
| )
else if prop.kind == 'object'
if prop.hasDef
| (
a(href=`#${prop.name}`)= prop.name
| )
else if prop.kind == 'date'
| (Date)
mixin propTable(props)
table.props
thead: tr
th= i18n('docs.api.props.name')
th= i18n('docs.api.props.type')
th= i18n('docs.api.props.description')
tbody
each prop in props
tr
td.name= prop.name
td.type
+type(prop)
td.desc!= prop.desc ? prop.desc[lang] || prop.desc['ja-JP'] : null

View File

@ -1,11 +0,0 @@
@import "../style"
table.props
.name
font-weight bold
.name
.type
.optional
font-family Consolas, 'Courier New', Courier, Monaco, monospace

View File

@ -17,17 +17,6 @@ html(lang= lang)
ul
each doc in docs
li: a(href=`/docs/${lang}/${doc.name}`)= doc.title[lang] || doc.title['ja-JP']
section
h2 API
ul
li Entities
ul
each entity in entities
li: a(href=`/docs/${lang}/api/entities/${kebab(entity)}`)= entity
li Endpoints
ul
each endpoint in endpoints
li: a(href=`/docs/${lang}/api/endpoints/${kebab(endpoint.name)}`)= endpoint.name
main
article
block main

View File

@ -26,6 +26,8 @@ export type ObjectId = mongo.ObjectID;
* ID
*/
export default class ID<Maybe = string> extends Context<string | Maybe> {
public readonly name = 'ID';
constructor(optional = false, nullable = false) {
super(optional, nullable);
@ -38,7 +40,7 @@ export default class ID<Maybe = string> extends Context<string | Maybe> {
}
public getType() {
return super.getType('string');
return super.getType('String');
}
public makeOptional(): ID<undefined> {

View File

@ -0,0 +1,13 @@
/**
* ID付きエラー
*/
export class IdentifiableError extends Error {
public message: string;
public id: string;
constructor(id: string, message?: string) {
super(message);
this.message = message;
this.id = id;
}
}

View File

@ -1,15 +1,13 @@
import * as os from 'os';
import * as sysUtils from 'systeminformation';
import Logger from "./logger";
import Logger from './logger';
export async function showMachineInfo(parentLogger: Logger) {
const logger = parentLogger.createSubLogger('machine');
logger.debug(`Hostname: ${os.hostname()}`);
logger.debug(`Platform: ${process.platform}`);
logger.debug(`Architecture: ${process.arch}`);
logger.debug(`CPU: ${os.cpus().length} core`);
logger.debug(`Platform: ${process.platform} Arch: ${process.arch}`);
const mem = await sysUtils.mem();
const totalmem = (mem.total / 1024 / 1024 / 1024).toFixed(1);
const availmem = (mem.available / 1024 / 1024 / 1024).toFixed(1);
logger.debug(`MEM: ${totalmem}GB (available: ${availmem}GB)`);
logger.debug(`CPU: ${os.cpus().length} core MEM: ${totalmem}GB (available: ${availmem}GB)`);
}

View File

@ -55,6 +55,8 @@ type IUserBase = {
emojis?: string[];
tags?: string[];
isDeleted: boolean;
/**
* 凍結されているか否か
*/

View File

@ -70,6 +70,32 @@ export function processInbox(activity: any, signature: httpSignature.IParsedSign
}
}
export function createDeleteNotesJob(user: ILocalUser) {
const data = {
type: 'deleteNotes',
user: user
};
if (queueAvailable && enableQueueProcessing) {
return queue.createJob(data).save();
} else {
return handler({ data }, () => {});
}
}
export function createDeleteDriveFilesJob(user: ILocalUser) {
const data = {
type: 'deleteDriveFiles',
user: user
};
if (queueAvailable && enableQueueProcessing) {
return queue.createJob(data).save();
} else {
return handler({ data }, () => {});
}
}
export function createExportNotesJob(user: ILocalUser) {
const data = {
type: 'exportNotes',

View File

@ -0,0 +1,55 @@
import * as bq from 'bee-queue';
import * as mongo from 'mongodb';
import { queueLogger } from '../logger';
import User from '../../models/user';
import DriveFile from '../../models/drive-file';
import deleteFile from '../../services/drive/delete-file';
const logger = queueLogger.createSubLogger('delete-drive-files');
export async function deleteDriveFiles(job: bq.Job, done: any): Promise<void> {
logger.info(`Deleting drive files of ${job.data.user._id} ...`);
const user = await User.findOne({
_id: new mongo.ObjectID(job.data.user._id.toString())
});
let deletedCount = 0;
let ended = false;
let cursor: any = null;
while (!ended) {
const files = await DriveFile.find({
userId: user._id,
...(cursor ? { _id: { $gt: cursor } } : {})
}, {
limit: 100,
sort: {
_id: 1
}
});
if (files.length === 0) {
ended = true;
if (job.reportProgress) job.reportProgress(100);
break;
}
cursor = files[files.length - 1]._id;
for (const file of files) {
await deleteFile(file);
deletedCount++;
}
const total = await DriveFile.count({
userId: user._id,
});
if (job.reportProgress) job.reportProgress(deletedCount / total);
}
logger.succ(`All drive files (${deletedCount}) of ${user._id} has been deleted.`);
done();
}

View File

@ -0,0 +1,55 @@
import * as bq from 'bee-queue';
import * as mongo from 'mongodb';
import { queueLogger } from '../logger';
import Note from '../../models/note';
import deleteNote from '../../services/note/delete';
import User from '../../models/user';
const logger = queueLogger.createSubLogger('delete-notes');
export async function deleteNotes(job: bq.Job, done: any): Promise<void> {
logger.info(`Deleting notes of ${job.data.user._id} ...`);
const user = await User.findOne({
_id: new mongo.ObjectID(job.data.user._id.toString())
});
let deletedCount = 0;
let ended = false;
let cursor: any = null;
while (!ended) {
const notes = await Note.find({
userId: user._id,
...(cursor ? { _id: { $gt: cursor } } : {})
}, {
limit: 100,
sort: {
_id: 1
}
});
if (notes.length === 0) {
ended = true;
if (job.reportProgress) job.reportProgress(100);
break;
}
cursor = notes[notes.length - 1]._id;
for (const note of notes) {
await deleteNote(user, note, true);
deletedCount++;
}
const total = await Note.count({
userId: user._id,
});
if (job.reportProgress) job.reportProgress(deletedCount / total);
}
logger.succ(`All notes (${deletedCount}) of ${user._id} has been deleted.`);
done();
}

View File

@ -1,5 +1,7 @@
import deliver from './http/deliver';
import processInbox from './http/process-inbox';
import { deleteNotes } from './delete-notes';
import { deleteDriveFiles } from './delete-drive-files';
import { exportNotes } from './export-notes';
import { exportFollowing } from './export-following';
import { exportMute } from './export-mute';
@ -9,6 +11,8 @@ import { queueLogger } from '../logger';
const handlers: any = {
deliver,
processInbox,
deleteNotes,
deleteDriveFiles,
exportNotes,
exportFollowing,
exportMute,

View File

@ -48,7 +48,6 @@ export default async (ctx: Router.IRouterContext) => {
const partOf = `${config.url}/users/${userId}/followers`;
if (page) {
// Construct query
const query = {
followeeId: user._id
} as any;

View File

@ -48,7 +48,6 @@ export default async (ctx: Router.IRouterContext) => {
const partOf = `${config.url}/users/${userId}/following`;
if (page) {
// Construct query
const query = {
followerId: user._id
} as any;

View File

@ -78,7 +78,6 @@ export default async (ctx: Router.IRouterContext) => {
}
//#endregion
// Issue query
const notes = await Note
.find(query, {
limit: limit,

View File

@ -3,45 +3,36 @@ import * as Koa from 'koa';
import { IEndpoint } from './endpoints';
import authenticate from './authenticate';
import call from './call';
import { IUser } from '../../models/user';
import { IApp } from '../../models/app';
import { ApiError } from './error';
export default async (endpoint: IEndpoint, ctx: Koa.BaseContext) => {
export default (endpoint: IEndpoint, ctx: Koa.BaseContext) => new Promise((res) => {
const body = ctx.is('multipart/form-data') ? (ctx.req as any).body : ctx.request.body;
const reply = (x?: any, y?: any) => {
if (x === undefined) {
const reply = (x?: any, y?: ApiError) => {
if (x == null) {
ctx.status = 204;
} else if (typeof x === 'number') {
ctx.status = x;
ctx.body = {
error: x === 500 ? 'INTERNAL_ERROR' : y
};
ctx.body = { error: y };
} else {
ctx.body = x;
}
res();
};
let user: IUser;
let app: IApp;
// Authentication
try {
[user, app] = await authenticate(body['i']);
} catch (e) {
reply(403, 'AUTHENTICATION_FAILED');
return;
}
let res;
// API invoking
try {
res = await call(endpoint.name, user, app, body, (ctx.req as any).file);
} catch (e) {
reply(400, e);
return;
}
reply(res);
};
authenticate(body['i']).then(([user, app]) => {
// API invoking
call(endpoint.name, user, app, body, (ctx.req as any).file).then(res => {
reply(res);
}).catch(e => {
reply(e.httpStatusCode ? e.httpStatusCode : e.kind == 'client' ? 400 : 500, e);
});
}).catch(() => {
reply(403, new ApiError({
message: 'Authentication failed. Please ensure your token is correct.',
code: 'AUTHENTICATION_FAILED',
id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14'
}));
});
});

View File

@ -2,6 +2,14 @@ import limiter from './limiter';
import { IUser } from '../../models/user';
import { IApp } from '../../models/app';
import endpoints from './endpoints';
import { ApiError } from './error';
import { apiLogger } from './logger';
const accessDenied = {
message: 'Access denied.',
code: 'ACCESS_DENIED',
id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e'
};
export default async (endpoint: string, user: IUser, app: IApp, data: any, file?: any) => {
const isSecure = user != null && app == null;
@ -9,58 +17,72 @@ export default async (endpoint: string, user: IUser, app: IApp, data: any, file?
const ep = endpoints.find(e => e.name === endpoint);
if (ep == null) {
throw 'ENDPOINT_NOT_FOUND';
throw new ApiError({
message: 'No such endpoint.',
code: 'NO_SUCH_ENDPOINT',
id: 'f8080b67-5f9c-4eb7-8c18-7f1eeae8f709',
httpStatusCode: 404
});
}
if (ep.meta.secure && !isSecure) {
throw 'ACCESS_DENIED';
throw new ApiError(accessDenied);
}
if (ep.meta.requireCredential && user == null) {
throw 'CREDENTIAL_REQUIRED';
throw new ApiError({
message: 'Credential required.',
code: 'CREDENTIAL_REQUIRED',
id: '1384574d-a912-4b81-8601-c7b1c4085df1',
httpStatusCode: 401
});
}
if (ep.meta.requireCredential && user.isSuspended) {
throw 'YOUR_ACCOUNT_HAS_BEEN_SUSPENDED';
throw new ApiError(accessDenied, { reason: 'Your account has been suspended.' });
}
if (ep.meta.requireAdmin && !user.isAdmin) {
throw 'YOU_ARE_NOT_ADMIN';
throw new ApiError(accessDenied, { reason: 'You are not the admin.' });
}
if (ep.meta.requireModerator && !user.isAdmin && !user.isModerator) {
throw 'YOU_ARE_NOT_MODERATOR';
throw new ApiError(accessDenied, { reason: 'You are not a moderator.' });
}
if (app && ep.meta.kind && !app.permission.some(p => p === ep.meta.kind)) {
throw 'PERMISSION_DENIED';
throw new ApiError({
message: 'Your app does not have the necessary permissions to use this endpoint.',
code: 'PERMISSION_DENIED',
id: '1370e5b7-d4eb-4566-bb1d-7748ee6a1838',
});
}
if (ep.meta.requireCredential && ep.meta.limit) {
try {
await limiter(ep, user); // Rate limit
} catch (e) {
// drop request if limit exceeded
throw 'RATE_LIMIT_EXCEEDED';
}
// Rate limit
await limiter(ep, user).catch(e => {
throw new ApiError({
message: 'Rate limit exceeded. Please try again later.',
code: 'RATE_LIMIT_EXCEEDED',
id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
httpStatusCode: 429
});
});
}
let res;
// API invoking
try {
res = await ep.exec(data, user, app, file);
} catch (e) {
if (e && e.name == 'INVALID_PARAM') {
throw {
code: e.name,
param: e.param,
reason: e.message
};
} else {
return await ep.exec(data, user, app, file).catch((e: Error) => {
if (e instanceof ApiError) {
throw e;
} else {
apiLogger.error(e);
throw new ApiError(null, {
e: {
message: e.message,
code: e.name,
stack: e.stack
}
});
}
}
return res;
});
};

View File

@ -1,18 +1,19 @@
import * as mongo from 'mongodb';
import Note from "../../../models/note";
import User, { isRemoteUser, isLocalUser } from "../../../models/user";
import Note from '../../../models/note';
import User, { isRemoteUser, isLocalUser } from '../../../models/user';
import { IdentifiableError } from '../../../misc/identifiable-error';
/**
* Get valied note for API processing
* Get note for API processing
*/
export async function getValiedNote(noteId: mongo.ObjectID) {
export async function getNote(noteId: mongo.ObjectID) {
const note = await Note.findOne({
_id: noteId,
deletedAt: { $exists: false }
});
if (note === null) {
throw 'note not found';
throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.');
}
return note;
@ -23,11 +24,22 @@ export async function getValiedNote(noteId: mongo.ObjectID) {
*/
export async function getUser(userId: mongo.ObjectID) {
const user = await User.findOne({
_id: userId
_id: userId,
$or: [{
isDeleted: { $exists: false }
}, {
isDeleted: false
}]
}, {
fields: {
data: false,
profile: false,
clientSettings: false
}
});
if (user == null) {
throw 'user not found';
if (user === null) {
throw new IdentifiableError('15348ddd-432d-49c2-8a5a-8069753becff', 'No such user.');
}
return user;

View File

@ -2,6 +2,7 @@ import * as fs from 'fs';
import { ILocalUser } from '../../models/user';
import { IApp } from '../../models/app';
import { IEndpointMeta } from './endpoints';
import { ApiError } from './error';
type Params<T extends IEndpointMeta> = {
[P in keyof T['params']]: T['params'][P]['transform'] extends Function
@ -9,13 +10,19 @@ type Params<T extends IEndpointMeta> = {
: ReturnType<T['params'][P]['validator']['get']>[0];
};
export default function <T extends IEndpointMeta>(meta: T, cb: (params: Params<T>, user: ILocalUser, app: IApp, file?: any, cleanup?: Function) => Promise<any>): (params: any, user: ILocalUser, app: IApp, file?: any) => Promise<any> {
export type Response = Record<string, any> | void;
export default function <T extends IEndpointMeta>(meta: T, cb: (params: Params<T>, user: ILocalUser, app: IApp, file?: any, cleanup?: Function) => Promise<Response>): (params: any, user: ILocalUser, app: IApp, file?: any) => Promise<any> {
return (params: any, user: ILocalUser, app: IApp, file?: any) => {
function cleanup() {
fs.unlink(file.path, () => {});
}
if (meta.requireFile && file == null) return Promise.reject('file required');
if (meta.requireFile && file == null) return Promise.reject(new ApiError({
message: 'File required.',
code: 'FILE_REQUIRED',
id: '4267801e-70d1-416a-b011-4ee502885d8b',
}));
const [ps, pserr] = getParams(meta, params);
if (pserr) {
@ -27,17 +34,22 @@ export default function <T extends IEndpointMeta>(meta: T, cb: (params: Params<T
};
}
function getParams<T extends IEndpointMeta>(defs: T, params: any): [Params<T>, Error] {
function getParams<T extends IEndpointMeta>(defs: T, params: any): [Params<T>, ApiError] {
if (defs.params == null) return [params, null];
const x: any = {};
let err: Error = null;
let err: ApiError = null;
Object.entries(defs.params).some(([k, def]) => {
const [v, e] = def.validator.get(params[k]);
if (e) {
err = new Error(e.message);
err.name = 'INVALID_PARAM';
(err as any).param = k;
err = new ApiError({
message: 'Invalid param.',
code: 'INVALID_PARAM',
id: '3d81ceae-475f-4600-b2a8-2bc116157532',
}, {
param: k,
reason: e.message
});
return true;
} else {
if (v === undefined && def.hasOwnProperty('default')) {

View File

@ -2,18 +2,30 @@ import { Context } from 'cafy';
import * as path from 'path';
import * as glob from 'glob';
export type Param = {
validator: Context<any>;
transform?: any;
default?: any;
desc?: { [key: string]: string };
ref?: string;
};
export interface IEndpointMeta {
stability?: string; //'deprecated' | 'experimental' | 'stable';
desc?: { [key: string]: string };
tags?: string[];
params?: {
[key: string]: Param;
};
errors?: {
[key: string]: {
validator: Context<any>;
transform?: any;
default?: any;
desc?: { [key: string]: string };
ref?: string;
message: string;
code: string;
id: string;
};
};

View File

@ -4,6 +4,8 @@ import Report, { packMany } from '../../../../models/abuse-user-report';
import define from '../../define';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -25,11 +27,7 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
if (ps.sinceId && ps.untilId) {
return rej('cannot set sinceId and untilId');
}
export default define(meta, async (ps) => {
const sort = {
_id: -1
};
@ -51,5 +49,5 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
sort: sort
});
res(await packMany(reports));
}));
return await packMany(reports);
});

View File

@ -4,6 +4,8 @@ import define from '../../../define';
import { fallback } from '../../../../../prelude/symbol';
export const meta = {
tags: ['admin'],
requireCredential: false,
requireModerator: true,
@ -46,7 +48,7 @@ const sort: any = { // < https://github.com/Microsoft/TypeScript/issues/1863
[fallback]: { _id: -1 }
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
export default define(meta, async (ps, me) => {
const q = {
'metadata.deletedAt': { $exists: false },
} as any;
@ -61,5 +63,5 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
skip: ps.offset
});
res(await packMany(files, { detail: true, withUser: true, self: true }));
}));
return await packMany(files, { detail: true, withUser: true, self: true });
});

View File

@ -2,8 +2,11 @@ import $ from 'cafy';
import ID, { transform } from '../../../../../misc/cafy-id';
import define from '../../../define';
import DriveFile from '../../../../../models/drive-file';
import { ApiError } from '../../../error';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -12,17 +15,25 @@ export const meta = {
validator: $.type(ID),
transform: transform,
},
},
errors: {
noSuchFile: {
message: 'No such file.',
code: 'NO_SUCH_FILE',
id: 'caf3ca38-c6e5-472e-a30c-b05377dcc240'
}
}
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
export default define(meta, async (ps, me) => {
const file = await DriveFile.findOne({
_id: ps.fileId
});
if (file == null) {
return rej('file not found');
throw new ApiError(meta.errors.noSuchFile);
}
res(file);
}));
return file;
});

View File

@ -7,6 +7,8 @@ export const meta = {
'ja-JP': 'カスタム絵文字を追加します。'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -26,7 +28,7 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const emoji = await Emoji.insert({
updatedAt: new Date(),
name: ps.name,
@ -35,7 +37,7 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
url: ps.url
});
res({
return {
id: emoji._id
});
}));
};
});

View File

@ -7,6 +7,8 @@ export const meta = {
'ja-JP': 'カスタム絵文字を取得します。'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -18,16 +20,16 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const emojis = await Emoji.find({
host: ps.host
});
res(emojis.map(e => ({
return emojis.map(e => ({
id: e._id,
name: e.name,
aliases: e.aliases,
host: e.host,
url: e.url
})));
}));
}));
});

View File

@ -8,6 +8,8 @@ export const meta = {
'ja-JP': 'カスタム絵文字を削除します。'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -18,14 +20,14 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const emoji = await Emoji.findOne({
_id: ps.id
});
if (emoji == null) return rej('emoji not found');
if (emoji == null) throw new Error('emoji not found');
await Emoji.remove({ _id: emoji._id });
res();
}));
return;
});

View File

@ -8,6 +8,8 @@ export const meta = {
'ja-JP': 'カスタム絵文字を更新します。'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -30,12 +32,12 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const emoji = await Emoji.findOne({
_id: ps.id
});
if (emoji == null) return rej('emoji not found');
if (emoji == null) throw new Error('emoji not found');
await Emoji.update({ _id: emoji._id }, {
$set: {
@ -46,5 +48,5 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
}
});
res();
}));
return;
});

View File

@ -5,6 +5,8 @@ import User from '../../../../../models/user';
import deleteFollowing from '../../../../../services/following/delete';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -15,7 +17,7 @@ export const meta = {
}
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
export default define(meta, async (ps, me) => {
const followings = await Following.find({
'_follower.host': ps.host
});
@ -29,5 +31,5 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
deleteFollowing(pair[0], pair[1]);
}
res();
}));
return;
});

View File

@ -3,6 +3,8 @@ import define from '../../../define';
import Instance from '../../../../../models/instance';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -21,11 +23,11 @@ export const meta = {
}
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
export default define(meta, async (ps, me) => {
const instance = await Instance.findOne({ host: ps.host });
if (instance == null) {
return rej('instance not found');
throw new Error('instance not found');
}
Instance.update({ host: ps.host }, {
@ -35,5 +37,5 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
}
});
res();
}));
return;
});

View File

@ -7,13 +7,15 @@ export const meta = {
'ja-JP': '招待コードを発行します。'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
params: {}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const code = rndstr({ length: 5, chars: '0-9' });
await RegistrationTicket.insert({
@ -21,7 +23,7 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
code: code
});
res({
return {
code: code
});
}));
};
});

View File

@ -9,6 +9,8 @@ export const meta = {
'en-US': 'Mark a user as moderator.'
},
tags: ['admin'],
requireCredential: true,
requireAdmin: true,
@ -24,13 +26,13 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
await User.update({
@ -41,5 +43,5 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
}
});
res();
}));
return;
});

View File

@ -9,6 +9,8 @@ export const meta = {
'en-US': 'Unmark a user as moderator.'
},
tags: ['admin'],
requireCredential: true,
requireAdmin: true,
@ -24,13 +26,13 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
await User.update({
@ -41,5 +43,5 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
}
});
res();
}));
return;
});

View File

@ -2,14 +2,16 @@ import define from '../../../define';
import { destroy } from '../../../../../queue';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
params: {}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
destroy();
res();
}));
return;
});

View File

@ -4,6 +4,8 @@ import define from '../../define';
import AbuseUserReport from '../../../../models/abuse-user-report';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -15,18 +17,18 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const report = await AbuseUserReport.findOne({
_id: ps.reportId
});
if (report == null) {
return rej('report not found');
throw new Error('report not found');
}
await AbuseUserReport.remove({
_id: report._id
});
res();
}));
return;
});

View File

@ -10,6 +10,8 @@ export const meta = {
'ja-JP': '指定したユーザーのパスワードをリセットします。',
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -25,17 +27,17 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
if (user.isAdmin) {
return rej('cannot reset password of admin');
throw new Error('cannot reset password of admin');
}
const passwd = rndstr('a-zA-Z0-9', 8);
@ -46,12 +48,12 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
await User.findOneAndUpdate({
_id: user._id
}, {
$set: {
password: hash
}
});
res({
password: passwd
$set: {
password: hash
}
});
}));
return {
password: passwd
};
});

View File

@ -8,6 +8,8 @@ export const meta = {
'ja-JP': '指定したユーザーの情報を取得します。',
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -23,18 +25,18 @@ export const meta = {
}
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
export default define(meta, async (ps, me) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
if (me.isModerator && user.isAdmin) {
return rej('cannot show info of admin');
throw new Error('cannot show info of admin');
}
res(user);
}));
return user;
});

View File

@ -4,6 +4,8 @@ import define from '../../define';
import { fallback } from '../../../../prelude/symbol';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -63,7 +65,7 @@ const sort: any = { // < https://github.com/Microsoft/TypeScript/issues/1863
[fallback]: { _id: -1 }
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
export default define(meta, async (ps, me) => {
const q = {
$and: []
} as any;
@ -99,5 +101,5 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
skip: ps.offset
});
res(await Promise.all(users.map(user => pack(user, me, { detail: true }))));
}));
return await Promise.all(users.map(user => pack(user, me, { detail: true })));
});

View File

@ -9,6 +9,8 @@ export const meta = {
'en-US': 'Make silence a user.'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -24,17 +26,17 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
if (user.isAdmin) {
return rej('cannot silence admin');
throw new Error('cannot silence admin');
}
await User.findOneAndUpdate({
@ -45,5 +47,5 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
}
});
res();
}));
return;
});

View File

@ -9,6 +9,8 @@ export const meta = {
'en-US': 'Suspend a user.'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -24,30 +26,30 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
if (user.isAdmin) {
return rej('cannot suspend admin');
throw new Error('cannot suspend admin');
}
if (user.isModerator) {
return rej('cannot suspend moderator');
throw new Error('cannot suspend moderator');
}
await User.findOneAndUpdate({
_id: user._id
}, {
$set: {
isSuspended: true
}
});
$set: {
isSuspended: true
}
});
res();
}));
return;
});

View File

@ -9,6 +9,8 @@ export const meta = {
'en-US': 'Unsilence a user.'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -24,13 +26,13 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
await User.findOneAndUpdate({
@ -41,5 +43,5 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
}
});
res();
}));
return;
});

View File

@ -9,6 +9,8 @@ export const meta = {
'en-US': 'Unsuspend a user.'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -24,22 +26,22 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
await User.findOneAndUpdate({
_id: user._id
}, {
$set: {
isSuspended: false
}
});
$set: {
isSuspended: false
}
});
res();
}));
return;
});

View File

@ -9,6 +9,8 @@ export const meta = {
'en-US': 'Mark a user as unverified.'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -24,22 +26,22 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
await User.findOneAndUpdate({
_id: user._id
}, {
$set: {
isVerified: false
}
});
$set: {
isVerified: false
}
});
res();
}));
return;
});

View File

@ -7,6 +7,8 @@ export const meta = {
'ja-JP': 'インスタンスの設定を更新します。'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -323,7 +325,7 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const set = {} as any;
if (ps.broadcasts) {
@ -506,5 +508,5 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
$set: set
}, { upsert: true });
res();
}));
return;
});

View File

@ -11,6 +11,8 @@ export const meta = {
'en-US': 'Update specified remote user information.'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -26,9 +28,10 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise((res, rej) => {
updatePersonById(ps.userId).then(() => res(), e => rej(e));
}));
export default define(meta, async (ps) => {
await updatePersonById(ps.userId);
return;
});
async function updatePersonById(userId: mongo.ObjectID) {
const user = await getRemoteUser(userId);

View File

@ -9,6 +9,8 @@ export const meta = {
'en-US': 'Mark a user as verified.'
},
tags: ['admin'],
requireCredential: true,
requireModerator: true,
@ -24,22 +26,22 @@ export const meta = {
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
throw new Error('user not found');
}
await User.findOneAndUpdate({
_id: user._id
}, {
$set: {
isVerified: true
}
});
$set: {
isVerified: true
}
});
res();
}));
return;
});

View File

@ -3,10 +3,12 @@ import define from '../../define';
import fetchMeta from '../../../../misc/fetch-meta';
export const meta = {
tags: ['hashtags'],
requireCredential: false,
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
const instance = await fetchMeta();
const hidedTags = instance.hidedTags.map(t => t.toLowerCase());
@ -40,7 +42,7 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
//#endregion
if (data.length == 0) {
return res([]);
return [];
}
let tags: {
@ -66,5 +68,5 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
tags = tags.slice(0, 30);
res(tags);
}));
return tags;
});

View File

@ -7,8 +7,11 @@ import { createPerson } from '../../../../remote/activitypub/models/person';
import Note, { pack as packNote, INote } from '../../../../models/note';
import { createNote } from '../../../../remote/activitypub/models/note';
import Resolver from '../../../../remote/activitypub/resolver';
import { ApiError } from '../../error';
export const meta = {
tags: ['federation'],
desc: {
'ja-JP': 'URIを指定してActivityPubオブジェクトを参照します。'
},
@ -23,13 +26,24 @@ export const meta = {
}
},
},
errors: {
noSuchObject: {
message: 'No such object.',
code: 'NO_SUCH_OBJECT',
id: 'dc94d745-1262-4e63-a17d-fecaa57efc82'
}
}
};
export default define(meta, (ps) => new Promise((res, rej) => {
fetchAny(ps.uri)
.then(object => object != null ? res(object) : rej('object not found'))
.catch(e => rej(e));
}));
export default define(meta, async (ps) => {
const object = await fetchAny(ps.uri);
if (object) {
return object;
} else {
throw new ApiError(meta.errors.noSuchObject);
}
});
/***
* URIからUserかNoteを解決する

View File

@ -4,6 +4,8 @@ import App, { pack } from '../../../../models/app';
import define from '../../define';
export const meta = {
tags: ['app'],
requireCredential: false,
params: {
@ -27,7 +29,7 @@ export const meta = {
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
// Generate secret
const secret = rndstr('a-zA-Z0-9', 32);
@ -42,9 +44,8 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
secret: secret
});
// Response
res(await pack(app, null, {
return await pack(app, null, {
detail: true,
includeSecret: true
}));
}));
});
});

View File

@ -2,29 +2,39 @@ import $ from 'cafy';
import ID, { transform } from '../../../../misc/cafy-id';
import App, { pack } from '../../../../models/app';
import define from '../../define';
import { ApiError } from '../../error';
export const meta = {
tags: ['app'],
params: {
appId: {
validator: $.type(ID),
transform: transform
},
},
errors: {
noSuchApp: {
message: 'No such app.',
code: 'NO_SUCH_APP',
id: 'dce83913-2dc6-4093-8a7b-71dbb11718a3'
}
}
};
export default define(meta, (ps, user, app) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user, app) => {
const isSecure = user != null && app == null;
// Lookup app
const ap = await App.findOne({ _id: ps.appId });
if (ap === null) {
return rej('app not found');
throw new ApiError(meta.errors.noSuchApp);
}
// Send response
res(await pack(ap, user, {
return await pack(ap, user, {
detail: true,
includeSecret: isSecure && ap.userId.equals(user._id)
}));
}));
});
});

View File

@ -5,8 +5,11 @@ import App from '../../../../models/app';
import AuthSess from '../../../../models/auth-session';
import AccessToken from '../../../../models/access-token';
import define from '../../define';
import { ApiError } from '../../error';
export const meta = {
tags: ['auth'],
requireCredential: true,
secure: true,
@ -15,16 +18,24 @@ export const meta = {
token: {
validator: $.str
}
},
errors: {
noSuchSession: {
message: 'No such session.',
code: 'NO_SUCH_SESSION',
id: '9c72d8de-391a-43c1-9d06-08d29efde8df'
},
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
export default define(meta, async (ps, user) => {
// Fetch token
const session = await AuthSess
.findOne({ token: ps.token });
if (session === null) {
return rej('session not found');
throw new ApiError(meta.errors.noSuchSession);
}
// Generate access token
@ -64,6 +75,5 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
}
});
// Response
res();
}));
return;
});

View File

@ -4,25 +4,36 @@ import App from '../../../../../models/app';
import AuthSess from '../../../../../models/auth-session';
import config from '../../../../../config';
import define from '../../../define';
import { ApiError } from '../../../error';
export const meta = {
tags: ['auth'],
requireCredential: false,
params: {
appSecret: {
validator: $.str
}
},
errors: {
noSuchApp: {
message: 'No such app.',
code: 'NO_SUCH_APP',
id: '92f93e63-428e-4f2f-a5a4-39e1407fe998'
}
}
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
export default define(meta, async (ps) => {
// Lookup app
const app = await App.findOne({
secret: ps.appSecret
});
if (app == null) {
return rej('app not found');
throw new ApiError(meta.errors.noSuchApp);
}
// Generate token
@ -35,9 +46,8 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
token: token
});
// Response
res({
return {
token: doc.token,
url: `${config.auth_url}/${doc.token}`
});
}));
};
});

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