Compare commits

...

96 Commits

Author SHA1 Message Date
7495206db2 Merge branch 'develop' 2019-04-25 07:48:29 +09:00
fe87d16d46 11.4.0 2019-04-25 07:48:12 +09:00
0db54386cd Resolve #3119 2019-04-25 07:46:39 +09:00
772258b0b8 Fix #4793 2019-04-25 04:32:01 +09:00
3ef002e14d Fix bug 2019-04-25 04:27:34 +09:00
b90d473ae5 Fix layout 2019-04-25 04:26:58 +09:00
f5091d524b Refactorgin 2019-04-25 04:17:03 +09:00
ee5720df2c Fix #4704 (#4797)
*  Fix #4632

* Fix #4795
2019-04-25 04:07:39 +09:00
afe2037985 Merge branch 'develop' 2019-04-24 20:51:34 +09:00
878cd18144 11.3.1 2019-04-24 20:51:14 +09:00
e8c8f67a09 Update post-form.vue
Close #4792

Co-Authored-By: tamaina <tamaina@hotmail.co.jp>
2019-04-24 20:47:47 +09:00
7b29e7cf7e Fix bug 2019-04-24 20:41:32 +09:00
723d3e6871 Merge branch 'develop' 2019-04-24 15:23:48 +09:00
f063dee12a 11.3.0 2019-04-24 15:23:31 +09:00
db4b315d3d Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-04-24 15:21:58 +09:00
64d00b08a3 Improve announcement
* お知らせにMFMを使えるように
* お知らせに画像を添付できるように
2019-04-24 15:21:39 +09:00
74736a941e Update README.md [AUTOGEN] (#4778) 2019-04-24 14:56:23 +09:00
c40366f20a Update README.md [AUTOGEN] (#4777) 2019-04-24 14:55:49 +09:00
d73e52ac2a Create find-by-hash.ts 2019-04-24 14:54:19 +09:00
9fac8a611f Fix response 2019-04-24 14:54:15 +09:00
9c774a50f8 Update attached-notes.ts 2019-04-24 14:54:06 +09:00
4f20ee1909 Rename theme 2019-04-24 14:44:28 +09:00
1440c5bc65 Fix bug 2019-04-24 14:39:59 +09:00
5829b2a7c1 Update CHANGELOG.md 2019-04-24 10:07:27 +09:00
e3c4bc18d0 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-04-24 10:02:49 +09:00
8555ec2f72 Fix #4767 2019-04-24 10:02:31 +09:00
8d7cefbbbe New Crowdin translations (#4771)
* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)
2019-04-24 09:54:43 +09:00
5aa5896b22 Cache meta 2019-04-24 08:11:19 +09:00
8a55bdd89d Increase reaction column length 2019-04-24 03:39:11 +09:00
6444ef6444 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-04-24 03:26:10 +09:00
701ab6cc68 Refactor 2019-04-24 03:26:02 +09:00
3c6ce7a943 Update README.md [AUTOGEN] (#4773) 2019-04-24 00:34:48 +09:00
1e54afd217 Update README.md [AUTOGEN] (#4772) 2019-04-24 00:33:55 +09:00
4e90fd3a06 メンテナ名を必須でなくす
Close #4753
2019-04-23 22:48:53 +09:00
0463c6bb0f Refactor API (#4770)
* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update description.ts

* wip
2019-04-23 22:35:26 +09:00
f31f986d66 Fix #4768 2019-04-23 02:50:59 +09:00
c203c8302b Add @rinsuki into collaborators (#4762) 2019-04-22 18:19:20 +09:00
dea4a7b389 Merge branch 'develop' 2019-04-22 00:50:14 +09:00
0caf9b0706 11.2.2 2019-04-22 00:49:36 +09:00
5f655c9fd7 Update dependencies 🚀 2019-04-22 00:43:15 +09:00
44aed53998 New Crowdin translations (#4736)
* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (French)

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

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

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

* New translations ja-JP.yml (French)

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

* 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 (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (French)

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

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Spanish)

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

* New translations ja-JP.yml (English)

* 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 (Czech)
2019-04-22 00:39:00 +09:00
f9ff621d4a 2段階認証を有効にするとログインできない問題を修正 2019-04-22 00:38:37 +09:00
23a5a75778 Docker: Remove quotes in docker_example.env (#4765)
Docker does not parse quotes in env_file.
As follows if enclosed in quotation marks.

docker_example.env:
POSTGRES_PASSWORD="password"

Environment value in Misskey container:
$ echo $POSTGRES_PASSWORD
"password"

Some users may see this behavior as unexpected.

Dockerはenv_fileのクォーテーションマークを解析しません。
もし値がクォーテーションマークで囲まれていた場合は以下のような動作になります。

docker_example.env:
POSTGRES_PASSWORD="password"

Misskeyのコンテナ内の環境変数:
$ echo $POSTGRES_PASSWORD
"password"

この動作を予期しない動作のように思うユーザーがいるかもしれません。
2019-04-21 23:58:15 +09:00
a78475844a Fix: リモートユーザーの修復処理が自動的に実行されない など for v11 (#4764)
* Fix #4761

* Fix: updatePersonができない
2019-04-21 23:57:44 +09:00
575cf2935e Merge branch 'develop' 2019-04-21 06:52:28 +09:00
ae43aa1bc0 11.2.1 2019-04-21 06:52:01 +09:00
0ba71c6a47 Fix #4752 (#4759) 2019-04-21 02:30:18 +09:00
643f7d1803 Update CHANGELOG.md 2019-04-20 05:19:28 +09:00
5d06da4db2 Update CHANGELOG.md 2019-04-20 05:13:38 +09:00
2934b09361 Fix bug 2019-04-19 21:37:36 +09:00
97341d3fa0 Update README.md [AUTOGEN] (#4748) 2019-04-19 15:37:47 +09:00
a95546ede0 Fix bug 2019-04-19 01:00:17 +09:00
7b44727b23 Merge branch 'develop' 2019-04-18 22:01:45 +09:00
debe648a98 11.2.0 2019-04-18 22:00:53 +09:00
fda8cf77ed Improve warp 2019-04-18 22:00:11 +09:00
8aaab195c6 Fix bug 2019-04-18 21:40:36 +09:00
4096ddcbd0 Use - 2019-04-18 21:36:44 +09:00
ae6cc11ad2 Fix icon 2019-04-18 21:34:56 +09:00
dab7e527de Improve user lists index (#4605)
* wip

* Revert "wip"

This reverts commit 6212831ce3bdae5ce17f8ace9945710ba7696185.

* improve list index

* Update user-lists.vue
2019-04-18 21:33:24 +09:00
8b92feac71 Resolve #4732 2019-04-18 21:29:19 +09:00
4beb3e5755 Fix #4734 (#4745) 2019-04-18 20:46:59 +09:00
55f63229cd Update timemachine.vue 2019-04-18 19:42:40 +09:00
7827aeb695 Resolve #4735 2019-04-18 19:40:23 +09:00
cce768aaac Fix API definition 2019-04-18 14:58:43 +09:00
80b83c0624 間違えた 2019-04-18 14:41:51 +09:00
73b683bb4d Add test 2019-04-18 14:39:49 +09:00
d78a5c0863 Fix #4703 2019-04-18 14:34:47 +09:00
683e5b6abe Add type annotations 2019-04-18 14:29:17 +09:00
653b8f6352 スプラッシュがクリックに反応するように (#4561)
* confirm silence

* Resolve #4554

* Revert "confirm silence"

This reverts commit e1dbdc2bfc0f41c2b308b142c70e9e4573c98cf9.
2019-04-18 03:37:49 +09:00
9ec6afa375 confirm on user menu (#4553) 2019-04-18 03:36:06 +09:00
adff5382ca confirm silence (#4560) 2019-04-18 03:33:51 +09:00
704aabd703 Use menu instead of prompt Fix #4540, Fix #342 (#4575)
* Use menu instead prompt

* fix

* https://bit.ly/2U0JuVt

* fix
2019-04-18 03:32:45 +09:00
f7b1ef0690 アンケートウィジットでもMFMを使用するように v11 (#4741)
* MFM in poll

* use mfm
2019-04-18 03:14:04 +09:00
929982117f Merge branch 'develop' 2019-04-18 01:12:21 +09:00
56a530d769 11.1.6 2019-04-18 01:12:03 +09:00
7ef75fb06b New Crowdin translations (#4692)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (French)

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

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Spanish)

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

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (French)

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

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

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

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

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

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

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

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Czech)
2019-04-18 01:11:06 +09:00
112a72abdf Docker: Back to npm from yarn (#4730)
This commit reverts "Fix Dockerfile #4214" which uses yarn instead of npm.
The cause of the build error is that binding.gyp and src/crypto_key.cc
are missing when installing dependencies.
In other words, yarn did not fix build error.

There is no reason to use yarn, so go back to npm.
2019-04-18 01:09:31 +09:00
71813e03ee Fix bug 2019-04-18 01:05:40 +09:00
9b05b6ef28 Improve readability 2019-04-18 00:57:06 +09:00
54a87b25b3 Remove unused imports 2019-04-18 00:56:10 +09:00
55e97864bd Fix: v11で未認知ユーザーからActivityが飛んできた場合に処理できない (#4733)
* Fix: inboxに未知のユーザーが来ると処理できない

* こうかな
2019-04-18 00:53:00 +09:00
95733c9490 [MFM] Better hashtag parsing 2019-04-18 00:40:56 +09:00
e19ae644f1 Fix indent 2019-04-18 00:13:31 +09:00
ac4ea25267 Better error handling 2019-04-18 00:09:08 +09:00
611e4f34dc Merge branch 'develop' 2019-04-17 19:37:32 +09:00
faf017f333 11.1.5 2019-04-17 19:36:58 +09:00
5eec896615 Better avgColor 2019-04-17 17:13:49 +09:00
17f35174ea 🎨 2019-04-17 17:01:57 +09:00
bf71b31123 Update CONTRIBUTING.md 2019-04-17 16:59:39 +09:00
9399a44c82 Fix error 2019-04-17 16:50:50 +09:00
c96418806f Disable sql log 2019-04-17 16:45:31 +09:00
7945eddef6 Clean up 2019-04-17 14:39:45 +09:00
0ede390fef Refactor 2019-04-17 14:32:59 +09:00
85959a3b9b Fix #4721 Fix #4722 2019-04-17 14:30:31 +09:00
946c3a25b9 Clean up 2019-04-17 07:25:34 +09:00
f9d697128a Update schemas.ts 2019-04-17 04:32:04 +09:00
227 changed files with 3140 additions and 1660 deletions

View File

@ -56,22 +56,10 @@ jobs:
executor: executor:
type: string type: string
default: "default" default: "default"
without_redis:
type: boolean
default: false
executor: <<parameters.executor>> executor: <<parameters.executor>>
steps: steps:
- attach_workspace: - attach_workspace:
at: /tmp/workspace at: /tmp/workspace
- when:
condition: <<parameters.without_redis>>
steps:
- run:
name: Configure
command: |
mv .config/test.yml .config/test_redis.yml
touch .config/test.yml
cat .config/test_redis.yml | while IFS= read line; do if [[ "$line" = '# __REDIS__' ]]; then break; else echo "$line" >> .config/test.yml; fi; done
- run: - run:
name: Test name: Test
command: | command: |
@ -134,32 +122,14 @@ workflows:
branches: branches:
only: master only: master
- test: - test:
name: manual-test-with-redis name: manual-test
executor: with-redis
requires: requires:
- manual-build - manual-build
filters: filters:
branches: branches:
ignore: master ignore: master
- test: - test:
name: auto-test-without-redis name: auto-test
executor: with-redis
requires:
- auto-build
filters:
branches:
only: master
- test:
name: manual-test-with-redis
without_redis: true
requires:
- manual-build
filters:
branches:
ignore: master
- test:
name: auto-test-without-redis
without_redis: true
requires: requires:
- auto-build - auto-build
filters: filters:

View File

@ -1,5 +1,4 @@
# db settings # db settings
POSTGRES_PASSWORD="example-misskey-pass" POSTGRES_PASSWORD=example-misskey-pass
POSTGRES_USER="example-misskey-user" POSTGRES_USER=example-misskey-user
POSTGRES_DB="misskey" POSTGRES_DB=misskey

4
.github/CODEOWNERS vendored
View File

@ -1,7 +1,7 @@
# PATH OWNERS # PATH OWNERS
/.autogen/ @acid-chicken /.autogen/ @acid-chicken
/.circleci/ @syuilo @acid-chicken /.circleci/ @syuilo @acid-chicken
/.config/ @syuilo @AyaMorisawa @mei23 @acid-chicken /.config/ @syuilo @AyaMorisawa @mei23 @acid-chicken @rinsuki
# /.config/mongo_initdb_example.js @khws4v1 # /.config/mongo_initdb_example.js @khws4v1
/.github/ @syuilo @AyaMorisawa @acid-chicken /.github/ @syuilo @AyaMorisawa @acid-chicken
/.vscode/ @acid-chicken /.vscode/ @acid-chicken
@ -12,7 +12,7 @@
# /docs/*.fr.md @BoFFire # /docs/*.fr.md @BoFFire
# /docs/docker.*.md @khws4v1 # /docs/docker.*.md @khws4v1
/locales/ @syuilo /locales/ @syuilo
/src/ @syuilo @AyaMorisawa @mei23 @acid-chicken /src/ @syuilo @AyaMorisawa @mei23 @acid-chicken @rinsuki
# /src/crypto_key.cc @akihikodaki # /src/crypto_key.cc @akihikodaki
# /src/crypto_key.d.ts @akihikodaki # /src/crypto_key.d.ts @akihikodaki
/.dockerignore @syuilo # @khws4v1 /.dockerignore @syuilo # @khws4v1

View File

@ -5,6 +5,109 @@ If you encounter any problems with updating, please try the following:
1. `npm run clean` or `npm run cleanall` 1. `npm run clean` or `npm run cleanall`
2. Retry update (Don't forget `npm i`) 2. Retry update (Don't forget `npm i`)
How to migrate to v11 from v10
------------------------------
1. v11をインストールしたい場所に syuilo/misskey をクローン
2. config を設定する
* PostgreSQL(`db`)の設定とは別に、v10からMongoDBの設定をコピペしてくる(例は下にあります)
* `id`の設定を`meid`または`objectid`にする
``` yml
db:
host: localhost
port: 5432
db: misskey
user: x
pass: x
mongodb:
user: x
pass: x
host: localhost
port: 27017
db: misskey
```
3. migration ブランチに切り替え
4. `npm i`
5. `npm run build`
6. `npm run init`
7. `npm run migrate`
8. master ブランチに戻す
9. enjoy
11.4.0 (2019/04/25)
-------------------
### Improvements
* 検索でローカルの投稿のみに絞れるように
* 検索で特定のインスタンスの投稿のみに絞れるように
* 検索で特定のユーザーの投稿のみに絞れるように
### Fixes
* 投稿が増殖する問題を修正
* ストリームで過去の投稿が流れてくる問題を修正
* モバイル版のユーザーページで遷移してもユーザー名が変わらない問題を修正
* お知らせを切り替えても内容が変わらない問題を修正
11.3.1 (2019/04/24)
-------------------
### Fixes
* Webからファイルがアップロードできない問題を修正
11.3.0 (2019/04/24)
-------------------
### Improvements
* お知らせにMFMを使えるように
* お知らせに画像を添付できるように
### Fixes
* 投稿のタグ検索APIで大文字小文字が区別されていたのを修正
* 公開範囲がホームの投稿がグローバルTLに流れる問題を修正
* モバイルビューの投稿詳細にて acct が長いとアイコンが圧迫面接される問題を修正
11.2.2 (2019/04/22)
-------------------
### Fixes
* 2段階認証を有効にするとログインできない問題を修正
* リモートユーザーの修復処理が自動的に実行されない問題を修正
* リモートユーザー情報が更新されない問題を修正
11.2.1 (2019/04/21)
-------------------
### Fixes
* MEIDが25桁になっているのを修正
* リモートユーザー情報が更新されない問題を修正
11.2.0 (2019/04/18)
-------------------
### Improvements
* 検索で日付(日時)を入力するとタイムラインをその時点まで遡るように
* APIコンソールでエンドポイントをサジェストするように
* モバイル版でドライブのメニューを使いやすく
* サイレンス時に確認を表示するように
* ユーザーメニューでブロックなどの操作を行う時に確認するように
### Fixes
* アプリケーション連携画面でパーミッションが表示されない問題を修正
* アンケートウィジットでもMFMを使用するように
* フォローしてないユーザーのホーム投稿がSTLに流れてくる問題を修正
* モバイル版でウィジェットを設定できない問題を修正
* スプラッシュがクリックに反応するように
11.1.6 (2019/04/18)
-------------------
### Fixes
* 未認知ユーザーからActivityが飛んできた場合に処理できない問題を修正
* その投稿を見たのにも関わらずメンションインジケーターが点灯し続ける問題を修正
* ハッシュタグの判定を改善
* サーバーのエラーハンドリングを改善
11.1.5 (2019/04/17)
-------------------
### Fixes
* ユーザー名に含まれているカスタム絵文字が表示されないことがある問題を修正
* 壁紙の設定ができない問題を修正
* デザインの調整
11.1.4 (2019/04/17) 11.1.4 (2019/04/17)
------------------- -------------------
### Fixes ### Fixes

View File

@ -46,10 +46,40 @@ Convert な(na) to にゃ(nya)
Revert Nyaize Revert Nyaize
## Code style ## Code style
### Use semicolon ### セミコロンを省略しない
To avoid ASI Hazard ASI Hazardを避けるためでもある
### 中括弧を省略しない
Bad:
``` ts
if (foo)
bar;
else
baz;
```
Good:
``` ts
if (foo) {
bar;
} else {
baz;
}
```
ただし**`if`が一行**の時だけは省略しても良い
Good:
``` ts
if (foo) bar;
```
### `export default`を使わない
インテリセンスと相性が悪かったりするため
参考:
* https://gfx.hatenablog.com/entry/2017/11/24/135343
* https://basarat.gitbooks.io/typescript/docs/tips/defaultIsBad.html
### Don't use `export default`
Bad: Bad:
``` ts ``` ts
export default function(foo: string): string { export default function(foo: string): string {

View File

@ -21,12 +21,11 @@ RUN apk add --no-cache \
pkgconfig \ pkgconfig \
python \ python \
zlib-dev zlib-dev
RUN npm i -g yarn
COPY package.json ./ COPY package.json ./
RUN yarn install RUN npm i
COPY . ./ COPY . ./
RUN yarn build RUN npm run build
FROM base AS runner FROM base AS runner

View File

@ -88,12 +88,14 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://avatars0.githubusercontent.com/u/10798641?s=460&v=4" alt="AyaMorisawa" width="100"></td> <td><img src="https://avatars0.githubusercontent.com/u/10798641?s=460&v=4" alt="AyaMorisawa" width="100"></td>
<td><img src="https://avatars1.githubusercontent.com/u/30769358?s=460&v=4" alt="mei23" width="100"></td> <td><img src="https://avatars1.githubusercontent.com/u/30769358?s=460&v=4" alt="mei23" width="100"></td>
<td><img src="https://avatars2.githubusercontent.com/u/20679825?s=460&v=4" alt="acid-chicken" width="100"></td> <td><img src="https://avatars2.githubusercontent.com/u/20679825?s=460&v=4" alt="acid-chicken" width="100"></td>
<td><img src="https://avatars2.githubusercontent.com/u/6533808?s=460&v=4" alt="rinsuki" width="100"></td>
</tr> </tr>
<tr> <tr>
<td align="center"><a href="https://github.com/syuilo">@syuilo</a></td> <td align="center"><a href="https://github.com/syuilo">@syuilo</a></td>
<td align="center"><a href="https://github.com/AyaMorisawa">@AyaMorisawa</a></td> <td align="center"><a href="https://github.com/AyaMorisawa">@AyaMorisawa</a></td>
<td align="center"><a href="https://github.com/mei23">@mei23</a></td> <td align="center"><a href="https://github.com/mei23">@mei23</a></td>
<td align="center"><a href="https://github.com/acid-chicken">@acid-chicken</a></td> <td align="center"><a href="https://github.com/acid-chicken">@acid-chicken</a></td>
<td align="center"><a href="https://github.com/rinsuki">@rinsuki</a></td>
</tr> </tr>
</table> </table>
@ -102,14 +104,14 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<!-- PATREON_START --> <!-- PATREON_START -->
<table><tr> <table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5888816/36da0f7c15954df0ab13f9abdf227f66/1.jpeg?token-time=2145916800&token-hash=at8QpJXJ8C0zINY_NmoMKv-MhXVoUK-YzTgaJPJzJYU%3D" alt="Hiroshi Seki" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5888816/36da0f7c15954df0ab13f9abdf227f66/1.jpeg?token-time=2145916800&token-hash=at8QpJXJ8C0zINY_NmoMKv-MhXVoUK-YzTgaJPJzJYU%3D" alt="Hiroshi Seki" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weep" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1.jpe?token-time=2145916800&token-hash=bqwLTk0Wo0hUJJ8J5y7ii05bLzz-_CDA7Bo0Mp4RFU0%3D" alt="ne_moni" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1.jpe?token-time=2145916800&token-hash=bqwLTk0Wo0hUJJ8J5y7ii05bLzz-_CDA7Bo0Mp4RFU0%3D" alt="ne_moni" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/4.jpe?token-time=2145916800&token-hash=zEyJqVM7u9d8Ri-65fJYSJcWF1jBH1nJ5a3taRzrTmw%3D" alt="Melilot" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/4.jpe?token-time=2145916800&token-hash=zEyJqVM7u9d8Ri-65fJYSJcWF1jBH1nJ5a3taRzrTmw%3D" alt="Melilot" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td> <td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td>
</tr><tr> </tr><tr>
<td><a href="https://www.patreon.com/rane_hs">Hiroshi Seki</a></td> <td><a href="https://www.patreon.com/rane_hs">Hiroshi Seki</a></td>
<td><a href="https://www.patreon.com/weepjp">weep</a></td> <td><a href="https://www.patreon.com/weepjp">weepjp</a></td>
<td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td> <td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td>
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td> <td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
<td><a href="https://www.patreon.com/osapon">osapon</a></td> <td><a href="https://www.patreon.com/osapon">osapon</a></td>
@ -124,8 +126,8 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin" width="100"></td> <td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/17463605" alt="Sampot" width="100"></td> <td><img src="https://c8.patreon.com/2/200/17463605" alt="Sampot" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpe?token-time=2145916800&token-hash=CPxGQhKIlEaa6WUcgbyHixyKEhakiw9RFdOhsIJBQ_o%3D" alt="takimura" width="100"></td>
</tr><tr> </tr><tr>
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td> <td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
<td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td> <td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td>
@ -135,10 +137,11 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td> <td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td>
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td> <td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
<td><a href="https://www.patreon.com/user?u=17463605">Sampot</a></td> <td><a href="https://www.patreon.com/user?u=17463605">Sampot</a></td>
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td>
<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td> <td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td>
<td><a href="https://www.patreon.com/takimura">takimura</a></td>
</tr></table> </tr></table>
<table><tr> <table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpe?token-time=2145916800&token-hash=CPxGQhKIlEaa6WUcgbyHixyKEhakiw9RFdOhsIJBQ_o%3D" alt="takimura" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17195955/be45e5e14c3e48b2bee0456c84e19df4/4.jpe?token-time=2145916800&token-hash=UslrPVM-8TXOe8AapuNiaFYjcIJgPNcU-fKpGbfGJNI%3D" alt="Damillora" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17195955/be45e5e14c3e48b2bee0456c84e19df4/4.jpe?token-time=2145916800&token-hash=UslrPVM-8TXOe8AapuNiaFYjcIJgPNcU-fKpGbfGJNI%3D" alt="Damillora" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/83884b38afc74d4cbe83c30a13b10edd/1.png?token-time=2145916800&token-hash=R5Tog8RWg0rguRoCIoir3lThokrdPvs8Utfikhc0nhY%3D" alt="Atsuko Tominaga" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/83884b38afc74d4cbe83c30a13b10edd/1.png?token-time=2145916800&token-hash=R5Tog8RWg0rguRoCIoir3lThokrdPvs8Utfikhc0nhY%3D" alt="Atsuko Tominaga" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td>
@ -147,8 +150,8 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1.jpeg?token-time=2145916800&token-hash=L55UhJ0rcuNAH3w_ryeeGN4hC6taoOixyAhraEi0bzw%3D" alt="dansup" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1.jpeg?token-time=2145916800&token-hash=L55UhJ0rcuNAH3w_ryeeGN4hC6taoOixyAhraEi0bzw%3D" alt="dansup" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1.jpeg?token-time=2145916800&token-hash=d8jBQLMOHD87KtXs5C9fk1o58DMF73pQ-dYH3uZJPBE%3D" alt="Gargron" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1.jpeg?token-time=2145916800&token-hash=d8jBQLMOHD87KtXs5C9fk1o58DMF73pQ-dYH3uZJPBE%3D" alt="Gargron" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
</tr><tr> </tr><tr>
<td><a href="https://www.patreon.com/takimura">takimura</a></td>
<td><a href="https://www.patreon.com/damillora">Damillora</a></td> <td><a href="https://www.patreon.com/damillora">Damillora</a></td>
<td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td> <td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td>
<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td> <td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
@ -157,13 +160,14 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><a href="https://www.patreon.com/dansup">dansup</a></td> <td><a href="https://www.patreon.com/dansup">dansup</a></td>
<td><a href="https://www.patreon.com/mastodon">Gargron</a></td> <td><a href="https://www.patreon.com/mastodon">Gargron</a></td>
<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td> <td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
</tr></table> </tr></table>
<table><tr> <table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
</tr><tr> </tr><tr>
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
</tr></table> </tr></table>
**Last updated:** Mon, 15 Apr 2019 12:07:08 UTC **Last updated:** Wed, 24 Apr 2019 05:56:07 UTC
<!-- PATREON_END --> <!-- PATREON_END -->
:four_leaf_clover: Copyright :four_leaf_clover: Copyright

View File

@ -70,8 +70,17 @@ common:
followers: "Sledující" followers: "Sledující"
favorites: "Oblíbené" favorites: "Oblíbené"
permissions: permissions:
'read:drive': "Prohlížet Disk" "read:account": "Zobrazit informace o účtu"
'write:drive': "Pracovat s Diskem" "read:drive": "Prohlížet Disk"
"write:drive": "Pracovat s Diskem"
"read:favorites": "Prohlížet oblíbené"
"read:messaging": "Prohlížet konverzaci"
"write:messaging": "Pracovat s konverzaci"
"read:mutes": "Prohlížet ztlumené"
"read:notifications": "Prohlížet oznámení"
"write:notifications": "Pracovat s oznámeními"
"read:reactions": "Prohlížet reakce"
"write:votes": "Hlasovat"
empty-timeline-info: empty-timeline-info:
follow-users-to-make-your-timeline: "Poznámky sledujících se zobrazí ve vaší časové ose" follow-users-to-make-your-timeline: "Poznámky sledujících se zobrazí ve vaší časové ose"
explore: "Najít uživatele" explore: "Najít uživatele"
@ -279,6 +288,7 @@ common:
nav: "Navigace" nav: "Navigace"
tips: "Tipy" tips: "Tipy"
hashtags: "Hashtagy" hashtags: "Hashtagy"
queue: "Ve frontě"
dev: "Nepodařilo se vytvořit aplikace. Prosím zkuste to znovu." dev: "Nepodařilo se vytvořit aplikace. Prosím zkuste to znovu."
ai-chan-kawaii: "Ai-chan kawaii!" ai-chan-kawaii: "Ai-chan kawaii!"
you: "Vy" you: "Vy"
@ -587,6 +597,8 @@ common/views/components/user-list-editor.vue:
remove-user: "Odebrat z tohoto seznamu" remove-user: "Odebrat z tohoto seznamu"
delete-are-you-sure: "Smazat seznam \"$1\"?" delete-are-you-sure: "Smazat seznam \"$1\"?"
deleted: "Smazáno" deleted: "Smazáno"
common/views/components/user-lists.vue:
list-name: "Název seznamu"
common/views/widgets/broadcast.vue: common/views/widgets/broadcast.vue:
fetching: "Načítám" fetching: "Načítám"
next: "Další" next: "Další"
@ -826,8 +838,6 @@ desktop/views/components/received-follow-requests-window.vue:
reject: "Odmítnout" reject: "Odmítnout"
desktop/views/components/user-lists-window.vue: desktop/views/components/user-lists-window.vue:
title: "Seznamy uživatelů" title: "Seznamy uživatelů"
create-list: "Vytvořit seznam"
list-name: "Název seznamu"
desktop/views/components/user-preview.vue: desktop/views/components/user-preview.vue:
notes: "Příspěvky" notes: "Příspěvky"
desktop/views/components/users-list.vue: desktop/views/components/users-list.vue:
@ -1072,7 +1082,6 @@ mobile/views/components/drive.vue:
used: "využito" used: "využito"
file-count: "Soubor(ů)" file-count: "Soubor(ů)"
folder-is-empty: "Tato složka je prázdná" folder-is-empty: "Tato složka je prázdná"
deletion-alert: "Omlouváme se, ale mazání složek ještě nebylo implementováno."
folder-name: "Název složky" folder-name: "Název složky"
url-prompt: "URL adresa souboru, který chcete nahrát" url-prompt: "URL adresa souboru, který chcete nahrát"
uploading: "Byl zahájen upload. Může chvilku trvat než bude dokončen." uploading: "Byl zahájen upload. Může chvilku trvat než bude dokončen."
@ -1114,7 +1123,7 @@ mobile/views/components/post-form.vue:
reply: "Odpovědět" reply: "Odpovědět"
renote: "Renotovat" renote: "Renotovat"
reply-placeholder: "Odpovědět na tento příspěvěk" reply-placeholder: "Odpovědět na tento příspěvěk"
location-alert: "Vaše zařízení nepodporuje lokační službu" geolocation-alert: "Vaše zařízení nepodporuje lokační službu"
error: "Chyba" error: "Chyba"
username-prompt: "Zadejte uživatelské jméno" username-prompt: "Zadejte uživatelské jméno"
mobile/views/components/sub-note-content.vue: mobile/views/components/sub-note-content.vue:
@ -1133,6 +1142,10 @@ mobile/views/components/ui.nav.vue:
game: "Hry" game: "Hry"
admin: "Administrace" admin: "Administrace"
about: "O Misskey" about: "O Misskey"
mobile/views/pages/drive.vue:
contextmenu:
upload: "Nahrát soubor"
create-folder: "Vytvořit složku"
mobile/views/pages/user-lists.vue: mobile/views/pages/user-lists.vue:
title: "Seznamy" title: "Seznamy"
mobile/views/pages/signup.vue: mobile/views/pages/signup.vue:
@ -1185,4 +1198,3 @@ deck/deck.user-column.vue:
activity: "Aktivita" activity: "Aktivita"
dev/views/new-app.vue: dev/views/new-app.vue:
app-name-desc: "Jméno vaší aplikace" app-name-desc: "Jméno vaší aplikace"
app-desc: "Stručný popis nebo představení vaší aplikace."

View File

@ -62,10 +62,14 @@ common:
followers: "Folgende" followers: "Folgende"
favorites: "Diesen Beitrag favorisieren" favorites: "Diesen Beitrag favorisieren"
permissions: permissions:
'read:account': "Accountinformationen anzeigen." "read:account": "Accountinformationen anzeigen."
'write:account': "Accountinformationen bearbeiten." "write:account": "Accountinformationen bearbeiten."
'read:drive': "Dateien anzeigen" "read:drive": "Dateien anzeigen"
'write:drive': "Dateien bearbeiten" "write:drive": "Dateien bearbeiten"
"read:favorites": "Favoriten anzeigen"
"read:messaging": "Unterhaltung anzeigen"
"write:messaging": "Unterhaltung bearbeiten"
"write:votes": "Abstimmen"
empty-timeline-info: empty-timeline-info:
follow-users-to-make-your-timeline: "Beiträge von Benutzern, denen du folgst, werden in der Zeitleiste angezeigt." follow-users-to-make-your-timeline: "Beiträge von Benutzern, denen du folgst, werden in der Zeitleiste angezeigt."
explore: "Benutzer finden" explore: "Benutzer finden"
@ -672,6 +676,10 @@ mobile/views/components/ui.nav.vue:
user-lists: "Listen" user-lists: "Listen"
game: "Spielen" game: "Spielen"
about: "Über" about: "Über"
mobile/views/pages/drive.vue:
contextmenu:
upload: "Eine Datei hochladen"
create-folder: "Ein Verzeichnis erstellen"
mobile/views/pages/user-lists.vue: mobile/views/pages/user-lists.vue:
title: "Listen" title: "Listen"
mobile/views/pages/home.vue: mobile/views/pages/home.vue:
@ -739,10 +747,7 @@ dev/views/new-app.vue:
create-app: "Erstelle Anwendung" create-app: "Erstelle Anwendung"
app-name: "Name der Anwendung" app-name: "Name der Anwendung"
app-name-desc: "Der Name der Anwendung" app-name-desc: "Der Name der Anwendung"
app-name-ex: "z.B. Misskey für iOS"
app-overview: "Beschreibung der Anwendung" app-overview: "Beschreibung der Anwendung"
app-desc: "Eine kurze Beschreibung oder Einführung der Anwendung."
app-desc-ex: "z.B. Ein iOS-Client für Misskey."
callback-url: "Callback-URL (optional)" callback-url: "Callback-URL (optional)"
callback-url-desc: "Die URL, auf die nach erfolgreicher Authentifizierung umgeleitet werden soll." callback-url-desc: "Die URL, auf die nach erfolgreicher Authentifizierung umgeleitet werden soll."
authority: "Berechtigungen" authority: "Berechtigungen"

View File

@ -70,10 +70,26 @@ common:
followers: "Followers" followers: "Followers"
favorites: "Favorites" favorites: "Favorites"
permissions: permissions:
'read:account': "View account information" "read:account": "View account information"
'write:account': "Update your account information" "write:account": "Update your account information"
'read:drive': "Browse the Drive" "read:blocks": "View Blocks"
'write:drive': "Work with the Drive" "write:blocks": "Work with Blocks"
"read:drive": "Browse the Drive"
"write:drive": "Work with the Drive"
"read:favorites": "View Favorites"
"write:favorites": "Work with Favorites"
"read:following": "View Follower info"
"write:following": "Work with Follow info"
"read:messaging": "View Messaging"
"write:messaging": "Work with Messaging"
"read:mutes": "View Muted"
"write:mutes": "Work with Muted"
"write:notes": "Create and delete posts"
"read:notifications": "View notifications"
"write:notifications": "Work with notifications"
"read:reactions": "View reactions"
"write:reactions": "Work with reactions"
"write:votes": "Vote"
empty-timeline-info: empty-timeline-info:
follow-users-to-make-your-timeline: "Following users will show their posts in your timeline." follow-users-to-make-your-timeline: "Following users will show their posts in your timeline."
explore: "Find users" explore: "Find users"
@ -281,6 +297,7 @@ common:
nav: "Navigation" nav: "Navigation"
tips: "Tips" tips: "Tips"
hashtags: "Hashtags" hashtags: "Hashtags"
queue: "Queue"
dev: "Failed to create the application. Please try again." dev: "Failed to create the application. Please try again."
ai-chan-kawaii: "Ai-chan kawaii!" ai-chan-kawaii: "Ai-chan kawaii!"
you: "You" you: "You"
@ -472,8 +489,12 @@ common/views/components/user-menu.vue:
mention: "Mention" mention: "Mention"
mute: "Mute" mute: "Mute"
unmute: "Unmute" unmute: "Unmute"
mute-confirm: "Are you sure you want to mute this user?"
unmute-confirm: "Are you certain that you want to unmute this user?"
block: "Block" block: "Block"
unblock: "Unblock" unblock: "Unblock"
block-confirm: "Are you sure you want to block this user?"
unblock-confirm: "Are you certain that you want to unblock this user?"
push-to-list: "Add to list" push-to-list: "Add to list"
select-list: "Select a list" select-list: "Select a list"
report-abuse: "Report abuse" report-abuse: "Report abuse"
@ -481,8 +502,12 @@ common/views/components/user-menu.vue:
report-abuse-reported: "The issue has been reported to the administrator. Your cooperation is much appreciated." report-abuse-reported: "The issue has been reported to the administrator. Your cooperation is much appreciated."
silence: "Silence" silence: "Silence"
unsilence: "Unsilence" unsilence: "Unsilence"
silence-confirm: "Are you sure that you want to silence this user?"
unsilence-confirm: "Are you sure that you want to stop silencing this user?"
suspend: "Suspend" suspend: "Suspend"
unsuspend: "Unsuspend" unsuspend: "Unsuspend"
suspend-confirm: "Are you sure that you want to suspend this user?"
unsuspend-confirm: "Are you sure that you want to unsuspend this user?"
common/views/components/poll.vue: common/views/components/poll.vue:
vote-to: "Vote for '{}'" vote-to: "Vote for '{}'"
vote-count: "{} votes" vote-count: "{} votes"
@ -665,6 +690,9 @@ common/views/components/user-list-editor.vue:
remove-user: "Remove from this list" remove-user: "Remove from this list"
delete-are-you-sure: "Delete list \"$1\"?" delete-are-you-sure: "Delete list \"$1\"?"
deleted: "Deleted successfully" deleted: "Deleted successfully"
common/views/components/user-lists.vue:
create-list: "Create a list"
list-name: "List name"
common/views/widgets/broadcast.vue: common/views/widgets/broadcast.vue:
fetching: "Checking" fetching: "Checking"
no-broadcasts: "No announcements" no-broadcasts: "No announcements"
@ -1013,8 +1041,6 @@ desktop/views/components/received-follow-requests-window.vue:
reject: "Reject" reject: "Reject"
desktop/views/components/user-lists-window.vue: desktop/views/components/user-lists-window.vue:
title: "User lists" title: "User lists"
create-list: "Create a list"
list-name: "List name"
desktop/views/components/user-preview.vue: desktop/views/components/user-preview.vue:
notes: "Posts" notes: "Posts"
following: "Following" following: "Following"
@ -1192,7 +1218,9 @@ admin/views/users.vue:
unsuspend-confirm: "Are you sure you want to unsuspend this account?" unsuspend-confirm: "Are you sure you want to unsuspend this account?"
unsuspended: "The user has successfully unsuspended." unsuspended: "The user has successfully unsuspended."
make-silence: "Silence" make-silence: "Silence"
silence-confirm: "Silence user?"
unmake-silence: "Unsilence" unmake-silence: "Unsilence"
unsilence-confirm: "Are you certain that you want to stop silencing this user?"
verify: "Verify account" verify: "Verify account"
verify-confirm: "Do you want this to be a verified account?" verify-confirm: "Do you want this to be a verified account?"
verified: "The account is now being verified" verified: "The account is now being verified"
@ -1386,6 +1414,7 @@ desktop/views/widgets/polls.vue:
desktop/views/widgets/post-form.vue: desktop/views/widgets/post-form.vue:
title: "Post" title: "Post"
note: "Post" note: "Post"
something-happened: "Could not be posted in this circumstance."
desktop/views/widgets/profile.vue: desktop/views/widgets/profile.vue:
update-banner: "Click to edit your banner" update-banner: "Click to edit your banner"
update-avatar: "Click to edit your avatar" update-avatar: "Click to edit your avatar"
@ -1404,12 +1433,11 @@ mobile/views/components/drive.vue:
file-count: "File(s)" file-count: "File(s)"
nothing-in-drive: "There's nothing stored." nothing-in-drive: "There's nothing stored."
folder-is-empty: "This folder is empty" folder-is-empty: "This folder is empty"
prompt: "What do you want to do? (Please enter a number): <1 → Upload a file | 2 → Upload a file from a URL | 3 → Create a folder | 4 → Change this folder's name | 5 → Move this folder | 6 → Delete this folder>"
deletion-alert: "Sorry! Deleting a folder is not yet implemented."
folder-name: "Folder name" folder-name: "Folder name"
here-is-root: "Currently, you are on the root, not inside of any folder." here-is-root: "Currently, you are on the root, not inside of any folder."
url-prompt: "URL of the file you want to upload" url-prompt: "URL of the file you want to upload"
uploading: "Upload requested. It may take a while for the upload to finish." uploading: "Upload requested. It may take a while for the upload to finish."
folder-name-cannot-empty: "Folder name cannot be blank."
mobile/views/components/drive-file-chooser.vue: mobile/views/components/drive-file-chooser.vue:
select-file: "Choose files" select-file: "Choose files"
mobile/views/components/drive-folder-chooser.vue: mobile/views/components/drive-folder-chooser.vue:
@ -1462,7 +1490,7 @@ mobile/views/components/post-form.vue:
quote-placeholder: "Quote this post... (optional)" quote-placeholder: "Quote this post... (optional)"
reply-placeholder: "Reply to this note..." reply-placeholder: "Reply to this note..."
cw-placeholder: "Comments for the post (optional)" cw-placeholder: "Comments for the post (optional)"
location-alert: "Your device does not provide location services" geolocation-alert: "Your device does not provide location services."
error: "Error" error: "Error"
username-prompt: "Enter user name" username-prompt: "Enter user name"
mobile/views/components/sub-note-content.vue: mobile/views/components/sub-note-content.vue:
@ -1483,9 +1511,16 @@ mobile/views/components/ui.nav.vue:
game: "Games" game: "Games"
admin: "Admin" admin: "Admin"
about: "About Misskey" about: "About Misskey"
mobile/views/pages/drive.vue:
contextmenu:
upload: "Upload a file"
url-upload: "Upload file from a URL"
create-folder: "Create a folder"
rename-folder: "Rename folder"
move-folder: "Move this folder"
delete-folder: "Delete this folder"
mobile/views/pages/user-lists.vue: mobile/views/pages/user-lists.vue:
title: "Lists" title: "Lists"
enter-list-name: "Enter a name of the list to make"
mobile/views/pages/signup.vue: mobile/views/pages/signup.vue:
lets-start: "Your account is now ready! 📦" lets-start: "Your account is now ready! 📦"
mobile/views/pages/followers.vue: mobile/views/pages/followers.vue:
@ -1612,13 +1647,12 @@ dev/views/apps.vue:
create-app: "Create app" create-app: "Create app"
app-missing: "No apps" app-missing: "No apps"
dev/views/new-app.vue: dev/views/new-app.vue:
new-app: "New Application"
create-app: "Creating application" create-app: "Creating application"
app-name: "Application name" app-name: "Application name"
app-name-desc: "The name of your app" app-name-desc: "The name of your app"
app-name-ex: "ex) Misskey for iOS"
app-overview: "Application summary" app-overview: "Application summary"
app-desc: "A brief description or introduction of your app." app-overview-desc: "A brief description, or an introduction of your app."
app-desc-ex: "ex) Misskey iOS client."
callback-url: "The callback URL (optional)" callback-url: "The callback URL (optional)"
callback-url-desc: "The URL to redirect to after the user is authenticated via the authentication form." callback-url-desc: "The URL to redirect to after the user is authenticated via the authentication form."
authority: "Permissions" authority: "Permissions"

View File

@ -20,8 +20,15 @@ common:
application-authorization: "Autorizaciones de la aplicación." application-authorization: "Autorizaciones de la aplicación."
close: "Cerrar" close: "Cerrar"
do-not-copy-paste: "Por favor no copies código aquí. Tu cuenta puede resultar comprometida." do-not-copy-paste: "Por favor no copies código aquí. Tu cuenta puede resultar comprometida."
load-more: "Leer más"
enter-password: "Escribe una contraseña" enter-password: "Escribe una contraseña"
2fa: "Autenticación de dos factores" 2fa: "Autenticación de dos factores"
customize-home: "Personaliza la página principal"
featured-notes: "Destacados"
dark-mode: "Modo oscuro"
signin: "Iniciar sesión"
signup: "¡Regístrate!"
signout: "Cerrar sesión"
got-it: "¡Listo!" got-it: "¡Listo!"
customization-tips: customization-tips:
title: "Consejos de personalización" title: "Consejos de personalización"
@ -50,8 +57,22 @@ common:
drive: "Drive" drive: "Drive"
messaging: "Conversación" messaging: "Conversación"
home: "Inicio" home: "Inicio"
deck: "Deck"
timeline: "Timeline"
explore: "Explorar"
following: "Siguiendo" following: "Siguiendo"
followers: "Seguidores"
favorites: "Me gusta esta nota" favorites: "Me gusta esta nota"
permissions:
"read:account": "Ver información de la cuenta"
"write:account": "Editar información de la cuenta"
"read:blocks": "Ver bloques"
"write:blocks": "Editar bloques"
"read:favorites": "Ver favoritos"
"write:favorites": "Editar favoritos"
"read:messaging": "Ver conversación"
"read:notifications": "Ver notificaciones"
"write:votes": "Vota"
weekday-short: weekday-short:
sunday: "domingo" sunday: "domingo"
monday: "lunes" monday: "lunes"
@ -97,12 +118,24 @@ common:
d: "¿Quieres decir algo?" d: "¿Quieres decir algo?"
e: "¡Escribe aquí!" e: "¡Escribe aquí!"
f: "Esperando a que escribas algo..." f: "Esperando a que escribas algo..."
settings: "Configuración"
_settings: _settings:
profile: "Tu perfil" profile: "Tu perfil"
notification: "Notificaciones" notification: "Notificaciones"
apps: "Aplicaciones"
tags: "Etiquetas" tags: "Etiquetas"
mute-and-block: "Silenciar/Bloquear"
blocking: "Bloquear" blocking: "Bloquear"
security: "Seguridad"
password: "Contraseña" password: "Contraseña"
other: "Otros"
appearance: "Diseño"
behavior: "Comportamiento"
fetch-on-scroll-desc: "Cuando te deslizas al final de la página nuevo contenido se carga automáticamente."
note-visibility: "Visibilidad de la publicación"
default-note-visibility: "Rango de publicación predeterminado"
web-search-engine: "Buscador web"
web-search-engine-desc: "Ejemplo: https://www.google.com/?#q={{query}}"
use-os-default-emojis: "Usar los emoticonos estándar del sistema operativo" use-os-default-emojis: "Usar los emoticonos estándar del sistema operativo"
line-width: "Grosor de línea" line-width: "Grosor de línea"
line-width-thick: "Grosor" line-width-thick: "Grosor"
@ -128,6 +161,19 @@ common:
contrasted-acct: "Añadir contraste al nombre de usuario" contrasted-acct: "Añadir contraste al nombre de usuario"
wallpaper: "Fondo de pantalla" wallpaper: "Fondo de pantalla"
choose-wallpaper: "Escoge un fondo de pantalla" choose-wallpaper: "Escoge un fondo de pantalla"
delete-wallpaper: "Quitar fondo de pantalla"
show-clock-on-header: "Muestra el reloj en la parte superior derecha"
timeline: "Timeline"
sound: "Sonido"
enable-sounds: "Habilitar sonido"
volume: "Volúmen"
test: "Prueba"
version: "Versión"
no-updates: "No hay actualizaciones disponibles"
no-updates-desc: "Tu Misskey está actualizado"
update-available: "¡Una nueva versión está disponible!"
update-available-desc: "Las actualizaciones se aplicarán cuando la página se vuelva a cargar."
advanced-settings: "Configuraciones avanzadas"
navbar-position-left: "Izquierda" navbar-position-left: "Izquierda"
search: "Buscar" search: "Buscar"
delete: "eliminar" delete: "eliminar"
@ -420,6 +466,8 @@ common/views/components/profile-editor.vue:
enter-password: "Escribe una contraseña" enter-password: "Escribe una contraseña"
common/views/components/user-list-editor.vue: common/views/components/user-list-editor.vue:
users: "Usuarios" users: "Usuarios"
common/views/components/user-lists.vue:
list-name: "Nombre de lista"
common/views/widgets/broadcast.vue: common/views/widgets/broadcast.vue:
fetching: "Recuperando" fetching: "Recuperando"
no-broadcasts: "Sin emisión" no-broadcasts: "Sin emisión"
@ -727,8 +775,6 @@ desktop/views/components/received-follow-requests-window.vue:
reject: "Rechazar" reject: "Rechazar"
desktop/views/components/user-lists-window.vue: desktop/views/components/user-lists-window.vue:
title: "Listas de usuario" title: "Listas de usuario"
create-list: "Crear lista"
list-name: "Nombre de lista"
desktop/views/components/user-preview.vue: desktop/views/components/user-preview.vue:
notes: "Publicaciones" notes: "Publicaciones"
following: "Sigue" following: "Sigue"
@ -869,6 +915,7 @@ mobile/views/components/post-form.vue:
reply: "Responder" reply: "Responder"
renote: "Republicar" renote: "Republicar"
reply-placeholder: "Responder a esta nota..." reply-placeholder: "Responder a esta nota..."
geolocation-alert: "Tu dispositivo no tiene soporte de geolocalización."
mobile/views/components/sub-note-content.vue: mobile/views/components/sub-note-content.vue:
private: "Esta publicación es privada" private: "Esta publicación es privada"
deleted: "Esta publicación ha sido removida" deleted: "Esta publicación ha sido removida"
@ -884,6 +931,10 @@ mobile/views/components/ui.nav.vue:
game: "Juegos" game: "Juegos"
admin: "Admin" admin: "Admin"
about: "Sobre" about: "Sobre"
mobile/views/pages/drive.vue:
contextmenu:
upload: "Subir fichero"
create-folder: "Crear una carpeta"
mobile/views/pages/user-lists.vue: mobile/views/pages/user-lists.vue:
title: "Listas" title: "Listas"
mobile/views/pages/home.vue: mobile/views/pages/home.vue:

View File

@ -13,7 +13,7 @@ common:
rich-contents-desc: "Partagez vos idées, les événements et les sujets qui vous tiennent à cœur ainsi que tout autre chose que vous souhaitez partager avec les autres. Si vous le désirez, vous pouvez décorer vos messages en utilisant une syntaxe différente ou en y joignant des sondages et des fichiers, tels que les photos ou les vidéos que vous aimez." rich-contents-desc: "Partagez vos idées, les événements et les sujets qui vous tiennent à cœur ainsi que tout autre chose que vous souhaitez partager avec les autres. Si vous le désirez, vous pouvez décorer vos messages en utilisant une syntaxe différente ou en y joignant des sondages et des fichiers, tels que les photos ou les vidéos que vous aimez."
reaction: "Réactions" reaction: "Réactions"
reaction-desc: "Une manière simple d'exprimer vos émotions. Misskey peut attacher diverses réactions aux publications des autres utilisateurs. Si vous essayez les réactions sur Misskey, vous ne pourrez plus retourner sur une autre plateforme de réseaux sociaux n'offrant que des « J'aime »." reaction-desc: "Une manière simple d'exprimer vos émotions. Misskey peut attacher diverses réactions aux publications des autres utilisateurs. Si vous essayez les réactions sur Misskey, vous ne pourrez plus retourner sur une autre plateforme de réseaux sociaux n'offrant que des « J'aime »."
ui: "Interface utilisateur" ui: "Interface"
ui-desc: "Aucune interface graphique ne peut plaire à tout le monde. Par conséquent, Misskey possède une interface utilisateur hautement personnalisable selon vos goûts. Vous pouvez rendre votre page d'accueil originale en modifiant la mise en page de votre fil et en déplaçant les widgets que vous pouvez facilement ajuster pour vous approprier cet espace." ui-desc: "Aucune interface graphique ne peut plaire à tout le monde. Par conséquent, Misskey possède une interface utilisateur hautement personnalisable selon vos goûts. Vous pouvez rendre votre page d'accueil originale en modifiant la mise en page de votre fil et en déplaçant les widgets que vous pouvez facilement ajuster pour vous approprier cet espace."
drive: "Drive" drive: "Drive"
drive-desc: "Vous voulez poster une photo que vous avez déjà transférée ? Vous souhaitez organiser, nommer et créer un dossier pour vos fichiers téléversés ? Misskey Drive est la meilleure solution pour vous. Très facile de partager vos fichiers en ligne." drive-desc: "Vous voulez poster une photo que vous avez déjà transférée ? Vous souhaitez organiser, nommer et créer un dossier pour vos fichiers téléversés ? Misskey Drive est la meilleure solution pour vous. Très facile de partager vos fichiers en ligne."
@ -33,6 +33,7 @@ common:
signin: "Se connecter" signin: "Se connecter"
signup: "S'enregistrer" signup: "S'enregistrer"
signout: "Se déconnecter" signout: "Se déconnecter"
reload-to-apply-the-setting: "Le rechargement de la page est nécessaire pour appliquer ces paramètres. Désirez-vous la recharger maintenant ?"
got-it: "Jai compris !" got-it: "Jai compris !"
customization-tips: customization-tips:
title: "Conseils de personnalisation" title: "Conseils de personnalisation"
@ -68,10 +69,15 @@ common:
followers: "Abonné·e·s" followers: "Abonné·e·s"
favorites: "Mettre cette note en favoris" favorites: "Mettre cette note en favoris"
permissions: permissions:
'read:account': "Afficher les informations du compte" "read:account": "Afficher les informations du compte"
'write:account': "Mettre à jour les informations de votre compte" "write:account": "Mettre à jour les informations de votre compte"
'read:drive': "Parcourir le Drive" "read:drive": "Parcourir le Drive"
'write:drive': "Écrire sur le Drive" "write:drive": "Écrire sur le Drive"
"read:favorites": "Afficher les favoris"
"write:notes": "Créer ou supprimer des publications"
"read:notifications": "Afficher les notifications"
"read:reactions": "Lire les réactions"
"write:votes": "Vote"
empty-timeline-info: empty-timeline-info:
follow-users-to-make-your-timeline: "Les utilisateurs suivants afficheront leurs publications sur votre fil." follow-users-to-make-your-timeline: "Les utilisateurs suivants afficheront leurs publications sur votre fil."
explore: "Trouver des utilisateurs" explore: "Trouver des utilisateurs"
@ -135,6 +141,7 @@ common:
appearance: "Apparence" appearance: "Apparence"
behavior: "Comportement" behavior: "Comportement"
fetch-on-scroll: "Chargement automatique lors du défilement" fetch-on-scroll: "Chargement automatique lors du défilement"
fetch-on-scroll-desc: "Chargement automatique du contenu lors du défilement de la page."
note-visibility: "Visibilité de la publication" note-visibility: "Visibilité de la publication"
default-note-visibility: "Visibilité par défaut" default-note-visibility: "Visibilité par défaut"
remember-note-visibility: "Se souvenir du mode de visibilité de la publication" remember-note-visibility: "Se souvenir du mode de visibilité de la publication"
@ -205,16 +212,16 @@ common:
debug-mode: "Activer le mode débogage" debug-mode: "Activer le mode débogage"
debug-mode-desc: "Ce paramètre est stocké dans le navigateur." debug-mode-desc: "Ce paramètre est stocké dans le navigateur."
navbar-position: "Position de la barre de navigation" navbar-position: "Position de la barre de navigation"
navbar-position-top: "en haut" navbar-position-top: "En haut"
navbar-position-left: "À gauche" navbar-position-left: "À gauche"
navbar-position-right: "à droite" navbar-position-right: "À droite"
i-am-under-limited-internet: "J'ai un accès Internet limité" i-am-under-limited-internet: "J'ai un accès Internet limité"
post-style: "Style d'affichage des notes" post-style: "Style d'affichage des notes"
post-style-standard: "Standard" post-style-standard: "Standard"
post-style-smart: "Intelligent" post-style-smart: "Intelligent"
notification-position: "Afficher les notifications" notification-position: "Afficher les notifications"
notification-position-bottom: "en bas" notification-position-bottom: "en bas"
notification-position-top: "en haut" notification-position-top: "En haut"
disable-via-mobile: "Enlever la mention publié via 'mobile'" disable-via-mobile: "Enlever la mention publié via 'mobile'"
load-raw-images: "Afficher les photos jointes dans leur qualité originale" load-raw-images: "Afficher les photos jointes dans leur qualité originale"
load-remote-media: "Afficher les médias depuis le serveur distant" load-remote-media: "Afficher les médias depuis le serveur distant"
@ -274,6 +281,7 @@ common:
nav: "Navigation" nav: "Navigation"
tips: "Conseils" tips: "Conseils"
hashtags: "Hashtags" hashtags: "Hashtags"
queue: "File d'attente"
dev: "Échec lors de la création de lapplication. Veuillez réessayer." dev: "Échec lors de la création de lapplication. Veuillez réessayer."
ai-chan-kawaii: "Ai-Chan est mignonne !" ai-chan-kawaii: "Ai-Chan est mignonne !"
you: "Vous" you: "Vous"
@ -302,6 +310,7 @@ common/views/pages/explore.vue:
explore: "Explorer {host}" explore: "Explorer {host}"
users-info: "Actuellement, {users} utilisateurs se sont inscrit ici" users-info: "Actuellement, {users} utilisateurs se sont inscrit ici"
common/views/components/url-preview.vue: common/views/components/url-preview.vue:
enable-player: "Activer la lecture"
disable-player: "Fermer le lecteur" disable-player: "Fermer le lecteur"
common/views/components/user-list.vue: common/views/components/user-list.vue:
no-users: "Il n'y a aucun utilisateur" no-users: "Il n'y a aucun utilisateur"
@ -642,6 +651,7 @@ common/views/components/profile-editor.vue:
blocking-list: "Liste des comptes bloqués" blocking-list: "Liste des comptes bloqués"
user-lists: "Listes" user-lists: "Listes"
export-requested: "Vous avez demandé une exportation. Cela peut prendre un certain temps. Une fois l'exportation terminée, le fichier résultant sera ajouté dans le Drive." export-requested: "Vous avez demandé une exportation. Cela peut prendre un certain temps. Une fois l'exportation terminée, le fichier résultant sera ajouté dans le Drive."
import-requested: "Vous avez initié un import. Ceci peut prendre un peu de temps."
enter-password: "Veuillez saisir votre mot de passe" enter-password: "Veuillez saisir votre mot de passe"
danger-zone: "Zone de danger" danger-zone: "Zone de danger"
delete-account: "Supprimer le compte" delete-account: "Supprimer le compte"
@ -653,6 +663,9 @@ common/views/components/user-list-editor.vue:
remove-user: "Retirer de cette liste" remove-user: "Retirer de cette liste"
delete-are-you-sure: "Voulez-vous vraiment supprimer la liste « $1 » ?" delete-are-you-sure: "Voulez-vous vraiment supprimer la liste « $1 » ?"
deleted: "Supprimé" deleted: "Supprimé"
common/views/components/user-lists.vue:
create-list: "Créer une liste"
list-name: "Nom de la liste"
common/views/widgets/broadcast.vue: common/views/widgets/broadcast.vue:
fetching: "Récupération" fetching: "Récupération"
no-broadcasts: "Aucune annonce" no-broadcasts: "Aucune annonce"
@ -953,6 +966,7 @@ common/views/components/password-settings.vue:
changed: "Mot de passe modifié avec succès" changed: "Mot de passe modifié avec succès"
failed: "Échec lors de la modification du mot de passe" failed: "Échec lors de la modification du mot de passe"
common/views/components/post-form-attaches.vue: common/views/components/post-form-attaches.vue:
attach-cancel: "Enlever le fichier attaché"
mark-as-sensitive: "Marquer comme sensible" mark-as-sensitive: "Marquer comme sensible"
unmark-as-sensitive: "Ne pas marquer comme sensible" unmark-as-sensitive: "Ne pas marquer comme sensible"
desktop/views/components/sub-note-content.vue: desktop/views/components/sub-note-content.vue:
@ -999,8 +1013,6 @@ desktop/views/components/received-follow-requests-window.vue:
reject: "Refuser" reject: "Refuser"
desktop/views/components/user-lists-window.vue: desktop/views/components/user-lists-window.vue:
title: "Listes de l'utilisateur" title: "Listes de l'utilisateur"
create-list: "Créer une liste"
list-name: "Nom de la liste"
desktop/views/components/user-preview.vue: desktop/views/components/user-preview.vue:
notes: "Publications" notes: "Publications"
following: "Abonné à" following: "Abonné à"
@ -1377,8 +1389,6 @@ mobile/views/components/drive.vue:
file-count: "Fichier·s" file-count: "Fichier·s"
nothing-in-drive: "Rien" nothing-in-drive: "Rien"
folder-is-empty: "Ce dossier est vide" folder-is-empty: "Ce dossier est vide"
prompt: "Que veux-tu faire ? (Entrez un nombre): <1 → Télécharger le fichier | 2 → Télécharger le fichier avec l'URL | 3 → Créer le dossier | 4 → Modifier le nom du dossier | 5 → Déplacer ce dossier | 6 → Supprimer ce dossier >"
deletion-alert: "Désolé ! La suppression dun dossier nest pas encore implémentée."
folder-name: "Nom du dossier" folder-name: "Nom du dossier"
url-prompt: "URL du fichier que vous souhaitez téléverser" url-prompt: "URL du fichier que vous souhaitez téléverser"
uploading: "Envoi demandé. Le téléversement pourrait prendre un certain temps avant de s'achever." uploading: "Envoi demandé. Le téléversement pourrait prendre un certain temps avant de s'achever."
@ -1434,7 +1444,7 @@ mobile/views/components/post-form.vue:
quote-placeholder: "Citer ce billet ... (Facultatif)" quote-placeholder: "Citer ce billet ... (Facultatif)"
reply-placeholder: "Répondre à cette note" reply-placeholder: "Répondre à cette note"
cw-placeholder: "Commenter le contenu (optionnel)" cw-placeholder: "Commenter le contenu (optionnel)"
location-alert: "Votre appareil ne prend pas en charge les services de localisation" geolocation-alert: "Votre appareil ne prend pas en charge les services de localisation"
error: "Erreur" error: "Erreur"
username-prompt: "Saisir un nom d'utilisateur" username-prompt: "Saisir un nom d'utilisateur"
mobile/views/components/sub-note-content.vue: mobile/views/components/sub-note-content.vue:
@ -1455,9 +1465,12 @@ mobile/views/components/ui.nav.vue:
game: "Jeux" game: "Jeux"
admin: "Admin" admin: "Admin"
about: "À propos de Misskey" about: "À propos de Misskey"
mobile/views/pages/drive.vue:
contextmenu:
upload: "Téléverser un fichier"
create-folder: "Créer un dossier"
mobile/views/pages/user-lists.vue: mobile/views/pages/user-lists.vue:
title: "Listes" title: "Listes"
enter-list-name: "Nom de la liste"
mobile/views/pages/signup.vue: mobile/views/pages/signup.vue:
lets-start: "Votre compte est prêt ! 📦" lets-start: "Votre compte est prêt ! 📦"
mobile/views/pages/followers.vue: mobile/views/pages/followers.vue:
@ -1586,10 +1599,7 @@ dev/views/new-app.vue:
create-app: "Création dune application" create-app: "Création dune application"
app-name: "Nom de lapplication" app-name: "Nom de lapplication"
app-name-desc: "Le nom de votre application" app-name-desc: "Le nom de votre application"
app-name-ex: "p. ex. Misskey pour iOS"
app-overview: "Description courte de lapplication" app-overview: "Description courte de lapplication"
app-desc: "Brève description introductive à votre application."
app-desc-ex: "p. ex) Misskey pour iOS"
callback-url: "LUrl de callback (facultatif)" callback-url: "LUrl de callback (facultatif)"
callback-url-desc: "Vous pouvez définir lURL de redirection lorsque lutilisateur sest authentifié via formulaire dauthentification." callback-url-desc: "Vous pouvez définir lURL de redirection lorsque lutilisateur sest authentifié via formulaire dauthentification."
authority: "Autorisations " authority: "Autorisations "

View File

@ -35,6 +35,7 @@ common:
signup: "新規登録" signup: "新規登録"
signout: "ログアウト" signout: "ログアウト"
reload-to-apply-the-setting: "この設定を反映するにはページをリロードする必要があります。今すぐリロードしますか?" reload-to-apply-the-setting: "この設定を反映するにはページをリロードする必要があります。今すぐリロードしますか?"
fetching-as-ap-object: "連合に照会中"
got-it: "わかった" got-it: "わかった"
customization-tips: customization-tips:
@ -527,8 +528,12 @@ common/views/components/user-menu.vue:
mention: "メンション" mention: "メンション"
mute: "ミュート" mute: "ミュート"
unmute: "ミュート解除" unmute: "ミュート解除"
mute-confirm: "このユーザーをミュートしますか?"
unmute-confirm: "このユーザーをミュート解除しますか?"
block: "ブロック" block: "ブロック"
unblock: "ブロック解除" unblock: "ブロック解除"
block-confirm: "このユーザーをブロックしますか?"
unblock-confirm: "このユーザーをブロック解除しますか?"
push-to-list: "リストに追加" push-to-list: "リストに追加"
select-list: "リストを選択してください" select-list: "リストを選択してください"
report-abuse: "スパムを報告" report-abuse: "スパムを報告"
@ -536,8 +541,12 @@ common/views/components/user-menu.vue:
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。" report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
silence: "サイレンス" silence: "サイレンス"
unsilence: "サイレンス解除" unsilence: "サイレンス解除"
silence-confirm: "このユーザーをサイレンスしますか?"
unsilence-confirm: "このユーザーをサイレンス解除しますか?"
suspend: "凍結" suspend: "凍結"
unsuspend: "凍結解除" unsuspend: "凍結解除"
suspend-confirm: "このユーザーを凍結しますか?"
unsuspend-confirm: "このユーザーを凍結解除しますか?"
common/views/components/poll.vue: common/views/components/poll.vue:
vote-to: "「{}」に投票する" vote-to: "「{}」に投票する"
@ -739,6 +748,10 @@ common/views/components/user-list-editor.vue:
delete-are-you-sure: "リスト「$1」を削除しますか" delete-are-you-sure: "リスト「$1」を削除しますか"
deleted: "削除しました" deleted: "削除しました"
common/views/components/user-lists.vue:
create-list: "リストを作成"
list-name: "リスト名"
common/views/widgets/broadcast.vue: common/views/widgets/broadcast.vue:
fetching: "確認中" fetching: "確認中"
no-broadcasts: "お知らせはありません" no-broadcasts: "お知らせはありません"
@ -1145,8 +1158,6 @@ desktop/views/components/received-follow-requests-window.vue:
desktop/views/components/user-lists-window.vue: desktop/views/components/user-lists-window.vue:
title: "リスト" title: "リスト"
create-list: "リストを作成"
list-name: "リスト名"
desktop/views/components/user-preview.vue: desktop/views/components/user-preview.vue:
notes: "投稿" notes: "投稿"
@ -1336,7 +1347,9 @@ admin/views/users.vue:
unsuspend-confirm: "凍結を解除しますか?" unsuspend-confirm: "凍結を解除しますか?"
unsuspended: "凍結を解除しました" unsuspended: "凍結を解除しました"
make-silence: "サイレンス" make-silence: "サイレンス"
silence-confirm: "サイレンスしますか?"
unmake-silence: "サイレンスの解除" unmake-silence: "サイレンスの解除"
unsilence-confirm: "サイレンスを解除しますか?"
verify: "公式アカウントにする" verify: "公式アカウントにする"
verify-confirm: "公式アカウントにしますか?" verify-confirm: "公式アカウントにしますか?"
verified: "公式アカウントにしました" verified: "公式アカウントにしました"
@ -1573,12 +1586,11 @@ mobile/views/components/drive.vue:
file-count: "ファイル" file-count: "ファイル"
nothing-in-drive: "ドライブには何もありません" nothing-in-drive: "ドライブには何もありません"
folder-is-empty: "このフォルダは空です" folder-is-empty: "このフォルダは空です"
prompt: "何をしますか?(数字を入力してください): <1 → ファイルをアップロード | 2 → ファイルをURLでアップロード | 3 → フォルダ作成 | 4 → このフォルダ名を変更 | 5 → このフォルダを移動 | 6 → このフォルダを削除>"
deletion-alert: "ごめんなさい!フォルダの削除は未実装です...。"
folder-name: "フォルダー名" folder-name: "フォルダー名"
here-is-root: "現在いる場所はルートで、フォルダではありません。" here-is-root: "現在いる場所はルートで、フォルダではありません。"
url-prompt: "アップロードしたいファイルのURL" url-prompt: "アップロードしたいファイルのURL"
uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。" uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。"
folder-name-cannot-empty: "フォルダ名を空白にすることはできません。"
mobile/views/components/drive-file-chooser.vue: mobile/views/components/drive-file-chooser.vue:
select-file: "ファイルを選択" select-file: "ファイルを選択"
@ -1668,9 +1680,17 @@ mobile/views/components/ui.nav.vue:
admin: "管理" admin: "管理"
about: "Misskeyについて" about: "Misskeyについて"
mobile/views/pages/drive.vue:
contextmenu:
upload: "ファイルをアップロード"
url-upload: "ファイルをURLでアップロード"
create-folder: "フォルダーを作成"
rename-folder: "フォルダー名を変更"
move-folder: "このフォルダを移動"
delete-folder: "このフォルダを削除"
mobile/views/pages/user-lists.vue: mobile/views/pages/user-lists.vue:
title: "リスト" title: "リスト"
enter-list-name: "リスト名を入力してください"
mobile/views/pages/signup.vue: mobile/views/pages/signup.vue:
lets-start: "📦 始めましょう" lets-start: "📦 始めましょう"

View File

@ -60,6 +60,8 @@ common:
following: "フォローしとる" following: "フォローしとる"
followers: "フォロワー" followers: "フォロワー"
favorites: "お気に入り" favorites: "お気に入り"
permissions:
"write:votes": "投票するで"
weekday-short: weekday-short:
sunday: "日" sunday: "日"
monday: "月" monday: "月"
@ -476,6 +478,8 @@ common/views/components/profile-editor.vue:
enter-password: "パスワードを入れてや" enter-password: "パスワードを入れてや"
common/views/components/user-list-editor.vue: common/views/components/user-list-editor.vue:
users: "ユーザー" users: "ユーザー"
common/views/components/user-lists.vue:
list-name: "リスト名"
common/views/widgets/broadcast.vue: common/views/widgets/broadcast.vue:
fetching: "見てみるわ…" fetching: "見てみるわ…"
no-broadcasts: "お知らせはあらへんで" no-broadcasts: "お知らせはあらへんで"
@ -813,8 +817,6 @@ desktop/views/components/received-follow-requests-window.vue:
reject: "許さん" reject: "許さん"
desktop/views/components/user-lists-window.vue: desktop/views/components/user-lists-window.vue:
title: "リスト" title: "リスト"
create-list: "新しいリストを作成"
list-name: "リスト名"
desktop/views/components/user-preview.vue: desktop/views/components/user-preview.vue:
notes: "投稿" notes: "投稿"
following: "フォロー" following: "フォロー"
@ -1069,8 +1071,6 @@ mobile/views/components/drive.vue:
file-count: "ファイル" file-count: "ファイル"
nothing-in-drive: "ドライブには何もあらへんで。" nothing-in-drive: "ドライブには何もあらへんで。"
folder-is-empty: "このフォルダ何もないわ" folder-is-empty: "このフォルダ何もないわ"
prompt: "何すんの?(数字を入れてや): <1 → ファイルをアップロード | 2 → ファイルをURLでアップロード | 3 → フォルダ作成 | 4 → このフォルダ名を変更 | 5 → このフォルダを移動 | 6 → このフォルダを削除>"
deletion-alert: "フォルダの削除は未実装やねん...。堪忍な!"
folder-name: "フォルダー名" folder-name: "フォルダー名"
url-prompt: "このURLのファイルをアップロードしたいねん" url-prompt: "このURLのファイルをアップロードしたいねん"
uploading: "アップロードをリクエストしたで。アップロードが完了するまで時間がかかるかも分からん、知らんけど。" uploading: "アップロードをリクエストしたで。アップロードが完了するまで時間がかかるかも分からん、知らんけど。"
@ -1126,7 +1126,7 @@ mobile/views/components/post-form.vue:
quote-placeholder: "この投稿を持ってくる(オプション)" quote-placeholder: "この投稿を持ってくる(オプション)"
reply-placeholder: "この投稿への返信..." reply-placeholder: "この投稿への返信..."
cw-placeholder: "内容への注釈 (オプション)" cw-placeholder: "内容への注釈 (オプション)"
location-alert: "あんさんのつことる端末は位置情報に対応しとらんみたいやわ、知らんけど。" geolocation-alert: "あんさんのつことる端末は位置情報に対応しとらんみたいやわ、知らんけど。"
error: "エラー" error: "エラー"
username-prompt: "ユーザー名を入力してや" username-prompt: "ユーザー名を入力してや"
mobile/views/components/sub-note-content.vue: mobile/views/components/sub-note-content.vue:
@ -1147,9 +1147,12 @@ mobile/views/components/ui.nav.vue:
game: "ゲーム" game: "ゲーム"
admin: "管理" admin: "管理"
about: "Misskeyってなんや" about: "Misskeyってなんや"
mobile/views/pages/drive.vue:
contextmenu:
upload: "ファイル上げる"
create-folder: "フォルダー作る"
mobile/views/pages/user-lists.vue: mobile/views/pages/user-lists.vue:
title: "リスト" title: "リスト"
enter-list-name: "リスト名を入力してや"
mobile/views/pages/signup.vue: mobile/views/pages/signup.vue:
lets-start: "📦 始めようや" lets-start: "📦 始めようや"
mobile/views/pages/followers.vue: mobile/views/pages/followers.vue:
@ -1272,10 +1275,7 @@ dev/views/new-app.vue:
create-app: "アプリケーション作る" create-app: "アプリケーション作る"
app-name: "アプリケーションの名前" app-name: "アプリケーションの名前"
app-name-desc: "あんたのアプリの名前。" app-name-desc: "あんたのアプリの名前。"
app-name-ex: "ex) 関西ミスキー保安協会"
app-overview: "このアプリどんなん?" app-overview: "このアプリどんなん?"
app-desc: "あんたのアプリどんなんか教えて"
app-desc-ex: "ex) 関西人なら誰でも口ずさめるこのCMがついにMisskeyへ。"
callback-url: "コールバックURL (無くてもええで)" callback-url: "コールバックURL (無くてもええで)"
callback-url-desc: "ユーザーが認証フォームで認証した後どこに連れてくかを設定できるで" callback-url-desc: "ユーザーが認証フォームで認証した後どこに連れてくかを設定できるで"
authority: "権限" authority: "権限"

View File

@ -70,10 +70,11 @@ common:
followers: "팔로워" followers: "팔로워"
favorites: "즐겨찾기" favorites: "즐겨찾기"
permissions: permissions:
'read:account': "계정 정보 보기" "read:account": "계정 정보 보기"
'write:account': "계정 정보 변경" "write:account": "계정 정보 변경"
'read:drive': "드라이브 보기" "read:drive": "드라이브 보기"
'write:drive': "드라이브 수정" "write:drive": "드라이브 수정"
"write:votes": "투표하기"
empty-timeline-info: empty-timeline-info:
follow-users-to-make-your-timeline: "사용자를 팔로우하면 글이 타임라인에 표시됩니다." follow-users-to-make-your-timeline: "사용자를 팔로우하면 글이 타임라인에 표시됩니다."
explore: "사용자 탐색" explore: "사용자 탐색"
@ -281,6 +282,7 @@ common:
nav: "내비게이션" nav: "내비게이션"
tips: "팁" tips: "팁"
hashtags: "해시태그" hashtags: "해시태그"
queue: "큐"
dev: "앱을 만드는 데 실패했습니다. 다시 시도하시기 바랍니다." dev: "앱을 만드는 데 실패했습니다. 다시 시도하시기 바랍니다."
ai-chan-kawaii: "아이쨩 귀여워" ai-chan-kawaii: "아이쨩 귀여워"
you: "당신" you: "당신"
@ -665,6 +667,8 @@ common/views/components/user-list-editor.vue:
remove-user: "이 리스트에서 제거" remove-user: "이 리스트에서 제거"
delete-are-you-sure: "리스트 \"$1\"을 삭제하시겠습니까?" delete-are-you-sure: "리스트 \"$1\"을 삭제하시겠습니까?"
deleted: "삭제하였습니다" deleted: "삭제하였습니다"
common/views/components/user-lists.vue:
list-name: "리스트 이름"
common/views/widgets/broadcast.vue: common/views/widgets/broadcast.vue:
fetching: "확인중" fetching: "확인중"
no-broadcasts: "공지사항이 없습니다" no-broadcasts: "공지사항이 없습니다"
@ -1013,8 +1017,6 @@ desktop/views/components/received-follow-requests-window.vue:
reject: "거부" reject: "거부"
desktop/views/components/user-lists-window.vue: desktop/views/components/user-lists-window.vue:
title: "리스트" title: "리스트"
create-list: "리스트 만들기"
list-name: "리스트 이름"
desktop/views/components/user-preview.vue: desktop/views/components/user-preview.vue:
notes: "글" notes: "글"
following: "팔로잉" following: "팔로잉"
@ -1404,8 +1406,6 @@ mobile/views/components/drive.vue:
file-count: "파일" file-count: "파일"
nothing-in-drive: "드라이브에 아무것도 없습니다" nothing-in-drive: "드라이브에 아무것도 없습니다"
folder-is-empty: "폴더가 비어있습니다" folder-is-empty: "폴더가 비어있습니다"
prompt: "무엇을 하시겠습니까? (숫자를 입력하여 주십시오): <1 → 파일 업로드 | 2 → 파일을 URL에서 업로드 | 3 → 폴더 만들기 | 4 → 이 폴더의 이름을 변경 | 5 → 현재 폴더 이동| 6 → 현재 폴더 삭제>"
deletion-alert: "죄송합니다! 폴더 삭제는 아직 구현되지 않았습니다..."
folder-name: "폴더 이름" folder-name: "폴더 이름"
here-is-root: "현재 경로는 루트 경로로 폴더가 아닙니다." here-is-root: "현재 경로는 루트 경로로 폴더가 아닙니다."
url-prompt: "업로드 하려는 파일의 URL" url-prompt: "업로드 하려는 파일의 URL"
@ -1462,7 +1462,7 @@ mobile/views/components/post-form.vue:
quote-placeholder: "이 글을 인용... (선택적)" quote-placeholder: "이 글을 인용... (선택적)"
reply-placeholder: "이 글에 답글..." reply-placeholder: "이 글에 답글..."
cw-placeholder: "내용 주석 (선택적)" cw-placeholder: "내용 주석 (선택적)"
location-alert: "사용하시는 장치가 위치정보 기능에 대응하지 않습니다" geolocation-alert: "사용하시는 장치가 위치정보 기능에 대응하지 않습니다"
error: "오류" error: "오류"
username-prompt: "사용자명을 입력하여 주십시오" username-prompt: "사용자명을 입력하여 주십시오"
mobile/views/components/sub-note-content.vue: mobile/views/components/sub-note-content.vue:
@ -1483,9 +1483,12 @@ mobile/views/components/ui.nav.vue:
game: "게임" game: "게임"
admin: "관리" admin: "관리"
about: "Misskey에 대하여" about: "Misskey에 대하여"
mobile/views/pages/drive.vue:
contextmenu:
upload: "파일 업로드"
create-folder: "폴더 만들기"
mobile/views/pages/user-lists.vue: mobile/views/pages/user-lists.vue:
title: "리스트" title: "리스트"
enter-list-name: "리스트명을 입력하십시오"
mobile/views/pages/signup.vue: mobile/views/pages/signup.vue:
lets-start: "📦 이제 시작해도 됩니다" lets-start: "📦 이제 시작해도 됩니다"
mobile/views/pages/followers.vue: mobile/views/pages/followers.vue:
@ -1615,10 +1618,7 @@ dev/views/new-app.vue:
create-app: "어플리케이션 생성" create-app: "어플리케이션 생성"
app-name: "어플리케이션 이름" app-name: "어플리케이션 이름"
app-name-desc: "앱의 이름." app-name-desc: "앱의 이름."
app-name-ex: "ex) Misskey for iOS"
app-overview: "앱 개요" app-overview: "앱 개요"
app-desc: "어플리케이션에 대한 간단한 설명."
app-desc-ex: "ex) Misskey iOS 클라이언트."
callback-url: "콜백 URL (옵션)" callback-url: "콜백 URL (옵션)"
callback-url-desc: "사용자가 인증 폼에서 인증한 뒤 리다이렉트할 URL을 설정합니다." callback-url-desc: "사용자가 인증 폼에서 인증한 뒤 리다이렉트할 URL을 설정합니다."
authority: "권한" authority: "권한"

View File

@ -23,6 +23,8 @@ common:
timeline: "Tijdlijn" timeline: "Tijdlijn"
followers: "Volgers" followers: "Volgers"
favorites: "Deze notitie toevoegen aan favorieten" favorites: "Deze notitie toevoegen aan favorieten"
permissions:
"write:votes": "Stemmen"
weekday-short: weekday-short:
sunday: "Z" sunday: "Z"
monday: "M" monday: "M"
@ -401,7 +403,6 @@ desktop/views/components/ui.header.search.vue:
placeholder: "Zoeken" placeholder: "Zoeken"
desktop/views/components/user-lists-window.vue: desktop/views/components/user-lists-window.vue:
title: "Lijsten" title: "Lijsten"
create-list: "Lijst creëren"
desktop/views/components/user-preview.vue: desktop/views/components/user-preview.vue:
notes: "Berichten" notes: "Berichten"
following: "Volgend" following: "Volgend"
@ -555,6 +556,10 @@ mobile/views/components/ui.nav.vue:
user-lists: "Lijsten" user-lists: "Lijsten"
game: "Othello spelen" game: "Othello spelen"
about: "Over Misskey" about: "Over Misskey"
mobile/views/pages/drive.vue:
contextmenu:
upload: "Bestand uploaden"
create-folder: "Map creëren"
mobile/views/pages/user-lists.vue: mobile/views/pages/user-lists.vue:
title: "Lijsten" title: "Lijsten"
mobile/views/pages/home.vue: mobile/views/pages/home.vue:

View File

@ -37,6 +37,8 @@ common:
home: "Hjem" home: "Hjem"
followers: "Følgere" followers: "Følgere"
favorites: "Merket som favoritt" favorites: "Merket som favoritt"
permissions:
"write:votes": "Stem"
weekday-short: weekday-short:
sunday: "S" sunday: "S"
monday: "M" monday: "M"
@ -190,6 +192,8 @@ common/views/components/profile-editor.vue:
user-lists: "Lister" user-lists: "Lister"
common/views/components/user-list-editor.vue: common/views/components/user-list-editor.vue:
users: "Bruker" users: "Bruker"
common/views/components/user-lists.vue:
list-name: "Liste navn"
common/views/widgets/broadcast.vue: common/views/widgets/broadcast.vue:
fetching: "Henter" fetching: "Henter"
next: "Neste" next: "Neste"
@ -308,7 +312,6 @@ desktop/views/components/received-follow-requests-window.vue:
reject: "Avslå" reject: "Avslå"
desktop/views/components/user-lists-window.vue: desktop/views/components/user-lists-window.vue:
title: "Brukerlister" title: "Brukerlister"
list-name: "Liste navn"
desktop/views/components/user-preview.vue: desktop/views/components/user-preview.vue:
notes: "Innlegg" notes: "Innlegg"
following: "Følger" following: "Følger"

View File

@ -63,7 +63,8 @@ common:
followers: "Śledzący" followers: "Śledzący"
favorites: "Moje ulubione" favorites: "Moje ulubione"
permissions: permissions:
'read:drive': "Wyświetl dysk" "read:drive": "Wyświetl dysk"
"write:votes": "Zagłosuj"
empty-timeline-info: empty-timeline-info:
explore: "Poznaj" explore: "Poznaj"
weekday-short: weekday-short:
@ -511,6 +512,8 @@ common/views/components/user-list-editor.vue:
remove-user: "Usuń z tej listy" remove-user: "Usuń z tej listy"
delete-are-you-sure: "Usunąć listę \"$1\"?" delete-are-you-sure: "Usunąć listę \"$1\"?"
deleted: "Usunięto" deleted: "Usunięto"
common/views/components/user-lists.vue:
list-name: "Nazwa listy"
common/views/widgets/broadcast.vue: common/views/widgets/broadcast.vue:
fetching: "Sprawdzanie" fetching: "Sprawdzanie"
no-broadcasts: "Brak transmisji" no-broadcasts: "Brak transmisji"
@ -833,8 +836,6 @@ desktop/views/components/received-follow-requests-window.vue:
reject: "Odmów" reject: "Odmów"
desktop/views/components/user-lists-window.vue: desktop/views/components/user-lists-window.vue:
title: "Listy" title: "Listy"
create-list: "Utwórz listę"
list-name: "Nazwa listy"
desktop/views/components/user-preview.vue: desktop/views/components/user-preview.vue:
notes: "Wpisy" notes: "Wpisy"
following: "Śledzeni" following: "Śledzeni"
@ -1025,8 +1026,6 @@ mobile/views/components/drive.vue:
file-count: "Plik(i)" file-count: "Plik(i)"
nothing-in-drive: "Pusto" nothing-in-drive: "Pusto"
folder-is-empty: "Ten katalog jest pusty" folder-is-empty: "Ten katalog jest pusty"
prompt: "Co chcesz zrobić? (wprowadź odpowiednią cyfrę): <1 → Wysłać plik | 2 → Wysłać plik z adresu URL | 3 → Utworzyć katalog | 4 → Zmienić nazwę tego katalogu | 5 → Przenieść ten katalog | 6 → Usunąć ten katalog>"
deletion-alert: "Przepraszamy. Usuwanie katalogów nie zostało jeszcze zaimplementowane."
folder-name: "Nazwa katalogu" folder-name: "Nazwa katalogu"
url-prompt: "Adres URL pliku, który chcesz wysłać" url-prompt: "Adres URL pliku, który chcesz wysłać"
uploading: "Rozpoczęto wysyłanie. Może to trochę potrwać." uploading: "Rozpoczęto wysyłanie. Może to trochę potrwać."
@ -1082,7 +1081,7 @@ mobile/views/components/post-form.vue:
quote-placeholder: "Zacytuj ten wpis… (nieobowiązkowe)" quote-placeholder: "Zacytuj ten wpis… (nieobowiązkowe)"
reply-placeholder: "Odpowiedź na ten wpis…" reply-placeholder: "Odpowiedź na ten wpis…"
cw-placeholder: "Treść ostrzeżenia (opcjonalnie)" cw-placeholder: "Treść ostrzeżenia (opcjonalnie)"
location-alert: "Twoje urządzenie nie pozwala na przekazywanie informacji o lokalizacji" geolocation-alert: "Twoje urządzenie nie obsługuje geolokalizacji."
error: "Błąd" error: "Błąd"
username-prompt: "Wprowadź nazwę użytkownika" username-prompt: "Wprowadź nazwę użytkownika"
mobile/views/components/sub-note-content.vue: mobile/views/components/sub-note-content.vue:
@ -1102,9 +1101,12 @@ mobile/views/components/ui.nav.vue:
game: "Gry" game: "Gry"
admin: "Admin" admin: "Admin"
about: "O Misskey" about: "O Misskey"
mobile/views/pages/drive.vue:
contextmenu:
upload: "Wyślij plik"
create-folder: "Utwórz katalog"
mobile/views/pages/user-lists.vue: mobile/views/pages/user-lists.vue:
title: "Listy" title: "Listy"
enter-list-name: "Wprowadź nazwę listy"
mobile/views/pages/signup.vue: mobile/views/pages/signup.vue:
lets-start: "Rozpocznijmy! 📦" lets-start: "Rozpocznijmy! 📦"
mobile/views/pages/home.vue: mobile/views/pages/home.vue:

View File

@ -34,6 +34,7 @@ common:
signup: "注册" signup: "注册"
signout: "退出" signout: "退出"
reload-to-apply-the-setting: "必须重新加载页面以应用此设置。 确实要立即重新加载吗?" reload-to-apply-the-setting: "必须重新加载页面以应用此设置。 确实要立即重新加载吗?"
fetching-as-ap-object: "联合查询"
got-it: "知道了" got-it: "知道了"
customization-tips: customization-tips:
title: "自定义提示" title: "自定义提示"
@ -70,10 +71,26 @@ common:
followers: "关注者" followers: "关注者"
favorites: "最爱" favorites: "最爱"
permissions: permissions:
'read:account': "查看账户信息" "read:account": "查看账户信息"
'write:account': "更改我的帐户信息" "write:account": "更改我的帐户信息"
'read:drive': "查看网盘" "read:blocks": "查看黑名单"
'write:drive': "管理网盘文件" "write:blocks": "编辑黑名单"
"read:drive": "查看网盘"
"write:drive": "管理网盘文件"
"read:favorites": "查看收藏夹"
"write:favorites": "编辑收藏夹"
"read:following": "查看关注信息"
"write:following": "关注/取消关注"
"read:messaging": "查看对话"
"write:messaging": "对话操作"
"read:mutes": "查看屏蔽列表"
"write:mutes": "编辑屏蔽列表"
"write:notes": "创建或删除帖子"
"read:notifications": "查看通知"
"write:notifications": "管理通知"
"read:reactions": "查看回应"
"write:reactions": "回应操作"
"write:votes": "投票"
empty-timeline-info: empty-timeline-info:
follow-users-to-make-your-timeline: "关注其他用户时,帖子将显示在时间线中。" follow-users-to-make-your-timeline: "关注其他用户时,帖子将显示在时间线中。"
explore: "查找用户" explore: "查找用户"
@ -129,7 +146,7 @@ common:
apps: "应用程序" apps: "应用程序"
tags: "标签" tags: "标签"
mute-and-block: "屏蔽/拉黑" mute-and-block: "屏蔽/拉黑"
blocking: "屏蔽" blocking: "拉黑"
security: "安全性" security: "安全性"
signin: "登录历史" signin: "登录历史"
password: "密码" password: "密码"
@ -146,7 +163,7 @@ common:
keep-cw: "保留内容警告" keep-cw: "保留内容警告"
keep-cw-desc: "在回复帖子时,如果原帖设置了内容警告,默认情况下回帖也会设置相同的内容警告。" keep-cw-desc: "在回复帖子时,如果原帖设置了内容警告,默认情况下回帖也会设置相同的内容警告。"
i-like-sushi: "相比于布丁来说, 我更喜欢寿司。" i-like-sushi: "相比于布丁来说, 我更喜欢寿司。"
show-reversi-board-labels: "在 Reversi 中显示行和列表签" show-reversi-board-labels: "在黑白棋中显示行和列表签"
use-avatar-reversi-stones: "用头像作为黑白棋的棋子" use-avatar-reversi-stones: "用头像作为黑白棋的棋子"
disable-animated-mfm: "在帖子中禁用动画文本" disable-animated-mfm: "在帖子中禁用动画文本"
disable-showing-animated-images: "不播放动画" disable-showing-animated-images: "不播放动画"
@ -189,9 +206,9 @@ common:
show-clock-on-header: "在右上角显示时钟" show-clock-on-header: "在右上角显示时钟"
show-reply-target: "显示回复目标" show-reply-target: "显示回复目标"
timeline: "时间线" timeline: "时间线"
show-my-renotes: "在时间线上显示我的Renote" show-my-renotes: "在时间线上显示我的转推"
show-renoted-my-notes: "在时间线上显示我的帖子的Renote" show-renoted-my-notes: "在时间线上显示我的帖子的转推"
show-local-renotes: "在时间线上显示本地帖子的Renote" show-local-renotes: "在时间线上显示本地帖子的转推"
remain-deleted-note: "继续显示已删除的帖子" remain-deleted-note: "继续显示已删除的帖子"
sound: "声音" sound: "声音"
enable-sounds: "开启声音" enable-sounds: "开启声音"
@ -241,7 +258,7 @@ common:
is-remote-user: "此用户信息可能不准确。" is-remote-user: "此用户信息可能不准确。"
is-remote-post: "该投稿已被复制." is-remote-post: "该投稿已被复制."
view-on-remote: "查看准确的信息" view-on-remote: "查看准确的信息"
renoted-by: "由 {user} Renote" renoted-by: "由 {user} 转推"
no-notes: "没有帖子" no-notes: "没有帖子"
turn-on-darkmode: "切换暗色主题" turn-on-darkmode: "切换暗色主题"
turn-off-darkmode: "切换亮色主题" turn-off-darkmode: "切换亮色主题"
@ -281,6 +298,7 @@ common:
nav: "导航" nav: "导航"
tips: "提示" tips: "提示"
hashtags: "标签" hashtags: "标签"
queue: "队列"
dev: "构建应用程序失败,请再试一次。" dev: "构建应用程序失败,请再试一次。"
ai-chan-kawaii: "小蓝真可爱" ai-chan-kawaii: "小蓝真可爱"
you: "您" you: "您"
@ -321,8 +339,8 @@ common/views/components/games/reversi/reversi.game.vue:
surrender: "认输" surrender: "认输"
surrendered: "已认输" surrendered: "已认输"
is-llotheo: "棋子较少一方获胜(LLoTheO规则)" is-llotheo: "棋子较少一方获胜(LLoTheO规则)"
looped-map: "环棋盘" looped-map: "环棋盘"
can-put-everywhere: "可以下在任意置" can-put-everywhere: "可以下在任意置"
common/views/components/games/reversi/reversi.index.vue: common/views/components/games/reversi/reversi.index.vue:
title: "Misskey 黑白棋" title: "Misskey 黑白棋"
sub-title: "和其他人一起来玩Misskey黑白棋" sub-title: "和其他人一起来玩Misskey黑白棋"
@ -346,8 +364,8 @@ common/views/components/games/reversi/reversi.room.vue:
black-is: "{}是黑" black-is: "{}是黑"
rules: "规则" rules: "规则"
is-llotheo: "棋子较少一方获胜(LLoTheO规则)" is-llotheo: "棋子较少一方获胜(LLoTheO规则)"
looped-map: "环棋盘" looped-map: "环棋盘"
can-put-everywhere: "可以下在任意置" can-put-everywhere: "可以下在任意置"
settings-of-the-bot: "机器人设定" settings-of-the-bot: "机器人设定"
this-game-is-started-soon: "游戏即将在数秒后开始" this-game-is-started-soon: "游戏即将在数秒后开始"
waiting-for-other: "等待对手准备" waiting-for-other: "等待对手准备"
@ -463,8 +481,8 @@ common/views/components/note-menu.vue:
unfavorite: "取消收藏" unfavorite: "取消收藏"
watch: "关注" watch: "关注"
unwatch: "取消关注" unwatch: "取消关注"
pin: "固定个人资料" pin: "置顶"
unpin: "解除固定" unpin: "取消置顶"
delete: "删除" delete: "删除"
delete-confirm: "确定删除这个投稿吗?" delete-confirm: "确定删除这个投稿吗?"
remote: "显示原始投稿" remote: "显示原始投稿"
@ -472,8 +490,12 @@ common/views/components/user-menu.vue:
mention: "提到" mention: "提到"
mute: "屏蔽" mute: "屏蔽"
unmute: "解除屏蔽" unmute: "解除屏蔽"
mute-confirm: "屏蔽此用户?"
unmute-confirm: "取消屏蔽用户?"
block: "拉黑" block: "拉黑"
unblock: "取消拉黑" unblock: "取消拉黑"
block-confirm: "确定拉黑此用户?"
unblock-confirm: "取消拉黑此用户?"
push-to-list: "添加至列表" push-to-list: "添加至列表"
select-list: "请选择一个列表" select-list: "请选择一个列表"
report-abuse: "举报骚扰" report-abuse: "举报骚扰"
@ -481,8 +503,12 @@ common/views/components/user-menu.vue:
report-abuse-reported: "已报告给管理员。 非常感谢你的合作。" report-abuse-reported: "已报告给管理员。 非常感谢你的合作。"
silence: "禁言" silence: "禁言"
unsilence: "解除禁言" unsilence: "解除禁言"
silence-confirm: "确认屏蔽此用户?"
unsilence-confirm: "取消屏蔽此用户?"
suspend: "冻结" suspend: "冻结"
unsuspend: "解除冻结" unsuspend: "解除冻结"
suspend-confirm: "确认冻结此用户?"
unsuspend-confirm: "确认解冻此用户?"
common/views/components/poll.vue: common/views/components/poll.vue:
vote-to: "为\"{}\"投票" vote-to: "为\"{}\"投票"
vote-count: "{}票" vote-count: "{}票"
@ -665,6 +691,9 @@ common/views/components/user-list-editor.vue:
remove-user: "从此列表中删除" remove-user: "从此列表中删除"
delete-are-you-sure: "删除列表“$1”" delete-are-you-sure: "删除列表“$1”"
deleted: "已删除" deleted: "已删除"
common/views/components/user-lists.vue:
create-list: "创建列表"
list-name: "列表名称"
common/views/widgets/broadcast.vue: common/views/widgets/broadcast.vue:
fetching: "确认中" fetching: "确认中"
no-broadcasts: "没有公告" no-broadcasts: "没有公告"
@ -707,7 +736,7 @@ common/views/widgets/tips.vue:
tips-line8: "可以从设置中定制主页。" tips-line8: "可以从设置中定制主页。"
tips-line9: "Misskey 根据 AGPLv3 获得许可。" tips-line9: "Misskey 根据 AGPLv3 获得许可。"
tips-line10: "使用Time Machine(时光机)小部件可以轻松追溯到过去的时间轴。" tips-line10: "使用Time Machine(时光机)小部件可以轻松追溯到过去的时间轴。"
tips-line11: "您可以点击“...”将帖子固定到用户页面" tips-line11: "您可以点击“...”将帖子置顶到用户页面"
tips-line13: "附在帖子上的所有文件都会保存到网盘中。" tips-line13: "附在帖子上的所有文件都会保存到网盘中。"
tips-line14: "在自定义首页布局时,您可以右键单击窗口小部件以更改其设计。" tips-line14: "在自定义首页布局时,您可以右键单击窗口小部件以更改其设计。"
tips-line17: "用“**”围绕文本将突出显示它。" tips-line17: "用“**”围绕文本将突出显示它。"
@ -742,7 +771,7 @@ desktop/views/components/activity.chart.vue:
total: "黑 ... 总计" total: "黑 ... 总计"
notes: "蓝 ... 投稿" notes: "蓝 ... 投稿"
replies: "红 ... 回复" replies: "红 ... 回复"
renotes: "绿 ... 转" renotes: "绿 ... 转"
desktop/views/components/activity.vue: desktop/views/components/activity.vue:
title: "活动" title: "活动"
toggle: "切换显示" toggle: "切换显示"
@ -839,12 +868,12 @@ desktop/views/components/note-detail.vue:
private: "私密投稿" private: "私密投稿"
deleted: "投稿已删除" deleted: "投稿已删除"
location: "位置信息" location: "位置信息"
renote: "转" renote: "转"
add-reaction: "回应" add-reaction: "回应"
undo-reaction: "取消回应" undo-reaction: "取消回应"
desktop/views/components/note.vue: desktop/views/components/note.vue:
reply: "回复" reply: "回复"
renote: "Renote" renote: "转推"
add-reaction: "回应" add-reaction: "回应"
undo-reaction: "取消回应" undo-reaction: "取消回应"
detail: "详细信息" detail: "详细信息"
@ -863,13 +892,13 @@ desktop/views/components/post-form.vue:
quote-placeholder: "引用这个帖子…" quote-placeholder: "引用这个帖子…"
submit: "投稿" submit: "投稿"
reply: "回复" reply: "回复"
renote: "转" renote: "转"
posted: "已发送投稿!" posted: "已发送投稿!"
replied: "已回复!" replied: "已回复!"
reposted: "已转!" reposted: "已转!"
note-failed: "发帖失败" note-failed: "发帖失败"
reply-failed: "回复失败" reply-failed: "回复失败"
renote-failed: "转失败" renote-failed: "转失败"
posting: "发送中" posting: "发送中"
attach-media-from-local: "从设备中添加媒体文件" attach-media-from-local: "从设备中添加媒体文件"
attach-media-from-drive: "从网盘中添加媒体文件" attach-media-from-drive: "从网盘中添加媒体文件"
@ -894,13 +923,13 @@ desktop/views/components/progress-dialog.vue:
desktop/views/components/renote-form.vue: desktop/views/components/renote-form.vue:
quote: "引用..." quote: "引用..."
cancel: "取消" cancel: "取消"
renote: "重新发送" renote: "转推"
renote-home: "重新发送(首页)" renote-home: "转推(首页)"
reposting: "重新发送中..." reposting: "重新发送中..."
success: "已重新发送" success: "已转推"
failure: "重新发送失败" failure: "转推失败"
desktop/views/components/renote-form-window.vue: desktop/views/components/renote-form-window.vue:
title: "您是否要重新发送" title: "您是否要转推"
desktop/views/pages/user-following-or-followers.vue: desktop/views/pages/user-following-or-followers.vue:
following: "{user}的正在关注" following: "{user}的正在关注"
followers: "{user}的关注者" followers: "{user}的关注者"
@ -950,7 +979,7 @@ common/views/components/drive-settings.vue:
common/views/components/mute-and-block.vue: common/views/components/mute-and-block.vue:
mute-and-block: "屏蔽/拉黑" mute-and-block: "屏蔽/拉黑"
mute: "屏蔽" mute: "屏蔽"
block: "拉黑" block: "拉黑"
no-muted-users: "无屏蔽用户" no-muted-users: "无屏蔽用户"
no-blocked-users: "无拉黑的用户" no-blocked-users: "无拉黑的用户"
word-mute: "文字屏蔽" word-mute: "文字屏蔽"
@ -966,6 +995,7 @@ common/views/components/password-settings.vue:
changed: "密码已更改" changed: "密码已更改"
failed: "更改密码失败" failed: "更改密码失败"
common/views/components/post-form-attaches.vue: common/views/components/post-form-attaches.vue:
attach-cancel: "删除附件"
mark-as-sensitive: "标记为“敏感”" mark-as-sensitive: "标记为“敏感”"
unmark-as-sensitive: "取消标记为“敏感”" unmark-as-sensitive: "取消标记为“敏感”"
desktop/views/components/sub-note-content.vue: desktop/views/components/sub-note-content.vue:
@ -1012,8 +1042,6 @@ desktop/views/components/received-follow-requests-window.vue:
reject: "拒绝" reject: "拒绝"
desktop/views/components/user-lists-window.vue: desktop/views/components/user-lists-window.vue:
title: "用户列表" title: "用户列表"
create-list: "创建列表"
list-name: "列表名称"
desktop/views/components/user-preview.vue: desktop/views/components/user-preview.vue:
notes: "帖子" notes: "帖子"
following: "关注中" following: "关注中"
@ -1191,7 +1219,9 @@ admin/views/users.vue:
unsuspend-confirm: "是否解除冻结?" unsuspend-confirm: "是否解除冻结?"
unsuspended: "已成功解除用户冻结" unsuspended: "已成功解除用户冻结"
make-silence: "禁言" make-silence: "禁言"
silence-confirm: "确认屏蔽?"
unmake-silence: "解除禁言" unmake-silence: "解除禁言"
unsilence-confirm: "解除屏蔽?"
verify: "认证用户" verify: "认证用户"
verify-confirm: "是否官方账号?" verify-confirm: "是否官方账号?"
verified: "此账户已被认证" verified: "此账户已被认证"
@ -1385,6 +1415,7 @@ desktop/views/widgets/polls.vue:
desktop/views/widgets/post-form.vue: desktop/views/widgets/post-form.vue:
title: "帖子" title: "帖子"
note: "帖子" note: "帖子"
something-happened: "由于某种原因无法发帖。"
desktop/views/widgets/profile.vue: desktop/views/widgets/profile.vue:
update-banner: "点击来剪辑背景" update-banner: "点击来剪辑背景"
update-avatar: "点击来剪辑头像" update-avatar: "点击来剪辑头像"
@ -1403,12 +1434,11 @@ mobile/views/components/drive.vue:
file-count: "文件" file-count: "文件"
nothing-in-drive: "网盘为空" nothing-in-drive: "网盘为空"
folder-is-empty: "这文件夹是空的" folder-is-empty: "这文件夹是空的"
prompt: "您想要干什么呢?(请输入数字):<1 → 上传文件 | 2 → 从URL上传文件 | 3 → 创建新文件夹 | 4 → 更改这个文件夹的名称 | 5 → 移动这个文件夹 | 6 → 删除这个文件夹>"
deletion-alert: "抱歉! 删除文件夹功能尚未实现。"
folder-name: "文件夹名称" folder-name: "文件夹名称"
here-is-root: "当前位置为根目录。" here-is-root: "当前位置为根目录。"
url-prompt: "要上传的文件的URL" url-prompt: "要上传的文件的URL"
uploading: "已请求上传。 上传完成可能需要一段时间。" uploading: "已请求上传。 上传完成可能需要一段时间。"
folder-name-cannot-empty: "文件夹名不能为空。"
mobile/views/components/drive-file-chooser.vue: mobile/views/components/drive-file-chooser.vue:
select-file: "选择文件" select-file: "选择文件"
mobile/views/components/drive-folder-chooser.vue: mobile/views/components/drive-folder-chooser.vue:
@ -1457,11 +1487,11 @@ mobile/views/components/post-form.vue:
add-visible-user: "添加用户" add-visible-user: "添加用户"
submit: "帖子" submit: "帖子"
reply: "回复" reply: "回复"
renote: "Renote" renote: "转推"
quote-placeholder: "引用这个帖子t... (可选)" quote-placeholder: "引用这个帖子t... (可选)"
reply-placeholder: "回复这个帖子" reply-placeholder: "回复这个帖子"
cw-placeholder: "评论帖子(可选)" cw-placeholder: "评论帖子(可选)"
location-alert: "您的设备不提供位服务" geolocation-alert: "您的设备不提供位服务"
error: "错误" error: "错误"
username-prompt: "请输入用户名" username-prompt: "请输入用户名"
mobile/views/components/sub-note-content.vue: mobile/views/components/sub-note-content.vue:
@ -1482,9 +1512,16 @@ mobile/views/components/ui.nav.vue:
game: "游戏" game: "游戏"
admin: "管理" admin: "管理"
about: "关于 Misskey" about: "关于 Misskey"
mobile/views/pages/drive.vue:
contextmenu:
upload: "上传文件"
url-upload: "从URL上传文件"
create-folder: "创建文件夹"
rename-folder: "重命名文件夹"
move-folder: "移动此文件夹"
delete-folder: "删除此文件夹"
mobile/views/pages/user-lists.vue: mobile/views/pages/user-lists.vue:
title: "列表" title: "列表"
enter-list-name: "输入要列出的列表的名称"
mobile/views/pages/signup.vue: mobile/views/pages/signup.vue:
lets-start: "您的账户现已准备就绪! 📦" lets-start: "您的账户现已准备就绪! 📦"
mobile/views/pages/followers.vue: mobile/views/pages/followers.vue:
@ -1581,7 +1618,7 @@ deck/deck.user-column.vue:
images: "图片" images: "图片"
activity: "活动" activity: "活动"
timeline: "时间线" timeline: "时间线"
pinned-notes: "标记投稿" pinned-notes: "置顶帖"
docs: docs:
edit-this-page-on-github: "发现错误或想要为文档做出贡献?" edit-this-page-on-github: "发现错误或想要为文档做出贡献?"
edit-this-page-on-github-link: "在GitHub上编辑这个页面。" edit-this-page-on-github-link: "在GitHub上编辑这个页面。"
@ -1611,14 +1648,17 @@ dev/views/apps.vue:
create-app: "创建应用" create-app: "创建应用"
app-missing: "没有应用" app-missing: "没有应用"
dev/views/new-app.vue: dev/views/new-app.vue:
new-app: "新应用"
new-app-info: "可以从 API 中创建应用。 (app/create)"
create-app: "正在创建应用" create-app: "正在创建应用"
app-name: "应用名称" app-name: "应用名称"
app-name-placeholder: "ex) iOS版Misskey"
app-name-desc: "您应用的名称" app-name-desc: "您应用的名称"
app-name-ex: "ex) iOS版本的Misskey"
app-overview: "应用摘要" app-overview: "应用摘要"
app-desc: "您的应用的简要说明或介绍。" app-overview-placeholder: " ex) iOS版Misskey客户端."
app-desc-ex: "ex) iOS版Misskey客户端." app-overview-desc: "您的应用的简要说明或介绍。"
callback-url: "回应URL (optional)" callback-url: "回应URL (optional)"
callback-url-placeholder: "ex) https://your.app.example.com/callback.php"
callback-url-desc: "通过身份验证表单对用户进行身份验证后重定向到的URL。" callback-url-desc: "通过身份验证表单对用户进行身份验证后重定向到的URL。"
authority: "权限" authority: "权限"
authority-desc: "只能通过API访问此处请求的功能。" authority-desc: "只能通过API访问此处请求的功能。"

View File

@ -1,7 +1,7 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "11.1.4", "version": "11.4.0",
"codename": "daybreak", "codename": "daybreak",
"repository": { "repository": {
"type": "git", "type": "git",
@ -23,20 +23,19 @@
"format": "gulp format" "format": "gulp format"
}, },
"dependencies": { "dependencies": {
"@elastic/elasticsearch": "7.0.0-rc.2",
"@fortawesome/fontawesome-svg-core": "1.2.15", "@fortawesome/fontawesome-svg-core": "1.2.15",
"@fortawesome/free-brands-svg-icons": "5.7.2", "@fortawesome/free-brands-svg-icons": "5.7.2",
"@fortawesome/free-regular-svg-icons": "5.7.2", "@fortawesome/free-regular-svg-icons": "5.7.2",
"@fortawesome/free-solid-svg-icons": "5.7.2", "@fortawesome/free-solid-svg-icons": "5.7.2",
"@fortawesome/vue-fontawesome": "0.1.5", "@fortawesome/vue-fontawesome": "0.1.5",
"@koa/cors": "2.2.3", "@koa/cors": "2.2.3",
"@prezzemolo/rap": "0.1.2",
"@prezzemolo/zip": "0.0.3", "@prezzemolo/zip": "0.0.3",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/bull": "3.5.11", "@types/bull": "3.5.11",
"@types/dateformat": "3.0.0", "@types/dateformat": "3.0.0",
"@types/deep-equal": "1.0.1", "@types/deep-equal": "1.0.1",
"@types/double-ended-queue": "2.1.0", "@types/double-ended-queue": "2.1.0",
"@types/elasticsearch": "5.0.32",
"@types/file-type": "10.9.1", "@types/file-type": "10.9.1",
"@types/gulp": "4.0.6", "@types/gulp": "4.0.6",
"@types/gulp-mocha": "0.0.32", "@types/gulp-mocha": "0.0.32",
@ -94,7 +93,7 @@
"@types/websocket": "0.0.40", "@types/websocket": "0.0.40",
"@types/ws": "6.0.1", "@types/ws": "6.0.1",
"animejs": "3.0.1", "animejs": "3.0.1",
"apexcharts": "3.6.7", "apexcharts": "3.6.8",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"autosize": "4.0.2", "autosize": "4.0.2",
"autwh": "0.1.0", "autwh": "0.1.0",
@ -112,9 +111,8 @@
"cssnano": "4.1.10", "cssnano": "4.1.10",
"dateformat": "3.0.3", "dateformat": "3.0.3",
"deep-equal": "1.0.1", "deep-equal": "1.0.1",
"diskusage": "1.0.0", "diskusage": "1.1.0",
"double-ended-queue": "2.1.0-0", "double-ended-queue": "2.1.0-0",
"elasticsearch": "15.4.1",
"emojilib": "2.4.0", "emojilib": "2.4.0",
"eslint": "5.16.0", "eslint": "5.16.0",
"eslint-plugin-vue": "5.2.2", "eslint-plugin-vue": "5.2.2",
@ -138,10 +136,10 @@
"html-minifier": "4.0.0", "html-minifier": "4.0.0",
"http-signature": "1.2.0", "http-signature": "1.2.0",
"insert-text-at-cursor": "0.2.0", "insert-text-at-cursor": "0.2.0",
"is-root": "2.0.0", "is-root": "2.1.0",
"is-svg": "4.1.0", "is-svg": "4.1.0",
"js-yaml": "3.13.1", "js-yaml": "3.13.1",
"jsdom": "14.0.0", "jsdom": "14.1.0",
"json5": "2.1.0", "json5": "2.1.0",
"json5-loader": "2.0.0", "json5-loader": "2.0.0",
"katex": "0.10.1", "katex": "0.10.1",
@ -161,20 +159,20 @@
"loader-utils": "1.2.3", "loader-utils": "1.2.3",
"lolex": "3.1.0", "lolex": "3.1.0",
"lookup-dns-cache": "2.1.0", "lookup-dns-cache": "2.1.0",
"minio": "7.0.5", "minio": "7.0.6",
"mocha": "6.1.3", "mocha": "6.1.3",
"moji": "0.5.1", "moji": "0.5.1",
"moment": "2.24.0", "moment": "2.24.0",
"ms": "2.1.1", "ms": "2.1.1",
"nested-property": "0.0.7", "nested-property": "0.0.7",
"node-fetch": "2.3.0", "node-fetch": "2.3.0",
"nodemailer": "6.1.0", "nodemailer": "6.1.1",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"object-assign-deep": "0.4.0", "object-assign-deep": "0.4.0",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"parse5": "5.1.0", "parse5": "5.1.0",
"parsimmon": "1.12.0", "parsimmon": "1.12.0",
"pg": "7.9.0", "pg": "7.10.0",
"portscanner": "2.2.0", "portscanner": "2.2.0",
"postcss-loader": "3.0.0", "postcss-loader": "3.0.0",
"prismjs": "1.16.0", "prismjs": "1.16.0",
@ -210,7 +208,7 @@
"stylus": "0.54.5", "stylus": "0.54.5",
"stylus-loader": "3.0.2", "stylus-loader": "3.0.2",
"summaly": "2.2.0", "summaly": "2.2.0",
"systeminformation": "4.1.4", "systeminformation": "4.1.5",
"syuilo-password-strength": "0.0.1", "syuilo-password-strength": "0.0.1",
"terser-webpack-plugin": "1.2.3", "terser-webpack-plugin": "1.2.3",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
@ -240,7 +238,7 @@
"vue-loader": "15.7.0", "vue-loader": "15.7.0",
"vue-marquee-text-component": "1.1.1", "vue-marquee-text-component": "1.1.1",
"vue-prism-component": "1.1.1", "vue-prism-component": "1.1.1",
"vue-router": "3.0.4", "vue-router": "3.0.6",
"vue-sequential-entrance": "1.1.3", "vue-sequential-entrance": "1.1.3",
"vue-style-loader": "4.1.2", "vue-style-loader": "4.1.2",
"vue-svg-inline-loader": "1.2.15", "vue-svg-inline-loader": "1.2.15",

View File

@ -27,7 +27,7 @@ function greet() {
console.log(' ' + chalk.gray(v) + (' |___|\n'.substr(v.length))); console.log(' ' + chalk.gray(v) + (' |___|\n'.substr(v.length)));
//#endregion //#endregion
console.log(' Misskey is maintained by @syuilo, @AyaMorisawa, @mei23, and @acid-chicken.'); console.log(' Misskey is maintained by @syuilo, @AyaMorisawa, @mei23, @acid-chicken, and @rinsuki.');
console.log(chalk.keyword('orange')(' If you like Misskey, please donate to support development. https://www.patreon.com/syuilo')); console.log(chalk.keyword('orange')(' If you like Misskey, please donate to support development. https://www.patreon.com/syuilo'));
console.log(''); console.log('');

View File

@ -9,6 +9,9 @@
<ui-textarea v-model="announcement.text"> <ui-textarea v-model="announcement.text">
<span>{{ $t('text') }}</span> <span>{{ $t('text') }}</span>
</ui-textarea> </ui-textarea>
<ui-input v-model="announcement.image">
<span>{{ $t('image-url') }}</span>
</ui-input>
<ui-horizon-group class="fit-bottom"> <ui-horizon-group class="fit-bottom">
<ui-button @click="save()"><fa :icon="['far', 'save']"/> {{ $t('save') }}</ui-button> <ui-button @click="save()"><fa :icon="['far', 'save']"/> {{ $t('save') }}</ui-button>
<ui-button @click="remove(i)"><fa :icon="['far', 'trash-alt']"/> {{ $t('remove') }}</ui-button> <ui-button @click="remove(i)"><fa :icon="['far', 'trash-alt']"/> {{ $t('remove') }}</ui-button>
@ -43,7 +46,8 @@ export default Vue.extend({
add() { add() {
this.announcements.unshift({ this.announcements.unshift({
title: '', title: '',
text: '' text: '',
image: null
}); });
}, },

View File

@ -232,6 +232,8 @@ export default Vue.extend({
}, },
async silenceUser() { async silenceUser() {
if (!await this.getConfirmed(this.$t('silence-confirm'))) return;
const process = async () => { const process = async () => {
await this.$root.api('admin/silence-user', { userId: this.user.id }); await this.$root.api('admin/silence-user', { userId: this.user.id });
this.$root.dialog({ this.$root.dialog({
@ -251,6 +253,8 @@ export default Vue.extend({
}, },
async unsilenceUser() { async unsilenceUser() {
if (!await this.getConfirmed(this.$t('unsilence-confirm'))) return;
const process = async () => { const process = async () => {
await this.$root.api('admin/unsilence-user', { userId: this.user.id }); await this.$root.api('admin/unsilence-user', { userId: this.user.id });
this.$root.dialog({ this.$root.dialog({

View File

@ -0,0 +1,31 @@
import parseAcct from '../../../../misc/acct/parse';
import { host as localHost } from '../../config';
export async function genSearchQuery(v: any, q: string) {
let host: string;
let userId: string;
if (q.split(' ').some(x => x.startsWith('@'))) {
for (const at of q.split(' ').filter(x => x.startsWith('@')).map(x => x.substr(1))) {
if (at.includes('.')) {
if (at === localHost || at === '.') {
host = null;
} else {
host = at;
}
} else {
const user = await v.$root.api('users/show', parseAcct(at)).catch(x => null);
if (user) {
userId = user.id;
} else {
// todo: show error
}
}
}
}
return {
query: q.split(' ').filter(x => !x.startsWith('/') && !x.startsWith('@')).join(' '),
host: host,
userId: userId
};
}

View File

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

View File

@ -0,0 +1,64 @@
import { faHistory } from '@fortawesome/free-solid-svg-icons';
export async function search(v: any, q: string) {
q = q.trim();
if (q.startsWith('@') && !q.includes(' ')) {
v.$router.push(`/${q}`);
return;
}
if (q.startsWith('#')) {
v.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
return;
}
// like 2018/03/12
if (/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}/.test(q.replace(/-/g, '/'))) {
const date = new Date(q.replace(/-/g, '/'));
// 日付しか指定されてない場合、例えば 2018/03/12 ならユーザーは
// 2018/03/12 のコンテンツを「含む」結果になることを期待するはずなので
// 23時間59分進める(そのままだと 2018/03/12 00:00:00 「まで」の
// 結果になってしまい、2018/03/12 のコンテンツは含まれない)
if (q.replace(/-/g, '/').match(/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}$/)) {
date.setHours(23, 59, 59, 999);
}
v.$root.$emit('warp', date);
v.$root.dialog({
icon: faHistory,
splash: true,
});
return;
}
if (q.startsWith('https://')) {
const dialog = v.$root.dialog({
type: 'waiting',
text: v.$t('@.fetching-as-ap-object'),
showOkButton: false,
showCancelButton: false,
cancelableByBgClick: false
});
try {
const res = await v.$root.api('ap/show', {
uri: q
});
dialog.close();
if (res.type == 'User') {
v.$router.push(`/@${res.object.username}@${res.object.host}`);
} else if (res.type == 'Note') {
v.$router.push(`/notes/${res.object.id}`);
}
} catch (e) {
dialog.close();
// TODO: Show error
}
return;
}
v.$router.push(`/search?q=${encodeURIComponent(q)}`);
}

View File

@ -78,6 +78,7 @@ export default Vue.extend({
.mk-avatar .mk-avatar
display inline-block display inline-block
vertical-align bottom vertical-align bottom
flex-shrink 0
&:not(.cat) &:not(.cat)
overflow hidden overflow hidden

View File

@ -6,7 +6,17 @@
<mk-signin/> <mk-signin/>
</template> </template>
<template v-else> <template v-else>
<div class="icon" v-if="!input && !select && !user" :class="type"><fa :icon="icon"/></div> <div class="icon" v-if="icon">
<fa :icon="icon"/>
</div>
<div class="icon" v-else-if="!input && !select && !user" :class="type">
<fa icon="check" v-if="type === 'success'"/>
<fa :icon="faTimesCircle" v-if="type === 'error'"/>
<fa icon="exclamation-triangle" v-if="type === 'warning'"/>
<fa icon="info-circle" v-if="type === 'info'"/>
<fa :icon="faQuestionCircle" v-if="type === 'question'"/>
<fa icon="spinner" pulse v-if="type === 'waiting'"/>
</div>
<header v-if="title" v-html="title"></header> <header v-if="title" v-html="title"></header>
<div class="body" v-if="text" v-html="text"></div> <div class="body" v-if="text" v-html="text"></div>
<ui-input v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></ui-input> <ui-input v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></ui-input>
@ -14,8 +24,8 @@
<ui-select v-if="select" v-model="selectedValue" autofocus> <ui-select v-if="select" v-model="selectedValue" autofocus>
<option v-for="item in select.items" :value="item.value">{{ item.text }}</option> <option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
</ui-select> </ui-select>
<ui-horizon-group no-grow class="buttons fit-bottom" v-if="!splash"> <ui-horizon-group no-grow class="buttons fit-bottom" v-if="!splash && (showOkButton || showCancelButton)">
<ui-button @click="ok" primary :autofocus="!input && !select && !user">{{ (showCancelButton || input || select || user) ? $t('@.ok') : $t('@.got-it') }}</ui-button> <ui-button @click="ok" v-if="showOkButton" primary :autofocus="!input && !select && !user">{{ (showCancelButton || input || select || user) ? $t('@.ok') : $t('@.got-it') }}</ui-button>
<ui-button @click="cancel" v-if="showCancelButton || input || select || user">{{ $t('@.cancel') }}</ui-button> <ui-button @click="cancel" v-if="showCancelButton || input || select || user">{{ $t('@.cancel') }}</ui-button>
</ui-horizon-group> </ui-horizon-group>
</template> </template>
@ -55,10 +65,21 @@ export default Vue.extend({
user: { user: {
required: false required: false
}, },
icon: {
required: false
},
showOkButton: {
type: Boolean,
default: true
},
showCancelButton: { showCancelButton: {
type: Boolean, type: Boolean,
default: false default: false
}, },
cancelableByBgClick: {
type: Boolean,
default: true
},
splash: { splash: {
type: Boolean, type: Boolean,
default: false default: false
@ -69,22 +90,11 @@ export default Vue.extend({
return { return {
inputValue: this.input && this.input.default ? this.input.default : null, inputValue: this.input && this.input.default ? this.input.default : null,
userInputValue: null, userInputValue: null,
selectedValue: null selectedValue: null,
faTimesCircle, faQuestionCircle
}; };
}, },
computed: {
icon(): any {
switch (this.type) {
case 'success': return 'check';
case 'error': return faTimesCircle;
case 'warning': return 'exclamation-triangle';
case 'info': return 'info-circle';
case 'question': return faQuestionCircle;
}
}
},
mounted() { mounted() {
this.$nextTick(() => { this.$nextTick(() => {
(this.$refs.bg as any).style.pointerEvents = 'auto'; (this.$refs.bg as any).style.pointerEvents = 'auto';
@ -113,6 +123,8 @@ export default Vue.extend({
methods: { methods: {
async ok() { async ok() {
if (!this.showOkButton) return;
if (this.user) { if (this.user) {
const user = await this.$root.api('users/show', parseAcct(this.userInputValue)); const user = await this.$root.api('users/show', parseAcct(this.userInputValue));
if (user) { if (user) {
@ -156,7 +168,9 @@ export default Vue.extend({
}, },
onBgClick() { onBgClick() {
this.cancel(); if (this.cancelableByBgClick) {
this.cancel();
}
}, },
onInputKeydown(e) { onInputKeydown(e) {
@ -183,9 +197,6 @@ export default Vue.extend({
height 100% height 100%
&.splash &.splash
&, *
pointer-events none !important
> .main > .main
min-width 0 min-width 0
width initial width initial
@ -243,7 +254,7 @@ export default Vue.extend({
margin-top 8px margin-top 8px
> .body > .body
margin 16px 0 margin 16px 0 0 0
> .buttons > .buttons
margin-top 16px margin-top 16px

View File

@ -121,7 +121,7 @@ export default Vue.extend({
if (this.file.properties.avgColor) { if (this.file.properties.avgColor) {
anime({ anime({
targets: this.$refs.thumbnail, targets: this.$refs.thumbnail,
backgroundColor: this.file.properties.avgColor.replace('255)', '0)'), backgroundColor: 'transparent', // TODO fade
duration: 100, duration: 100,
easing: 'linear' easing: 'linear'
}); });

View File

@ -6,7 +6,7 @@
<div class="body"> <div class="body">
<p class="init" v-if="init"><fa icon="spinner .spin"/>{{ $t('@.loading') }}</p> <p class="init" v-if="init"><fa icon="spinner .spin"/>{{ $t('@.loading') }}</p>
<p class="empty" v-if="!init && messages.length == 0"><fa icon="info-circle"/>{{ $t('empty') }}</p> <p class="empty" v-if="!init && messages.length == 0"><fa icon="info-circle"/>{{ $t('empty') }}</p>
<p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages"><fa icon="flag"/>{{ $t('no-history') }}</p> <p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages"><fa :icon="faFlag"/>{{ $t('no-history') }}</p>
<button class="more" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages"> <button class="more" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages">
<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }} <template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }}
</button> </button>
@ -35,6 +35,7 @@ import XMessage from './messaging-room.message.vue';
import XForm from './messaging-room.form.vue'; import XForm from './messaging-room.form.vue';
import { url } from '../../../config'; import { url } from '../../../config';
import { faArrowCircleDown } from '@fortawesome/free-solid-svg-icons'; import { faArrowCircleDown } from '@fortawesome/free-solid-svg-icons';
import { faFlag } from '@fortawesome/free-regular-svg-icons';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('common/views/components/messaging-room.vue'), i18n: i18n('common/views/components/messaging-room.vue'),
@ -54,7 +55,7 @@ export default Vue.extend({
connection: null, connection: null,
showIndicator: false, showIndicator: false,
timer: null, timer: null,
faArrowCircleDown faArrowCircleDown, faFlag
}; };
}, },

View File

@ -32,7 +32,7 @@ export default Vue.extend({
props: { props: {
files: { files: {
type: Object, type: Array,
required: true required: true
}, },
detachMediaFn: { detachMediaFn: {

View File

@ -14,7 +14,7 @@
<section> <section>
<header><fa icon="terminal"/> {{ $t('console.title') }}</header> <header><fa icon="terminal"/> {{ $t('console.title') }}</header>
<ui-input v-model="endpoint"> <ui-input v-model="endpoint" :datalist="endpoints">
<span>{{ $t('console.endpoint') }}</span> <span>{{ $t('console.endpoint') }}</span>
</ui-input> </ui-input>
<ui-textarea v-model="body"> <ui-textarea v-model="body">
@ -39,15 +39,23 @@ import * as JSON5 from 'json5';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('common/views/components/api-settings.vue'), i18n: i18n('common/views/components/api-settings.vue'),
data() { data() {
return { return {
endpoint: '', endpoint: '',
body: '{}', body: '{}',
res: null, res: null,
sending: false sending: false,
endpoints: []
}; };
}, },
created() {
this.$root.api('endpoints').then(endpoints => {
this.endpoints = endpoints;
});
},
methods: { methods: {
regenerateToken() { regenerateToken() {
this.$root.dialog({ this.$root.dialog({

View File

@ -525,15 +525,11 @@ export default Vue.extend({
this.$chooseDriveFile({ this.$chooseDriveFile({
multiple: false multiple: false
}).then(file => { }).then(file => {
this.$root.api('i/update', { this.$store.dispatch('settings/set', { key: 'wallpaper', value: file.url });
wallpaperId: file.id
});
}); });
}, },
deleteWallpaper() { deleteWallpaper() {
this.$root.api('i/update', { this.$store.dispatch('settings/set', { key: 'wallpaper', value: null });
wallpaperId: null
});
}, },
checkForUpdate() { checkForUpdate() {
this.checkingForUpdate = true; this.checkingForUpdate = true;

View File

@ -23,6 +23,7 @@
@focus="focused = true" @focus="focused = true"
@blur="focused = false" @blur="focused = false"
@keydown="$emit('keydown', $event)" @keydown="$emit('keydown', $event)"
:list="id"
> >
<input v-else ref="input" <input v-else ref="input"
:type="type" :type="type"
@ -37,7 +38,11 @@
@focus="focused = true" @focus="focused = true"
@blur="focused = false" @blur="focused = false"
@keydown="$emit('keydown', $event)" @keydown="$emit('keydown', $event)"
:list="id"
> >
<datalist :id="id" v-if="datalist">
<option v-for="data in datalist" :value="data"/>
</datalist>
</template> </template>
<template v-else> <template v-else>
<input ref="input" <input ref="input"
@ -130,6 +135,10 @@ export default Vue.extend({
required: false, required: false,
default: false default: false
}, },
datalist: {
type: Array,
required: false,
},
inline: { inline: {
type: Boolean, type: Boolean,
required: false, required: false,
@ -147,7 +156,8 @@ export default Vue.extend({
return { return {
v: this.value, v: this.value,
focused: false, focused: false,
passwordStrength: '' passwordStrength: '',
id: Math.random().toString()
}; };
}, },
computed: { computed: {

View File

@ -38,10 +38,10 @@ export default Vue.extend({
const data = new FormData(); const data = new FormData();
data.append('md5', getMD5(fileData)); data.append('md5', getMD5(fileData));
this.$root.api('drive/files/check_existence', { this.$root.api('drive/files/find-by-hash', {
md5: getMD5(fileData) md5: getMD5(fileData)
}).then(resp => { }).then(resp => {
resolve(resp.file); resolve(resp.length > 0 ? resp[0] : null);
}); });
}); });
}, },

View File

@ -77,11 +77,11 @@ export default Vue.extend({
input: { input: {
default: this.list.name default: this.list.name
} }
}).then(({ canceled, result: title }) => { }).then(({ canceled, result: name }) => {
if (canceled) return; if (canceled) return;
this.$root.api('users/lists/update', { this.$root.api('users/lists/update', {
listId: this.list.id, listId: this.list.id,
title: title name: name
}); });
}); });
}, },

View File

@ -60,9 +60,9 @@ export default Vue.extend({
}, },
methods: { methods: {
init() { async init() {
this.fetching = true; this.fetching = true;
this.makePromise().then(x => { await (this.makePromise()).then(x => {
if (Array.isArray(x)) { if (Array.isArray(x)) {
this.us = x; this.us = x;
} else { } else {
@ -76,9 +76,9 @@ export default Vue.extend({
}); });
}, },
fetchMoreUsers() { async fetchMoreUsers() {
this.fetchingMoreUsers = true; this.fetchingMoreUsers = true;
this.makePromise(this.cursor).then(x => { await (this.makePromise(this.cursor)).then(x => {
this.us = this.us.concat(x.users); this.us = this.us.concat(x.users);
this.cursor = x.cursor; this.cursor = x.cursor;
this.fetchingMoreUsers = false; this.fetchingMoreUsers = false;

View File

@ -0,0 +1,95 @@
<template>
<div class="xkxvokkjlptzyewouewmceqcxhpgzprp">
<button class="ui" @click="add">{{ $t('create-list') }}</button>
<a v-for="list in lists" :key="list.id" @click="choice(list)">{{ list.name }}</a>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
export default Vue.extend({
i18n: i18n('common/views/components/user-lists.vue'),
data() {
return {
fetching: true,
lists: []
};
},
mounted() {
this.$root.api('users/lists/list').then(lists => {
this.fetching = false;
this.lists = lists;
});
},
methods: {
add() {
this.$root.dialog({
title: this.$t('list-name'),
input: true
}).then(async ({ canceled, result: name }) => {
if (canceled) return;
const list = await this.$root.api('users/lists/create', {
name
});
this.lists.push(list)
this.$emit('choosen', list);
});
},
choice(list) {
this.$emit('choosen', list);
}
}
});
</script>
<style lang="stylus" scoped>
.xkxvokkjlptzyewouewmceqcxhpgzprp
padding 16px
background: var(--bg)
> button
display block
margin-bottom 16px
color var(--primaryForeground)
background var(--primary)
width 100%
border-radius 38px
user-select none
cursor pointer
padding 0 16px
min-width 100px
line-height 38px
font-size 14px
font-weight 700
&:hover
background var(--primaryLighten10)
&:active
background var(--primaryDarken10)
a
display block
margin 8px 0
padding 8px
color var(--text)
background var(--face)
box-shadow 0 2px 16px var(--reversiListItemShadow)
border-radius 6px
cursor pointer
line-height 32px
*
pointer-events none
user-select none
&:hover
box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.05)
&:active
box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.1)
</style>

View File

@ -89,8 +89,10 @@ export default Vue.extend({
}); });
}, },
toggleMute() { async toggleMute() {
if (this.user.isMuted) { if (this.user.isMuted) {
if (!await this.getConfirmed(this.$t('unmute-confirm'))) return;
this.$root.api('mute/delete', { this.$root.api('mute/delete', {
userId: this.user.id userId: this.user.id
}).then(() => { }).then(() => {
@ -102,6 +104,8 @@ export default Vue.extend({
}); });
}); });
} else { } else {
if (!await this.getConfirmed(this.$t('mute-confirm'))) return;
this.$root.api('mute/create', { this.$root.api('mute/create', {
userId: this.user.id userId: this.user.id
}).then(() => { }).then(() => {
@ -115,8 +119,10 @@ export default Vue.extend({
} }
}, },
toggleBlock() { async toggleBlock() {
if (this.user.isBlocking) { if (this.user.isBlocking) {
if (!await this.getConfirmed(this.$t('unblock-confirm'))) return;
this.$root.api('blocking/delete', { this.$root.api('blocking/delete', {
userId: this.user.id userId: this.user.id
}).then(() => { }).then(() => {
@ -128,6 +134,8 @@ export default Vue.extend({
}); });
}); });
} else { } else {
if (!await this.getConfirmed(this.$t('block-confirm'))) return;
this.$root.api('blocking/create', { this.$root.api('blocking/create', {
userId: this.user.id userId: this.user.id
}).then(() => { }).then(() => {
@ -164,7 +172,9 @@ export default Vue.extend({
}); });
}, },
toggleSilence() { async toggleSilence() {
if (!await this.getConfirmed(this.$t(this.user.isSilenced ? 'unsilence-confirm' : 'silence-confirm'))) return;
this.$root.api(this.user.isSilenced ? 'admin/unsilence-user' : 'admin/silence-user', { this.$root.api(this.user.isSilenced ? 'admin/unsilence-user' : 'admin/silence-user', {
userId: this.user.id userId: this.user.id
}).then(() => { }).then(() => {
@ -181,7 +191,9 @@ export default Vue.extend({
}); });
}, },
toggleSuspend() { async toggleSuspend() {
if (!await this.getConfirmed(this.$t(this.user.isSuspended ? 'unsuspend-confirm' : 'suspend-confirm'))) return;
this.$root.api(this.user.isSuspended ? 'admin/unsuspend-user' : 'admin/suspend-user', { this.$root.api(this.user.isSuspended ? 'admin/unsuspend-user' : 'admin/suspend-user', {
userId: this.user.id userId: this.user.id
}).then(() => { }).then(() => {
@ -196,7 +208,18 @@ export default Vue.extend({
text: e text: e
}); });
}); });
} },
async getConfirmed(text: string): Promise<Boolean> {
const confirm = await this.$root.dialog({
type: 'warning',
showCancelButton: true,
title: 'confirm',
text,
});
return !confirm.canceled;
},
} }
}); });
</script> </script>

View File

@ -110,11 +110,11 @@ export default Vue.extend({
this.init(); this.init();
}, },
init() { async init() {
this.queue = []; this.queue = [];
this.notes = []; this.notes = [];
this.fetching = true; this.fetching = true;
this.makePromise().then(x => { await (this.makePromise()).then(x => {
if (Array.isArray(x)) { if (Array.isArray(x)) {
this.notes = x; this.notes = x;
} else { } else {
@ -129,10 +129,10 @@ export default Vue.extend({
}); });
}, },
fetchMore() { async fetchMore() {
if (!this.more || this.moreFetching) return; if (!this.more || this.moreFetching) return;
this.moreFetching = true; this.moreFetching = true;
this.makePromise(this.notes[this.notes.length - 1].id).then(x => { await (this.makePromise(this.notes[this.notes.length - 1].id)).then(x => {
this.notes = this.notes.concat(x.notes); this.notes = this.notes.concat(x.notes);
this.more = x.more; this.more = x.more;
this.moreFetching = false; this.moreFetching = false;

View File

@ -14,6 +14,7 @@
import Vue from 'vue'; import Vue from 'vue';
import XColumn from './deck.column.vue'; import XColumn from './deck.column.vue';
import XNotes from './deck.notes.vue'; import XNotes from './deck.notes.vue';
import { genSearchQuery } from '../../../common/scripts/gen-search-query';
const limit = 20; const limit = 20;
@ -25,10 +26,10 @@ export default Vue.extend({
data() { data() {
return { return {
makePromise: cursor => this.$root.api('notes/search', { makePromise: async cursor => this.$root.api('notes/search', {
limit: limit + 1, limit: limit + 1,
offset: cursor ? cursor : undefined, offset: cursor ? cursor : undefined,
query: this.q ...(await genSearchQuery(this, this.q))
}).then(notes => { }).then(notes => {
if (notes.length == limit + 1) { if (notes.length == limit + 1) {
notes.pop(); notes.pop();

View File

@ -18,7 +18,8 @@
<p class="fetching" v-if="fetching">{{ $t('fetching') }}<mk-ellipsis/></p> <p class="fetching" v-if="fetching">{{ $t('fetching') }}<mk-ellipsis/></p>
<h1 v-if="!fetching">{{ announcements.length == 0 ? $t('no-broadcasts') : announcements[i].title }}</h1> <h1 v-if="!fetching">{{ announcements.length == 0 ? $t('no-broadcasts') : announcements[i].title }}</h1>
<p v-if="!fetching"> <p v-if="!fetching">
<span v-if="announcements.length != 0" v-html="announcements[i].text"></span> <mfm v-if="announcements.length != 0" :text="announcements[i].text" :key="i"/>
<img v-if="announcements.length != 0 && announcements[i].image" :src="announcements[i].image" alt="" style="display: block; max-height: 130px; max-width: 100%;"/>
<template v-if="announcements.length == 0">{{ $t('have-a-nice-day') }}</template> <template v-if="announcements.length == 0">{{ $t('have-a-nice-day') }}</template>
</p> </p>
<a v-if="announcements.length > 1" @click="next">{{ $t('next') }} &gt;&gt;</a> <a v-if="announcements.length > 1" @click="next">{{ $t('next') }} &gt;&gt;</a>

View File

@ -21,7 +21,7 @@
<fa :icon="['far', 'laugh']"/> <fa :icon="['far', 'laugh']"/>
</button> </button>
</div> </div>
<x-post-form-attaches class="files" :files="files" :detachMediaFn="detachMedia"/> <x-post-form-attaches class="files" :files="files" :detach-media-fn="detachMedia"/>
<input ref="file" type="file" multiple="multiple" tabindex="-1" @change="onChangeFile"/> <input ref="file" type="file" multiple="multiple" tabindex="-1" @change="onChangeFile"/>
<mk-uploader ref="uploader" @uploaded="attachMedia"/> <mk-uploader ref="uploader" @uploaded="attachMedia"/>
<footer> <footer>

View File

@ -142,7 +142,7 @@ export default Vue.extend({
if (this.file.properties.avgColor) { if (this.file.properties.avgColor) {
anime({ anime({
targets: this.$refs.thumbnail, targets: this.$refs.thumbnail,
backgroundColor: this.file.properties.avgColor.replace('255)', '0)'), backgroundColor: 'transparent', // TODO fade
duration: 100, duration: 100,
easing: 'linear' easing: 'linear'
}); });

View File

@ -105,9 +105,9 @@ export default Vue.extend({
this.init(); this.init();
}, },
init() { async init() {
this.fetching = true; this.fetching = true;
this.makePromise().then(x => { await (this.makePromise()).then(x => {
if (Array.isArray(x)) { if (Array.isArray(x)) {
this.notes = x; this.notes = x;
} else { } else {
@ -122,8 +122,8 @@ export default Vue.extend({
}); });
}, },
fetchMore() { async fetchMore() {
if (!this.more || this.moreFetching) return; if (!this.more || this.moreFetching || this.notes.length === 0) return;
this.moreFetching = true; this.moreFetching = true;
this.makePromise(this.notes[this.notes.length - 1].id).then(x => { this.makePromise(this.notes[this.notes.length - 1].id).then(x => {
this.notes = this.notes.concat(x.notes); this.notes = this.notes.concat(x.notes);

View File

@ -228,7 +228,7 @@ export default Vue.extend({
const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[this.draftId]; const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[this.draftId];
if (draft) { if (draft) {
this.text = draft.data.text; this.text = draft.data.text;
this.files = draft.data.files; this.files = (draft.data.files || []).filter(e => e);
if (draft.data.poll) { if (draft.data.poll) {
this.poll = true; this.poll = true;
this.$nextTick(() => { this.$nextTick(() => {

View File

@ -87,6 +87,5 @@ export default Vue.extend({
height 100% height 100%
flex auto flex auto
overflow auto overflow auto
background var(--bg)
</style> </style>

View File

@ -90,9 +90,8 @@
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import MkUserListsWindow from './user-lists-window.vue'; import MkUserListsWindow from './user-lists-window.vue';
import MkUserListWindow from './user-list-window.vue';
import MkFollowRequestsWindow from './received-follow-requests-window.vue'; import MkFollowRequestsWindow from './received-follow-requests-window.vue';
import MkSettingsWindow from './settings-window.vue'; // import MkSettingsWindow from './settings-window.vue';
import MkDriveWindow from './drive-window.vue'; import MkDriveWindow from './drive-window.vue';
import contains from '../../../common/scripts/contains'; import contains from '../../../common/scripts/contains';
import { faHome, faColumns } from '@fortawesome/free-solid-svg-icons'; import { faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
@ -143,12 +142,7 @@ export default Vue.extend({
}, },
list() { list() {
this.close(); this.close();
const w = this.$root.new(MkUserListsWindow); this.$root.new(MkUserListsWindow);
w.$once('choosen', list => {
this.$root.new(MkUserListWindow, {
list
});
});
}, },
followRequests() { followRequests() {
this.close(); this.close();

View File

@ -9,6 +9,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import { search } from '../../../common/scripts/search';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('desktop/views/components/ui.header.search.vue'), i18n: i18n('desktop/views/components/ui.header.search.vue'),
@ -22,29 +23,11 @@ export default Vue.extend({
async onSubmit() { async onSubmit() {
if (this.wait) return; if (this.wait) return;
const q = this.q.trim(); this.wait = true;
if (q.startsWith('@')) { search(this, this.q).finally(() => {
this.$router.push(`/${q}`);
} else if (q.startsWith('#')) {
this.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
} else if (q.startsWith('https://')) {
this.wait = true;
try {
const res = await this.$root.api('ap/show', {
uri: q
});
if (res.type == 'User') {
this.$router.push(`/@${res.object.username}@${res.object.host}`);
} else if (res.type == 'Note') {
this.$router.push(`/notes/${res.object.id}`);
}
} catch (e) {
// TODO
}
this.wait = false; this.wait = false;
} else { this.q = '';
this.$router.push(`/search?q=${encodeURIComponent(q)}`); });
}
} }
} }
}); });

View File

@ -148,10 +148,7 @@ export default Vue.extend({
}, },
list() { list() {
const w = this.$root.new(MkUserListsWindow); this.$root.new(MkUserListsWindow);
w.$once('choosen', list => {
this.$router.push(`i/lists/${ list.id }`);
});
}, },
followRequests() { followRequests() {

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="mk-ui" v-hotkey.global="keymap"> <div class="mk-ui" v-hotkey.global="keymap">
<div class="bg" v-if="$store.getters.isSignedIn && $store.state.i.wallpaperUrl" :style="style"></div> <div class="bg" v-if="$store.getters.isSignedIn && $store.state.settings.wallpaper" :style="style"></div>
<x-header class="header" v-if="navbar == 'top'" v-show="!zenMode" ref="header"/> <x-header class="header" v-if="navbar == 'top'" v-show="!zenMode" ref="header"/>
<x-sidebar class="sidebar" v-if="navbar != 'top'" v-show="!zenMode" ref="sidebar"/> <x-sidebar class="sidebar" v-if="navbar != 'top'" v-show="!zenMode" ref="sidebar"/>
<div class="content" :class="[{ sidebar: navbar != 'top', zen: zenMode }, navbar]"> <div class="content" :class="[{ sidebar: navbar != 'top', zen: zenMode }, navbar]">
@ -33,10 +33,9 @@ export default Vue.extend({
}, },
style(): any { style(): any {
if (!this.$store.getters.isSignedIn || this.$store.state.i.wallpaperUrl == null) return {}; if (!this.$store.getters.isSignedIn || this.$store.state.settings.wallpaper == null) return {};
return { return {
backgroundColor: this.$store.state.i.wallpaperColor && this.$store.state.i.wallpaperColor.length == 3 ? `rgb(${ this.$store.state.i.wallpaperColor.join(',') })` : null, backgroundImage: `url(${ this.$store.state.settings.wallpaper })`
backgroundImage: `url(${ this.$store.state.i.wallpaperUrl })`
}; };
}, },
@ -96,7 +95,6 @@ export default Vue.extend({
background-size cover background-size cover
background-position center background-position center
background-attachment fixed background-attachment fixed
opacity 0.3
> .content.sidebar.left > .content.sidebar.left
padding-left 68px padding-left 68px

View File

@ -18,10 +18,12 @@ export default Vue.extend({
data() { data() {
return { return {
connection: null, connection: null,
date: null,
makePromise: cursor => this.$root.api('notes/user-list-timeline', { makePromise: cursor => this.$root.api('notes/user-list-timeline', {
listId: this.list.id, listId: this.list.id,
limit: fetchLimit + 1, limit: fetchLimit + 1,
untilId: cursor ? cursor : undefined, untilId: cursor ? cursor : undefined,
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
includeMyRenotes: this.$store.state.settings.showMyRenotes, includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
includeLocalRenotes: this.$store.state.settings.showLocalRenotes includeLocalRenotes: this.$store.state.settings.showLocalRenotes
@ -46,6 +48,10 @@ export default Vue.extend({
}, },
mounted() { mounted() {
this.init(); this.init();
this.$root.$on('warp', this.warp);
this.$once('hook:beforeDestroy', () => {
this.$root.$off('warp', this.warp);
});
}, },
beforeDestroy() { beforeDestroy() {
this.connection.dispose(); this.connection.dispose();
@ -68,6 +74,10 @@ export default Vue.extend({
}, },
onUserRemoved() { onUserRemoved() {
(this.$refs.timeline as any).reload(); (this.$refs.timeline as any).reload();
},
warp(date) {
this.date = date;
(this.$refs.timeline as any).reload();
} }
} }
}); });

View File

@ -1,85 +1,36 @@
<template> <template>
<mk-window ref="window" width="450px" height="500px" @closed="destroyDom"> <mk-window ref="window" width="450px" height="500px" @closed="destroyDom">
<template #header><fa icon="list"/> {{ $t('title') }}</template> <template #header><fa icon="list"/> {{ $t('title') }}</template>
<x-lists :class="$style.content" @choosen="choosen"/>
<div class="xkxvokkjlptzyewouewmceqcxhpgzprp">
<button class="ui" @click="add">{{ $t('create-list') }}</button>
<a v-for="list in lists" :key="list.id" @click="choice(list)">{{ list.name }}</a>
</div>
</mk-window> </mk-window>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import MkUserListWindow from './user-list-window.vue';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('desktop/views/components/user-lists-window.vue'), i18n: i18n('desktop/views/components/user-lists-window.vue'),
data() { components: {
return { XLists: () => import('../../../common/views/components/user-lists.vue').then(m => m.default)
fetching: true,
lists: []
};
},
mounted() {
this.$root.api('users/lists/list').then(lists => {
this.fetching = false;
this.lists = lists;
});
}, },
methods: { methods: {
add() {
this.$root.dialog({
title: this.$t('list-name'),
input: true
}).then(async ({ canceled, result: title }) => {
if (canceled) return;
const list = await this.$root.api('users/lists/create', {
title
});
this.$emit('choosen', list);
});
},
choice(list) {
this.$emit('choosen', list);
},
close() { close() {
(this as any).$refs.window.close(); (this as any).$refs.window.close();
},
choosen(list) {
this.$root.new(MkUserListWindow, {
list
});
} }
} }
}); });
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" module>
.xkxvokkjlptzyewouewmceqcxhpgzprp .content
padding 16px height 100%
overflow auto
> button
display block
margin-bottom 16px
color var(--primaryForeground)
background var(--primary)
width 100%
border-radius 38px
user-select none
cursor pointer
padding 0 16px
min-width 100px
line-height 38px
font-size 14px
font-weight 700
&:hover
background var(--primaryLighten10)
&:active
background var(--primaryDarken10)
> a
display block
padding 16px
border solid 1px var(--faceDivider)
border-radius 4px
</style> </style>

View File

@ -14,6 +14,7 @@
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import Progress from '../../../common/scripts/loading'; import Progress from '../../../common/scripts/loading';
import { genSearchQuery } from '../../../common/scripts/gen-search-query';
const limit = 20; const limit = 20;
@ -21,10 +22,10 @@ export default Vue.extend({
i18n: i18n('desktop/views/pages/search.vue'), i18n: i18n('desktop/views/pages/search.vue'),
data() { data() {
return { return {
makePromise: cursor => this.$root.api('notes/search', { makePromise: async cursor => this.$root.api('notes/search', {
limit: limit + 1, limit: limit + 1,
offset: cursor ? cursor : undefined, offset: cursor ? cursor : undefined,
query: this.q ...(await genSearchQuery(this, this.q))
}).then(notes => { }).then(notes => {
if (notes.length == limit + 1) { if (notes.length == limit + 1) {
notes.pop(); notes.pop();

View File

@ -53,6 +53,12 @@ export default Vue.extend({
}, },
created() { created() {
this.$root.$on('warp', this.warp);
this.$once('hook:beforeDestroy', () => {
this.$root.$off('warp', this.warp);
this.connection.dispose();
});
const prepend = note => { const prepend = note => {
(this.$refs.timeline as any).prepend(note); (this.$refs.timeline as any).prepend(note);
}; };
@ -124,13 +130,14 @@ export default Vue.extend({
}); });
}, },
beforeDestroy() {
this.connection.dispose();
},
methods: { methods: {
focus() { focus() {
(this.$refs.timeline as any).focus(); (this.$refs.timeline as any).focus();
},
warp(date) {
this.date = date;
(this.$refs.timeline as any).reload();
} }
} }
}); });

View File

@ -123,10 +123,10 @@ export default Vue.extend({
this.$root.dialog({ this.$root.dialog({
title: this.$t('list-name'), title: this.$t('list-name'),
input: true input: true
}).then(async ({ canceled, result: title }) => { }).then(async ({ canceled, result: name }) => {
if (canceled) return; if (canceled) return;
const list = await this.$root.api('users/lists/create', { const list = await this.$root.api('users/lists/create', {
title name
}); });
this.list = list; this.list = list;

View File

@ -36,7 +36,8 @@ export default Vue.extend({
includeReplies: this.mode == 'with-replies', includeReplies: this.mode == 'with-replies',
includeMyRenotes: this.mode != 'my-posts', includeMyRenotes: this.mode != 'my-posts',
withFiles: this.mode == 'with-media', withFiles: this.mode == 'with-media',
untilDate: cursor ? cursor : new Date().getTime() + 1000 * 86400 * 365 untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
untilId: cursor ? cursor : undefined
}).then(notes => { }).then(notes => {
if (notes.length == fetchLimit + 1) { if (notes.length == fetchLimit + 1) {
notes.pop(); notes.pop();
@ -62,10 +63,11 @@ export default Vue.extend({
mounted() { mounted() {
document.addEventListener('keydown', this.onDocumentKeydown); document.addEventListener('keydown', this.onDocumentKeydown);
}, this.$root.$on('warp', this.warp);
this.$once('hook:beforeDestroy', () => {
beforeDestroy() { this.$root.$off('warp', this.warp);
document.removeEventListener('keydown', this.onDocumentKeydown); document.removeEventListener('keydown', this.onDocumentKeydown);
});
}, },
methods: { methods: {

View File

@ -44,7 +44,8 @@
<div v-if="announcements && announcements.length > 0"> <div v-if="announcements && announcements.length > 0">
<div v-for="announcement in announcements"> <div v-for="announcement in announcements">
<h1 v-html="announcement.title"></h1> <h1 v-html="announcement.title"></h1>
<div v-html="announcement.text"></div> <mfm :text="announcement.text"/>
<img v-if="announcement.image" :src="announcement.image" alt="" style="display: block; max-height: 130px; max-width: 100%;"/>
</div> </div>
</div> </div>
</div> </div>

View File

@ -11,7 +11,9 @@
<div class="mkw-polls--body"> <div class="mkw-polls--body">
<div class="poll" v-if="!fetching && poll != null"> <div class="poll" v-if="!fetching && poll != null">
<p v-if="poll.text"><router-link :to="poll | notePage">{{ poll.text }}</router-link></p> <p v-if="poll.text"><router-link :to="poll | notePage">
<mfm :text="poll.text" :author="poll.user" :custom-emojis="poll.emojis"/>
</router-link></p>
<p v-if="!poll.text"><router-link :to="poll | notePage"><fa icon="link"/></router-link></p> <p v-if="!poll.text"><router-link :to="poll | notePage"><fa icon="link"/></router-link></p>
<mk-poll :note="poll"/> <mk-poll :note="poll"/>
</div> </div>

View File

@ -14,7 +14,7 @@ export default define({
}).extend({ }).extend({
methods: { methods: {
chosen(date) { chosen(date) {
this.$emit('chosen', date); this.$root.$emit('warp', date);
}, },
func() { func() {
if (this.props.design == 5) { if (this.props.design == 5) {

View File

@ -458,10 +458,14 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS], os: MiOS)
}, },
dialog(opts) { dialog(opts) {
const vm = this.new(Dialog, opts); const vm = this.new(Dialog, opts);
return new Promise((res) => { const p: any = new Promise((res) => {
vm.$once('ok', result => res({ canceled: false, result })); vm.$once('ok', result => res({ canceled: false, result }));
vm.$once('cancel', () => res({ canceled: true })); vm.$once('cancel', () => res({ canceled: true }));
}); });
p.close = () => {
vm.close();
};
return p;
} }
}, },
router, router,

View File

@ -379,43 +379,30 @@ export default Vue.extend({
}); });
}, },
openContextMenu() {
const fn = window.prompt(this.$t('prompt'));
if (fn == null || fn == '') return;
switch (fn) {
case '1':
this.selectLocalFile();
break;
case '2':
this.urlUpload();
break;
case '3':
this.createFolder();
break;
case '4':
this.renameFolder();
break;
case '5':
this.moveFolder();
break;
case '6':
this.deleteFolder();
break;
}
},
selectLocalFile() { selectLocalFile() {
(this.$refs.file as any).click(); (this.$refs.file as any).click();
}, },
createFolder() { createFolder() {
const name = window.prompt(this.$t('folder-name')); this.$root.dialog({
if (name == null || name == '') return; title: this.$t('folder-name')
this.$root.api('drive/folders/create', { input: {
name: name, default: this.folder.name
parentId: this.folder ? this.folder.id : undefined }
}).then(folder => { }).then(({ result: name }) => {
this.addFolder(folder, true); if (!name) {
this.$root.dialog({
type: 'error',
text: this.$t('folder-name-cannot-empty')
});
return;
}
this.$root.api('drive/folders/create', {
name: name,
parentId: this.folder ? this.folder.id : undefined
}).then(folder => {
this.addFolder(folder, true);
});
}); });
}, },
@ -427,13 +414,25 @@ export default Vue.extend({
}); });
return; return;
} }
const name = window.prompt(this.$t('folder-name'), this.folder.name); this.$root.dialog({
if (name == null || name == '') return; title: this.$t('folder-name')
this.$root.api('drive/folders/update', { input: {
name: name, default: this.folder.name
folderId: this.folder.id }
}).then(folder => { }).then(({ result: name }) => {
this.cd(folder); if (!name) {
this.$root.dialog({
type: 'error',
text: this.$t('cannot-empty')
});
return;
}
this.$root.api('drive/folders/update', {
name: name,
folderId: this.folder.id
}).then(folder => {
this.cd(folder);
});
}); });
}, },

View File

@ -215,11 +215,6 @@ export default Vue.extend({
@media (min-width 500px) @media (min-width 500px)
padding 28px 32px 18px 32px padding 28px 32px 18px 32px
&:after
content ""
display block
clear both
> header > header
display flex display flex
line-height 1.1em line-height 1.1em
@ -236,6 +231,7 @@ export default Vue.extend({
height 60px height 60px
> div > div
min-width 0
> .name > .name
display inline-block display inline-block

View File

@ -106,9 +106,9 @@ export default Vue.extend({
this.init(); this.init();
}, },
init() { async init() {
this.fetching = true; this.fetching = true;
this.makePromise().then(x => { await (this.makePromise()).then(x => {
if (Array.isArray(x)) { if (Array.isArray(x)) {
this.notes = x; this.notes = x;
} else { } else {
@ -123,10 +123,10 @@ export default Vue.extend({
}); });
}, },
fetchMore() { async fetchMore() {
if (!this.more || this.moreFetching) return; if (!this.more || this.moreFetching || this.notes.length === 0) return;
this.moreFetching = true; this.moreFetching = true;
this.makePromise(this.notes[this.notes.length - 1].id).then(x => { await (this.makePromise(this.notes[this.notes.length - 1].id)).then(x => {
this.notes = this.notes.concat(x.notes); this.notes = this.notes.concat(x.notes);
this.more = x.more; this.more = x.more;
this.moreFetching = false; this.moreFetching = false;

View File

@ -43,7 +43,8 @@
<div class="announcements" v-if="announcements && announcements.length > 0"> <div class="announcements" v-if="announcements && announcements.length > 0">
<article v-for="announcement in announcements"> <article v-for="announcement in announcements">
<span v-html="announcement.title" class="title"></span> <span v-html="announcement.title" class="title"></span>
<div v-html="announcement.text"></div> <div><mfm :text="announcement.text"/></div>
<img v-if="announcement.image" :src="announcement.image" alt="" style="display: block; max-height: 120px; max-width: 100%;"/>
</article> </article>
</div> </div>
<a :href="aboutUrl"><p class="about">{{ $t('about') }}</p></a> <a :href="aboutUrl"><p class="about">{{ $t('about') }}</p></a>
@ -66,6 +67,7 @@ import i18n from '../../../i18n';
import { lang } from '../../../config'; import { lang } from '../../../config';
import { faNewspaper, faHashtag, faHome, faColumns } from '@fortawesome/free-solid-svg-icons'; import { faNewspaper, faHashtag, faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
import { faMoon, faSun } from '@fortawesome/free-regular-svg-icons'; import { faMoon, faSun } from '@fortawesome/free-regular-svg-icons';
import { search } from '../../../common/scripts/search';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('mobile/views/components/ui.nav.vue'), i18n: i18n('mobile/views/components/ui.nav.vue'),
@ -133,29 +135,10 @@ export default Vue.extend({
}).then(async ({ canceled, result: query }) => { }).then(async ({ canceled, result: query }) => {
if (canceled) return; if (canceled) return;
const q = query.trim(); this.searching = true;
if (q.startsWith('@')) { search(this, query).finally(() => {
this.$router.push(`/${q}`);
} else if (q.startsWith('#')) {
this.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
} else if (q.startsWith('https://')) {
this.searching = true;
try {
const res = await this.$root.api('ap/show', {
uri: q
});
if (res.type == 'User') {
this.$router.push(`/@${res.object.username}@${res.object.host}`);
} else if (res.type == 'Note') {
this.$router.push(`/notes/${res.object.id}`);
}
} catch (e) {
// TODO
}
this.searching = false; this.searching = false;
} else { });
this.$router.push(`/search?q=${encodeURIComponent(q)}`);
}
}); });
}, },

View File

@ -15,10 +15,12 @@ export default Vue.extend({
data() { data() {
return { return {
connection: null, connection: null,
date: null,
makePromise: cursor => this.$root.api('notes/user-list-timeline', { makePromise: cursor => this.$root.api('notes/user-list-timeline', {
listId: this.list.id, listId: this.list.id,
limit: fetchLimit + 1, limit: fetchLimit + 1,
untilId: cursor ? cursor : undefined, untilId: cursor ? cursor : undefined,
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
includeMyRenotes: this.$store.state.settings.showMyRenotes, includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
includeLocalRenotes: this.$store.state.settings.showLocalRenotes includeLocalRenotes: this.$store.state.settings.showLocalRenotes
@ -45,6 +47,11 @@ export default Vue.extend({
mounted() { mounted() {
this.init(); this.init();
this.$root.$on('warp', this.warp);
this.$once('hook:beforeDestroy', () => {
this.$root.$off('warp', this.warp);
});
}, },
beforeDestroy() { beforeDestroy() {
@ -73,6 +80,11 @@ export default Vue.extend({
onUserRemoved() { onUserRemoved() {
(this.$refs.timeline as any).reload(); (this.$refs.timeline as any).reload();
},
warp(date) {
this.date = date;
(this.$refs.timeline as any).reload();
} }
} }
}); });

View File

@ -17,11 +17,13 @@ export default Vue.extend({
data() { data() {
return { return {
date: null,
makePromise: cursor => this.$root.api('users/notes', { makePromise: cursor => this.$root.api('users/notes', {
userId: this.user.id, userId: this.user.id,
limit: fetchLimit + 1, limit: fetchLimit + 1,
withFiles: this.withMedia, withFiles: this.withMedia,
untilDate: cursor ? cursor : new Date().getTime() + 1000 * 86400 * 365 untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
untilId: cursor ? cursor : undefined
}).then(notes => { }).then(notes => {
if (notes.length == fetchLimit + 1) { if (notes.length == fetchLimit + 1) {
notes.pop(); notes.pop();
@ -37,6 +39,20 @@ export default Vue.extend({
} }
}) })
}; };
},
created() {
this.$root.$on('warp', this.warp);
this.$once('hook:beforeDestroy', () => {
this.$root.$off('warp', this.warp);
});
},
methods: {
warp(date) {
this.date = date;
(this.$refs.timeline as any).reload();
}
} }
}); });
</script> </script>

View File

@ -5,7 +5,7 @@
<template v-if="file"><mk-file-type-icon data-icon :type="file.type" style="margin-right:4px;"/>{{ file.name }}</template> <template v-if="file"><mk-file-type-icon data-icon :type="file.type" style="margin-right:4px;"/>{{ file.name }}</template>
<template v-if="!folder && !file"><span style="margin-right:4px;"><fa icon="cloud"/></span>{{ $t('@.drive') }}</template> <template v-if="!folder && !file"><span style="margin-right:4px;"><fa icon="cloud"/></span>{{ $t('@.drive') }}</template>
</template> </template>
<template #func><button @click="fn"><fa icon="ellipsis-h"/></button></template> <template #func v-if="folder || (!folder && !file)"><button @click="openContextMenu" ref="contextSource"><fa icon="ellipsis-h"/></button></template>
<x-drive <x-drive
ref="browser" ref="browser"
:init-folder="initFolder" :init-folder="initFolder"
@ -26,9 +26,12 @@
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import Progress from '../../../common/scripts/loading'; import Progress from '../../../common/scripts/loading';
import XMenu from '../../../common/views/components/menu.vue';
import { faTrashAlt } from '@fortawesome/free-regular-svg-icons';
import { faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons';
export default Vue.extend({ export default Vue.extend({
i18n: i18n(), i18n: i18n('mobile/views/pages/drive.vue'),
components: { components: {
XDrive: () => import('../components/drive.vue').then(m => m.default), XDrive: () => import('../components/drive.vue').then(m => m.default),
}, },
@ -63,9 +66,6 @@ export default Vue.extend({
(this.$refs as any).browser.goRoot(true); (this.$refs as any).browser.goRoot(true);
} }
}, },
fn() {
(this.$refs as any).browser.openContextMenu();
},
onMoveRoot(silent) { onMoveRoot(silent) {
const title = `${this.$root.instanceName} Drive`; const title = `${this.$root.instanceName} Drive`;
@ -104,6 +104,42 @@ export default Vue.extend({
this.file = file; this.file = file;
this.folder = null; this.folder = null;
},
openContextMenu() {
this.$root.new(XMenu, {
items: [{
type: 'item',
text: this.$t('contextmenu.upload'),
icon: 'upload',
action: this.$refs.browser.selectLocalFile
}, {
type: 'item',
text: this.$t('contextmenu.url-upload'),
icon: faCloudUploadAlt,
action: this.$refs.browser.urlUpload
}, {
type: 'item',
text: this.$t('contextmenu.create-folder'),
icon: ['far', 'folder'],
action: this.$refs.browser.createFolder
}, ...(this.folder ? [{
type: 'item',
text: this.$t('contextmenu.rename-folder'),
icon: 'i-cursor',
action: this.$refs.browser.renameFolder
}, {
type: 'item',
text: this.$t('contextmenu.move-folder'),
icon: ['far', 'folder-open'],
action: this.$refs.browser.moveFolder
}, {
type: 'item',
text: this.$t('contextmenu.delete-folder'),
icon: faTrashAlt,
action: this.$refs.browser.deleteFolder
}] : [])],
source: this.$refs.contextSource,
});
} }
} }
}); });

View File

@ -54,6 +54,12 @@ export default Vue.extend({
}, },
created() { created() {
this.$root.$on('warp', this.warp);
this.$once('hook:beforeDestroy', () => {
this.$root.$off('warp', this.warp);
this.connection.dispose();
});
const prepend = note => { const prepend = note => {
(this.$refs.timeline as any).prepend(note); (this.$refs.timeline as any).prepend(note);
}; };
@ -125,10 +131,6 @@ export default Vue.extend({
}); });
}, },
beforeDestroy() {
this.connection.dispose();
},
methods: { methods: {
focus() { focus() {
(this.$refs.timeline as any).focus(); (this.$refs.timeline as any).focus();

View File

@ -12,6 +12,7 @@
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import Progress from '../../../common/scripts/loading'; import Progress from '../../../common/scripts/loading';
import { genSearchQuery } from '../../../common/scripts/gen-search-query';
const limit = 20; const limit = 20;
@ -19,10 +20,10 @@ export default Vue.extend({
i18n: i18n('mobile/views/pages/search.vue'), i18n: i18n('mobile/views/pages/search.vue'),
data() { data() {
return { return {
makePromise: cursor => this.$root.api('notes/search', { makePromise: async cursor => this.$root.api('notes/search', {
limit: limit + 1, limit: limit + 1,
untilId: cursor ? cursor : undefined, untilId: cursor ? cursor : undefined,
query: this.q ...(await genSearchQuery(this, this.q))
}).then(notes => { }).then(notes => {
if (notes.length == limit + 1) { if (notes.length == limit + 1) {
notes.pop(); notes.pop();

View File

@ -1,20 +1,15 @@
<template> <template>
<mk-ui> <mk-ui>
<template #header><fa icon="list"/>{{ $t('title') }}</template> <template #header><fa icon="list"/>{{ $t('title') }}</template>
<template #func><button @click="fn"><fa icon="plus"/></button></template> <template #func><button @click="$refs.lists.add()"><fa icon="plus"/></button></template>
<main> <x-lists ref="lists" @choosen="choosen"/>
<ul>
<li v-for="list in lists" :key="list.id"><router-link :to="`/i/lists/${list.id}`">{{ list.name }}</router-link></li>
</ul>
</main>
</mk-ui> </mk-ui>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import Progress from '../../../common/scripts/loading';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('mobile/views/pages/user-lists.vue'), i18n: i18n('mobile/views/pages/user-lists.vue'),
@ -24,31 +19,16 @@ export default Vue.extend({
lists: [] lists: []
}; };
}, },
components: {
XLists: () => import('../../../common/views/components/user-lists.vue').then(m => m.default)
},
mounted() { mounted() {
document.title = this.$t('title'); document.title = this.$t('title');
Progress.start();
this.$root.api('users/lists/list').then(lists => {
this.fetching = false;
this.lists = lists;
Progress.done();
});
}, },
methods: { methods: {
fn() { choosen(list) {
this.$root.dialog({ if (!list) return;
title: this.$t('enter-list-name'), this.$router.push(`/i/lists/${list.id}`);
input: true
}).then(async ({ canceled, result: title }) => {
if (canceled) return;
const list = await this.$root.api('users/lists/create', {
title
});
this.$router.push(`/i/lists/${list.id}`);
});
} }
} }
}); });

View File

@ -18,7 +18,7 @@
</div> </div>
<div class="title"> <div class="title">
<h1><mk-user-name :user="user" :key="user.id"/></h1> <h1><mk-user-name :user="user" :key="user.id"/></h1>
<span class="username"><mk-acct :user="user" :detail="true" /></span> <span class="username"><mk-acct :user="user" :detail="true" :key="user.id"/></span>
<span class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</span> <span class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</span>
</div> </div>
<div class="description"> <div class="description">

View File

@ -29,7 +29,8 @@
<div class="announcements" v-if="announcements && announcements.length > 0"> <div class="announcements" v-if="announcements && announcements.length > 0">
<article v-for="announcement in announcements"> <article v-for="announcement in announcements">
<span class="title" v-html="announcement.title"></span> <span class="title" v-html="announcement.title"></span>
<div v-html="announcement.text"></div> <mfm :text="announcement.text"/>
<img v-if="announcement.image" :src="announcement.image" alt="" style="display: block; max-height: 120px; max-width: 100%;"/>
</article> </article>
</div> </div>
<article class="about-misskey"> <article class="about-misskey">

View File

@ -72,13 +72,13 @@ export default Vue.extend({
computed: { computed: {
widgets(): any[] { widgets(): any[] {
return this.$store.state.settings.mobileHome; return this.$store.state.device.mobileHome;
} }
}, },
created() { created() {
if (this.widgets.length == 0) { if (this.widgets.length == 0) {
this.widgets = [{ this.$store.commit('device/setMobileHome', [{
name: 'calendar', name: 'calendar',
id: 'a', data: {} id: 'a', data: {}
}, { }, {
@ -96,8 +96,7 @@ export default Vue.extend({
}, { }, {
name: 'version', name: 'version',
id: 'g', data: {} id: 'g', data: {}
}]; }]);
this.saveHome();
} }
}, },
@ -123,7 +122,7 @@ export default Vue.extend({
}, },
addWidget() { addWidget() {
this.$store.commit('settings/addMobileHomeWidget', { this.$store.commit('device/addMobileHomeWidget', {
name: this.widgetAdderSelected, name: this.widgetAdderSelected,
id: uuid(), id: uuid(),
data: {} data: {}
@ -131,11 +130,11 @@ export default Vue.extend({
}, },
removeWidget(widget) { removeWidget(widget) {
this.$store.commit('settings/removeMobileHomeWidget', widget); this.$store.commit('device/removeMobileHomeWidget', widget);
}, },
saveHome() { saveHome() {
this.$store.commit('settings/setMobileHome', this.widgets); this.$store.commit('device/setMobileHome', this.widgets);
} }
} }
}); });

View File

@ -28,6 +28,7 @@ const defaultSettings = {
iLikeSushi: false, iLikeSushi: false,
rememberNoteVisibility: false, rememberNoteVisibility: false,
defaultNoteVisibility: 'public', defaultNoteVisibility: 'public',
wallpaper: null,
webSearchEngine: 'https://www.google.com/?#q={{query}}', webSearchEngine: 'https://www.google.com/?#q={{query}}',
mutedWords: [], mutedWords: [],
games: { games: {
@ -357,7 +358,7 @@ export default (os: MiOS) => new Vuex.Store({
ctx.commit('set', x); ctx.commit('set', x);
if (ctx.rootGetters.isSignedIn) { if (ctx.rootGetters.isSignedIn) {
os.api('i/update_client_setting', { os.api('i/update-client-setting', {
name: x.key, name: x.key,
value: x.value value: x.value
}); });

View File

@ -19,7 +19,7 @@ export const cafeTheme: Theme = require('../themes/cafe.json5');
export const japaneseSushiSetTheme: Theme = require('../themes/japanese-sushi-set.json5'); export const japaneseSushiSetTheme: Theme = require('../themes/japanese-sushi-set.json5');
export const gruvboxDarkTheme: Theme = require('../themes/gruvbox-dark.json5'); export const gruvboxDarkTheme: Theme = require('../themes/gruvbox-dark.json5');
export const monokaiTheme: Theme = require('../themes/monokai.json5'); export const monokaiTheme: Theme = require('../themes/monokai.json5');
export const colorfulTheme: Theme = require('../themes/colorful.json5'); export const vividTheme: Theme = require('../themes/vivid.json5');
export const rainyTheme: Theme = require('../themes/rainy.json5'); export const rainyTheme: Theme = require('../themes/rainy.json5');
export const mauveTheme: Theme = require('../themes/mauve.json5'); export const mauveTheme: Theme = require('../themes/mauve.json5');
export const grayTheme: Theme = require('../themes/gray.json5'); export const grayTheme: Theme = require('../themes/gray.json5');
@ -35,7 +35,7 @@ export const builtinThemes = [
japaneseSushiSetTheme, japaneseSushiSetTheme,
gruvboxDarkTheme, gruvboxDarkTheme,
monokaiTheme, monokaiTheme,
colorfulTheme, vividTheme,
rainyTheme, rainyTheme,
mauveTheme, mauveTheme,
grayTheme, grayTheme,

View File

@ -1,7 +1,7 @@
{ {
id: '2d066d6e-bd39-4f23-bd48-686d5c1c6ae8', id: '2d066d6e-bd39-4f23-bd48-686d5c1c6ae8',
name: 'Colorful', name: 'Vivid',
author: 'syuilo', author: 'syuilo',
base: 'light', base: 'light',

View File

@ -1,41 +1,30 @@
import * as elasticsearch from 'elasticsearch'; import * as elasticsearch from '@elastic/elasticsearch';
import config from '../config'; import config from '../config';
import Logger from '../services/logger';
const esLogger = new Logger('es');
const index = { const index = {
settings: { settings: {
analysis: { analysis: {
normalizer: {
lowercase_normalizer: {
type: 'custom',
filter: ['lowercase']
}
},
analyzer: { analyzer: {
bigram: { ngram: {
tokenizer: 'bigram_tokenizer' tokenizer: 'ngram'
}
},
tokenizer: {
bigram_tokenizer: {
type: 'nGram',
min_gram: 2,
max_gram: 2
} }
} }
} }
}, },
mappings: { mappings: {
note: { properties: {
properties: { text: {
text: { type: 'text',
type: 'text', index: true,
index: true, analyzer: 'ngram',
analyzer: 'bigram', },
normalizer: 'lowercase_normalizer' userId: {
} type: 'keyword',
index: true,
},
userHost: {
type: 'keyword',
index: true,
} }
} }
} }
@ -43,31 +32,20 @@ const index = {
// Init ElasticSearch connection // Init ElasticSearch connection
const client = config.elasticsearch ? new elasticsearch.Client({ const client = config.elasticsearch ? new elasticsearch.Client({
host: `${config.elasticsearch.host}:${config.elasticsearch.port}` node: `http://${config.elasticsearch.host}:${config.elasticsearch.port}`,
pingTimeout: 30000
}) : null; }) : null;
if (client) { if (client) {
// Send a HEAD request
client.ping({
// Ping usually has a 3000ms timeout
requestTimeout: 30000
}, error => {
if (error) {
esLogger.error('elasticsearch is down!');
} else {
esLogger.succ('elasticsearch is available!');
}
});
client.indices.exists({ client.indices.exists({
index: 'misskey' index: 'misskey_note'
}).then(exist => { }).then(exist => {
if (exist) return; if (!exist.body) {
client.indices.create({
client.indices.create({ index: 'misskey_note',
index: 'misskey', body: index
body: index });
}); }
}); });
} }

View File

@ -76,8 +76,6 @@ class MyCustomLogger implements Logger {
} }
export function initDb(justBorrow = false, sync = false, log = false) { export function initDb(justBorrow = false, sync = false, log = false) {
const enableLogging = log || !['production', 'test'].includes(process.env.NODE_ENV || '');
try { try {
const conn = getConnection(); const conn = getConnection();
return Promise.resolve(conn); return Promise.resolve(conn);
@ -92,8 +90,8 @@ export function initDb(justBorrow = false, sync = false, log = false) {
database: config.db.db, database: config.db.db,
synchronize: process.env.NODE_ENV === 'test' || sync, synchronize: process.env.NODE_ENV === 'test' || sync,
dropSchema: process.env.NODE_ENV === 'test' && !justBorrow, dropSchema: process.env.NODE_ENV === 'test' && !justBorrow,
logging: enableLogging, logging: log,
logger: enableLogging ? new MyCustomLogger() : undefined, logger: log ? new MyCustomLogger() : undefined,
entities: [ entities: [
Meta, Meta,
Instance, Instance,

View File

@ -141,7 +141,7 @@ export const mfmLanguage = P.createLanguage({
}, },
hashtag: () => P((input, i) => { hashtag: () => P((input, i) => {
const text = input.substr(i); const text = input.substr(i);
const match = text.match(/^#([^\s\.,!\?'"#:\/\[\]]+)/i); const match = text.match(/^#([^\s\.,!\?'"#:\/\[\]【】]+)/i);
if (!match) return P.makeFailure(i, 'not a hashtag'); if (!match) return P.makeFailure(i, 'not a hashtag');
let hashtag = match[1]; let hashtag = match[1];
hashtag = removeOrphanedBrackets(hashtag); hashtag = removeOrphanedBrackets(hashtag);

View File

@ -1,7 +1,11 @@
import { Meta } from '../models/entities/meta'; import { Meta } from '../models/entities/meta';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
export default async function(): Promise<Meta> { let cache: Meta;
export async function fetchMeta(noCache = false): Promise<Meta> {
if (!noCache && cache) return cache;
return await getConnection().transaction(async transactionalEntityManager => { return await getConnection().transaction(async transactionalEntityManager => {
// バグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する // バグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する
const meta = await transactionalEntityManager.findOne(Meta, { const meta = await transactionalEntityManager.findOne(Meta, {
@ -11,11 +15,21 @@ export default async function(): Promise<Meta> {
}); });
if (meta) { if (meta) {
cache = meta;
return meta; return meta;
} else { } else {
return await transactionalEntityManager.save(Meta, { const saved = await transactionalEntityManager.save(Meta, {
id: 'x' id: 'x'
}) as Meta; }) as Meta;
cache = saved;
return saved;
} }
}); });
} }
setInterval(() => {
fetchMeta(true).then(meta => {
cache = meta;
});
}, 5000);

View File

@ -1,4 +1,4 @@
import fetchMeta from './fetch-meta'; import { fetchMeta } from './fetch-meta';
import { ILocalUser } from '../models/entities/user'; import { ILocalUser } from '../models/entities/user';
import { Users } from '../models'; import { Users } from '../models';
import { ensure } from '../prelude/ensure'; import { ensure } from '../prelude/ensure';

View File

@ -22,5 +22,5 @@ function getRandom() {
} }
export function genMeid(date: Date): string { export function genMeid(date: Date): string {
return 'f' + getTime(date.getTime()) + getRandom(); return getTime(date.getTime()) + getRandom();
} }

View File

@ -1,5 +1,5 @@
import { emojiRegex } from './emoji-regex'; import { emojiRegex } from './emoji-regex';
import fetchMeta from './fetch-meta'; import { fetchMeta } from './fetch-meta';
import { Emojis } from '../models'; import { Emojis } from '../models';
const basic10: Record<string, string> = { const basic10: Record<string, string> = {

View File

@ -1,14 +1,46 @@
export const types = {
boolean: 'boolean' as 'boolean',
string: 'string' as 'string',
number: 'number' as 'number',
array: 'array' as 'array',
object: 'object' as 'object',
any: 'any' as 'any',
};
export const bool = {
true: true as true,
false: false as false,
};
export type Schema = { export type Schema = {
type: 'number' | 'string' | 'array' | 'object' | any; type: 'boolean' | 'number' | 'string' | 'array' | 'object' | 'any';
optional?: boolean; nullable: boolean;
optional: boolean;
items?: Schema; items?: Schema;
properties?: Obj; properties?: Obj;
description?: string; description?: string;
example?: any;
format?: string;
ref?: string;
enum?: string[];
}; };
type NonUndefinedPropertyNames<T extends Obj> = {
[K in keyof T]: T[K]['optional'] extends true ? never : K
}[keyof T];
type UndefinedPropertyNames<T extends Obj> = {
[K in keyof T]: T[K]['optional'] extends true ? K : never
}[keyof T];
type OnlyRequired<T extends Obj> = Pick<T, NonUndefinedPropertyNames<T>>;
type OnlyOptional<T extends Obj> = Pick<T, UndefinedPropertyNames<T>>;
export type Obj = { [key: string]: Schema }; export type Obj = { [key: string]: Schema };
export type ObjType<s extends Obj> = { [P in keyof s]: SchemaType<s[P]> }; export type ObjType<s extends Obj> =
{ [P in keyof OnlyOptional<s>]?: SchemaType<s[P]> } &
{ [P in keyof OnlyRequired<s>]: SchemaType<s[P]> };
// https://qiita.com/hrsh7th@github/items/84e8968c3601009cdcf2 // https://qiita.com/hrsh7th@github/items/84e8968c3601009cdcf2
type MyType<T extends Schema> = { type MyType<T extends Schema> = {
@ -16,26 +48,20 @@ type MyType<T extends Schema> = {
1: SchemaType<T>; 1: SchemaType<T>;
}[T extends Schema ? 1 : 0]; }[T extends Schema ? 1 : 0];
export type SchemaType<p extends Schema> = type NullOrUndefined<p extends Schema, T> =
p['type'] extends 'number' ? number : p['nullable'] extends true
p['type'] extends 'string' ? string : ? p['optional'] extends true
p['type'] extends 'array' ? MyType<NonNullable<p['items']>>[] : ? (T | null | undefined)
p['type'] extends 'object' ? ObjType<NonNullable<p['properties']>> : : (T | null)
any; : p['optional'] extends true
? (T | undefined)
: T;
export function convertOpenApiSchema(schema: Schema) { export type SchemaType<p extends Schema> =
const x = JSON.parse(JSON.stringify(schema)); // copy p['type'] extends 'number' ? NullOrUndefined<p, number> :
if (!['string', 'number', 'boolean', 'array', 'object'].includes(x.type)) { p['type'] extends 'string' ? NullOrUndefined<p, string> :
x['$ref'] = `#/components/schemas/${x.type}`; p['type'] extends 'boolean' ? NullOrUndefined<p, boolean> :
} p['type'] extends 'array' ? NullOrUndefined<p, MyType<NonNullable<p['items']>>[]> :
if (x.type === 'array' && x.items) { p['type'] extends 'object' ? NullOrUndefined<p, ObjType<NonNullable<p['properties']>>> :
x.items = convertOpenApiSchema(x.items); p['type'] extends 'any' ? NullOrUndefined<p, any> :
} any;
if (x.type === 'object' && x.properties) {
x.required = Object.entries(x.properties).filter(([k, v]: any) => !v.isOptional).map(([k, v]: any) => k);
for (const k of Object.keys(x.properties)) {
x.properties[k] = convertOpenApiSchema(x.properties[k]);
}
}
return x;
}

View File

@ -36,7 +36,7 @@ export class NoteReaction {
public note: Note | null; public note: Note | null;
@Column('varchar', { @Column('varchar', {
length: 32 length: 128
}) })
public reaction: string; public reaction: string;
} }

View File

@ -1,8 +1,8 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { Users } from '..'; import { Users } from '..';
import rap from '@prezzemolo/rap';
import { AbuseUserReport } from '../entities/abuse-user-report'; import { AbuseUserReport } from '../entities/abuse-user-report';
import { ensure } from '../../prelude/ensure'; import { ensure } from '../../prelude/ensure';
import { awaitAll } from '../../prelude/await-all';
@EntityRepository(AbuseUserReport) @EntityRepository(AbuseUserReport)
export class AbuseUserReportRepository extends Repository<AbuseUserReport> { export class AbuseUserReportRepository extends Repository<AbuseUserReport> {
@ -17,7 +17,7 @@ export class AbuseUserReportRepository extends Repository<AbuseUserReport> {
) { ) {
const report = typeof src === 'object' ? src : await this.findOne(src).then(ensure); const report = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({ return await awaitAll({
id: report.id, id: report.id,
createdAt: report.createdAt, createdAt: report.createdAt,
reporterId: report.reporterId, reporterId: report.reporterId,

View File

@ -2,6 +2,9 @@ import { EntityRepository, Repository } from 'typeorm';
import { App } from '../entities/app'; import { App } from '../entities/app';
import { AccessTokens } from '..'; import { AccessTokens } from '..';
import { ensure } from '../../prelude/ensure'; import { ensure } from '../../prelude/ensure';
import { types, bool, SchemaType } from '../../misc/schema';
export type PackedApp = SchemaType<typeof packedAppSchema>;
@EntityRepository(App) @EntityRepository(App)
export class AppRepository extends Repository<App> { export class AppRepository extends Repository<App> {
@ -13,7 +16,7 @@ export class AppRepository extends Repository<App> {
includeSecret?: boolean, includeSecret?: boolean,
includeProfileImageIds?: boolean includeProfileImageIds?: boolean
} }
) { ): Promise<PackedApp> {
const opts = Object.assign({ const opts = Object.assign({
detail: false, detail: false,
includeSecret: false, includeSecret: false,
@ -26,6 +29,7 @@ export class AppRepository extends Repository<App> {
id: app.id, id: app.id,
name: app.name, name: app.name,
callbackUrl: app.callbackUrl, callbackUrl: app.callbackUrl,
permission: app.permission,
...(opts.includeSecret ? { secret: app.secret } : {}), ...(opts.includeSecret ? { secret: app.secret } : {}),
...(me ? { ...(me ? {
isAuthorized: await AccessTokens.count({ isAuthorized: await AccessTokens.count({
@ -36,3 +40,40 @@ export class AppRepository extends Repository<App> {
}; };
} }
} }
export const packedAppSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this Note.',
example: 'xxxxxxxxxx',
},
name: {
type: types.string,
optional: bool.false, nullable: bool.false,
description: 'アプリケーションの名前'
},
callbackUrl: {
type: types.string,
optional: bool.false, nullable: bool.true,
description: 'コールバックするURL'
},
permission: {
type: types.array,
optional: bool.true, nullable: bool.false,
items: {
type: types.string,
optional: bool.false, nullable: bool.false,
}
},
secret: {
type: types.string,
optional: bool.true, nullable: bool.false,
description: 'アプリケーションのシークレットキー'
}
},
};

View File

@ -1,8 +1,8 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { Apps } from '..'; import { Apps } from '..';
import rap from '@prezzemolo/rap';
import { AuthSession } from '../entities/auth-session'; import { AuthSession } from '../entities/auth-session';
import { ensure } from '../../prelude/ensure'; import { ensure } from '../../prelude/ensure';
import { awaitAll } from '../../prelude/await-all';
@EntityRepository(AuthSession) @EntityRepository(AuthSession)
export class AuthSessionRepository extends Repository<AuthSession> { export class AuthSessionRepository extends Repository<AuthSession> {
@ -12,7 +12,7 @@ export class AuthSessionRepository extends Repository<AuthSession> {
) { ) {
const session = typeof src === 'object' ? src : await this.findOne(src).then(ensure); const session = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({ return await awaitAll({
id: session.id, id: session.id,
app: Apps.pack(session.appId, me), app: Apps.pack(session.appId, me),
token: session.token token: session.token

View File

@ -1,8 +1,11 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { Users } from '..'; import { Users } from '..';
import rap from '@prezzemolo/rap';
import { Blocking } from '../entities/blocking'; import { Blocking } from '../entities/blocking';
import { ensure } from '../../prelude/ensure'; import { ensure } from '../../prelude/ensure';
import { awaitAll } from '../../prelude/await-all';
import { SchemaType, types, bool } from '../../misc/schema';
export type PackedBlocking = SchemaType<typeof packedBlockingSchema>;
@EntityRepository(Blocking) @EntityRepository(Blocking)
export class BlockingRepository extends Repository<Blocking> { export class BlockingRepository extends Repository<Blocking> {
@ -16,14 +19,47 @@ export class BlockingRepository extends Repository<Blocking> {
public async pack( public async pack(
src: Blocking['id'] | Blocking, src: Blocking['id'] | Blocking,
me?: any me?: any
) { ): Promise<PackedBlocking> {
const blocking = typeof src === 'object' ? src : await this.findOne(src).then(ensure); const blocking = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({ return await awaitAll({
id: blocking.id, id: blocking.id,
createdAt: blocking.createdAt.toISOString(),
blockeeId: blocking.blockeeId,
blockee: Users.pack(blocking.blockeeId, me, { blockee: Users.pack(blocking.blockeeId, me, {
detail: true detail: true
}) })
}); });
} }
} }
export const packedBlockingSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this blocking.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the blocking was created.'
},
blockeeId: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
},
blockee: {
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'User',
description: 'The blockee.'
},
}
};

View File

@ -1,10 +1,13 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { DriveFile } from '../entities/drive-file'; import { DriveFile } from '../entities/drive-file';
import { Users, DriveFolders } from '..'; import { Users, DriveFolders } from '..';
import rap from '@prezzemolo/rap';
import { User } from '../entities/user'; import { User } from '../entities/user';
import { toPuny } from '../../misc/convert-host'; import { toPuny } from '../../misc/convert-host';
import { ensure } from '../../prelude/ensure'; import { ensure } from '../../prelude/ensure';
import { awaitAll } from '../../prelude/await-all';
import { types, bool, SchemaType } from '../../misc/schema';
export type PackedDriveFile = SchemaType<typeof packedDriveFileSchema>;
@EntityRepository(DriveFile) @EntityRepository(DriveFile)
export class DriveFileRepository extends Repository<DriveFile> { export class DriveFileRepository extends Repository<DriveFile> {
@ -82,7 +85,7 @@ export class DriveFileRepository extends Repository<DriveFile> {
self?: boolean, self?: boolean,
withUser?: boolean, withUser?: boolean,
} }
) { ): Promise<PackedDriveFile> {
const opts = Object.assign({ const opts = Object.assign({
detail: false, detail: false,
self: false self: false
@ -90,9 +93,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
const file = typeof src === 'object' ? src : await this.findOne(src).then(ensure); const file = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({ return await awaitAll({
id: file.id, id: file.id,
createdAt: file.createdAt, createdAt: file.createdAt.toISOString(),
name: file.name, name: file.name,
type: file.type, type: file.type,
md5: file.md5, md5: file.md5,
@ -109,3 +112,66 @@ export class DriveFileRepository extends Repository<DriveFile> {
}); });
} }
} }
export const packedDriveFileSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this Drive file.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the Drive file was created on Misskey.'
},
name: {
type: types.string,
optional: bool.false, nullable: bool.false,
description: 'The file name with extension.',
example: 'lenna.jpg'
},
type: {
type: types.string,
optional: bool.false, nullable: bool.false,
description: 'The MIME type of this Drive file.',
example: 'image/jpeg'
},
md5: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'md5',
description: 'The MD5 hash of this Drive file.',
example: '15eca7fba0480996e2245f5185bf39f2'
},
size: {
type: types.number,
optional: bool.false, nullable: bool.false,
description: 'The size of this Drive file. (bytes)',
example: 51469
},
url: {
type: types.string,
optional: bool.false, nullable: bool.true,
format: 'url',
description: 'The URL of this Drive file.',
},
folderId: {
type: types.string,
optional: bool.false, nullable: bool.true,
format: 'id',
description: 'The parent folder ID of this Drive file.',
example: 'xxxxxxxxxx',
},
isSensitive: {
type: types.boolean,
optional: bool.false, nullable: bool.false,
description: 'Whether this Drive file is sensitive.',
},
},
};

View File

@ -1,8 +1,11 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { DriveFolders, DriveFiles } from '..'; import { DriveFolders, DriveFiles } from '..';
import rap from '@prezzemolo/rap';
import { DriveFolder } from '../entities/drive-folder'; import { DriveFolder } from '../entities/drive-folder';
import { ensure } from '../../prelude/ensure'; import { ensure } from '../../prelude/ensure';
import { awaitAll } from '../../prelude/await-all';
import { SchemaType, types, bool } from '../../misc/schema';
export type PackedDriveFolder = SchemaType<typeof packedDriveFolderSchema>;
@EntityRepository(DriveFolder) @EntityRepository(DriveFolder)
export class DriveFolderRepository extends Repository<DriveFolder> { export class DriveFolderRepository extends Repository<DriveFolder> {
@ -18,16 +21,16 @@ export class DriveFolderRepository extends Repository<DriveFolder> {
options?: { options?: {
detail: boolean detail: boolean
} }
): Promise<Record<string, any>> { ): Promise<PackedDriveFolder> {
const opts = Object.assign({ const opts = Object.assign({
detail: false detail: false
}, options); }, options);
const folder = typeof src === 'object' ? src : await this.findOne(src).then(ensure); const folder = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({ return await awaitAll({
id: folder.id, id: folder.id,
createdAt: folder.createdAt, createdAt: folder.createdAt.toISOString(),
name: folder.name, name: folder.name,
parentId: folder.parentId, parentId: folder.parentId,
@ -48,3 +51,50 @@ export class DriveFolderRepository extends Repository<DriveFolder> {
}); });
} }
} }
export const packedDriveFolderSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this Drive folder.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the Drive folder was created.'
},
name: {
type: types.string,
optional: bool.false, nullable: bool.false,
description: 'The folder name.',
},
foldersCount: {
type: types.number,
optional: bool.true, nullable: bool.false,
description: 'The count of child folders.',
},
filesCount: {
type: types.number,
optional: bool.true, nullable: bool.false,
description: 'The count of child files.',
},
parentId: {
type: types.string,
optional: bool.false, nullable: bool.true,
format: 'id',
description: 'The parent folder ID of this folder.',
example: 'xxxxxxxxxx',
},
parent: {
type: types.object,
optional: bool.true, nullable: bool.true,
ref: 'DriveFolder'
},
},
};

View File

@ -1,8 +1,9 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { Users } from '..'; import { Users } from '..';
import rap from '@prezzemolo/rap';
import { Following } from '../entities/following'; import { Following } from '../entities/following';
import { ensure } from '../../prelude/ensure'; import { ensure } from '../../prelude/ensure';
import { awaitAll } from '../../prelude/await-all';
import { SchemaType, types, bool } from '../../misc/schema';
type LocalFollowerFollowing = Following & { type LocalFollowerFollowing = Following & {
followerHost: null; followerHost: null;
@ -28,6 +29,8 @@ type RemoteFolloweeFollowing = Following & {
followeeSharedInbox: string; followeeSharedInbox: string;
}; };
export type PackedFollowing = SchemaType<typeof packedFollowingSchema>;
@EntityRepository(Following) @EntityRepository(Following)
export class FollowingRepository extends Repository<Following> { export class FollowingRepository extends Repository<Following> {
public isLocalFollower(following: Following): following is LocalFollowerFollowing { public isLocalFollower(following: Following): following is LocalFollowerFollowing {
@ -64,22 +67,64 @@ export class FollowingRepository extends Repository<Following> {
populateFollowee?: boolean; populateFollowee?: boolean;
populateFollower?: boolean; populateFollower?: boolean;
} }
) { ): Promise<PackedFollowing> {
const following = typeof src === 'object' ? src : await this.findOne(src).then(ensure); const following = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
if (opts == null) opts = {}; if (opts == null) opts = {};
return await rap({ return await awaitAll({
id: following.id, id: following.id,
createdAt: following.createdAt, createdAt: following.createdAt.toISOString(),
followeeId: following.followeeId, followeeId: following.followeeId,
followerId: following.followerId, followerId: following.followerId,
followee: opts.populateFollowee ? Users.pack(following.followee || following.followeeId, me, { followee: opts.populateFollowee ? Users.pack(following.followee || following.followeeId, me, {
detail: true detail: true
}) : null, }) : undefined,
follower: opts.populateFollower ? Users.pack(following.follower || following.followerId, me, { follower: opts.populateFollower ? Users.pack(following.follower || following.followerId, me, {
detail: true detail: true
}) : null, }) : undefined,
}); });
} }
} }
export const packedFollowingSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this following.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the following was created.'
},
followeeId: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
},
followee: {
type: types.object,
optional: bool.true, nullable: bool.false,
ref: 'User',
description: 'The followee.'
},
followerId: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
},
follower: {
type: types.object,
optional: bool.true, nullable: bool.false,
ref: 'User',
description: 'The follower.'
},
}
};

View File

@ -1,8 +1,8 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import rap from '@prezzemolo/rap';
import { ReversiMatching } from '../../../entities/games/reversi/matching'; import { ReversiMatching } from '../../../entities/games/reversi/matching';
import { Users } from '../../..'; import { Users } from '../../..';
import { ensure } from '../../../../prelude/ensure'; import { ensure } from '../../../../prelude/ensure';
import { awaitAll } from '../../../../prelude/await-all';
@EntityRepository(ReversiMatching) @EntityRepository(ReversiMatching)
export class ReversiMatchingRepository extends Repository<ReversiMatching> { export class ReversiMatchingRepository extends Repository<ReversiMatching> {
@ -12,7 +12,7 @@ export class ReversiMatchingRepository extends Repository<ReversiMatching> {
) { ) {
const matching = typeof src === 'object' ? src : await this.findOne(src).then(ensure); const matching = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({ return await awaitAll({
id: matching.id, id: matching.id,
createdAt: matching.createdAt, createdAt: matching.createdAt,
parentId: matching.parentId, parentId: matching.parentId,

View File

@ -2,6 +2,9 @@ import { EntityRepository, Repository } from 'typeorm';
import { MessagingMessage } from '../entities/messaging-message'; import { MessagingMessage } from '../entities/messaging-message';
import { Users, DriveFiles } from '..'; import { Users, DriveFiles } from '..';
import { ensure } from '../../prelude/ensure'; import { ensure } from '../../prelude/ensure';
import { types, bool, SchemaType } from '../../misc/schema';
export type PackedMessagingMessage = SchemaType<typeof packedMessagingMessageSchema>;
@EntityRepository(MessagingMessage) @EntityRepository(MessagingMessage)
export class MessagingMessageRepository extends Repository<MessagingMessage> { export class MessagingMessageRepository extends Repository<MessagingMessage> {
@ -15,7 +18,7 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> {
options?: { options?: {
populateRecipient: boolean populateRecipient: boolean
} }
) { ): Promise<PackedMessagingMessage> {
const opts = options || { const opts = options || {
populateRecipient: true populateRecipient: true
}; };
@ -24,15 +27,73 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> {
return { return {
id: message.id, id: message.id,
createdAt: message.createdAt, createdAt: message.createdAt.toISOString(),
text: message.text, text: message.text,
userId: message.userId, userId: message.userId,
user: await Users.pack(message.user || message.userId, me), user: await Users.pack(message.user || message.userId, me),
recipientId: message.recipientId, recipientId: message.recipientId,
recipient: opts.populateRecipient ? await Users.pack(message.recipient || message.recipientId, me) : null, recipient: opts.populateRecipient ? await Users.pack(message.recipient || message.recipientId, me) : undefined,
fileId: message.fileId, fileId: message.fileId,
file: message.fileId ? await DriveFiles.pack(message.fileId) : null, file: message.fileId ? await DriveFiles.pack(message.fileId) : null,
isRead: message.isRead isRead: message.isRead
}; };
} }
} }
export const packedMessagingMessageSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this MessagingMessage.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the MessagingMessage was created.'
},
userId: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
},
user: {
type: types.object,
ref: 'User',
optional: bool.true, nullable: bool.false,
},
text: {
type: types.string,
optional: bool.false, nullable: bool.true,
},
fileId: {
type: types.string,
optional: bool.true, nullable: bool.true,
format: 'id',
},
file: {
type: types.object,
optional: bool.true, nullable: bool.true,
ref: 'DriveFile',
},
recipientId: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
},
recipient: {
type: types.object,
optional: bool.true, nullable: bool.false,
ref: 'User'
},
isRead: {
type: types.boolean,
optional: bool.true, nullable: bool.false,
},
},
};

View File

@ -1,8 +1,11 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { Users } from '..'; import { Users } from '..';
import rap from '@prezzemolo/rap';
import { Muting } from '../entities/muting'; import { Muting } from '../entities/muting';
import { ensure } from '../../prelude/ensure'; import { ensure } from '../../prelude/ensure';
import { awaitAll } from '../../prelude/await-all';
import { types, bool, SchemaType } from '../../misc/schema';
export type PackedMuting = SchemaType<typeof packedMutingSchema>;
@EntityRepository(Muting) @EntityRepository(Muting)
export class MutingRepository extends Repository<Muting> { export class MutingRepository extends Repository<Muting> {
@ -16,14 +19,47 @@ export class MutingRepository extends Repository<Muting> {
public async pack( public async pack(
src: Muting['id'] | Muting, src: Muting['id'] | Muting,
me?: any me?: any
) { ): Promise<PackedMuting> {
const muting = typeof src === 'object' ? src : await this.findOne(src).then(ensure); const muting = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({ return await awaitAll({
id: muting.id, id: muting.id,
createdAt: muting.createdAt.toISOString(),
muteeId: muting.muteeId,
mutee: Users.pack(muting.muteeId, me, { mutee: Users.pack(muting.muteeId, me, {
detail: true detail: true
}) })
}); });
} }
} }
export const packedMutingSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this muting.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the muting was created.'
},
muteeId: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
},
mutee: {
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'User',
description: 'The mutee.'
},
}
};

View File

@ -2,18 +2,54 @@ import { EntityRepository, Repository } from 'typeorm';
import { NoteReaction } from '../entities/note-reaction'; import { NoteReaction } from '../entities/note-reaction';
import { Users } from '..'; import { Users } from '..';
import { ensure } from '../../prelude/ensure'; import { ensure } from '../../prelude/ensure';
import { types, bool, SchemaType } from '../../misc/schema';
export type PackedNoteReaction = SchemaType<typeof packedNoteReactionSchema>;
@EntityRepository(NoteReaction) @EntityRepository(NoteReaction)
export class NoteReactionRepository extends Repository<NoteReaction> { export class NoteReactionRepository extends Repository<NoteReaction> {
public async pack( public async pack(
src: NoteReaction['id'] | NoteReaction, src: NoteReaction['id'] | NoteReaction,
me?: any me?: any
) { ): Promise<PackedNoteReaction> {
const reaction = typeof src === 'object' ? src : await this.findOne(src).then(ensure); const reaction = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return { return {
id: reaction.id, id: reaction.id,
createdAt: reaction.createdAt.toISOString(),
user: await Users.pack(reaction.userId, me), user: await Users.pack(reaction.userId, me),
type: reaction.reaction,
}; };
} }
} }
export const packedNoteReactionSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this reaction.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the reaction was created.'
},
user: {
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'User',
description: 'User who performed this reaction.'
},
type: {
type: types.string,
optional: bool.false, nullable: bool.false,
description: 'The reaction type.'
},
},
};

View File

@ -4,8 +4,11 @@ import { User } from '../entities/user';
import { unique, concat } from '../../prelude/array'; import { unique, concat } from '../../prelude/array';
import { nyaize } from '../../misc/nyaize'; import { nyaize } from '../../misc/nyaize';
import { Emojis, Users, Apps, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..'; import { Emojis, Users, Apps, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..';
import rap from '@prezzemolo/rap';
import { ensure } from '../../prelude/ensure'; import { ensure } from '../../prelude/ensure';
import { SchemaType, types, bool } from '../../misc/schema';
import { awaitAll } from '../../prelude/await-all';
export type PackedNote = SchemaType<typeof packedNoteSchema>;
@EntityRepository(Note) @EntityRepository(Note)
export class NoteRepository extends Repository<Note> { export class NoteRepository extends Repository<Note> {
@ -13,18 +16,18 @@ export class NoteRepository extends Repository<Note> {
return x.trim().length <= 100; return x.trim().length <= 100;
} }
private async hideNote(packedNote: any, meId: User['id'] | null) { private async hideNote(packedNote: PackedNote, meId: User['id'] | null) {
let hide = false; let hide = false;
// visibility が specified かつ自分が指定されていなかったら非表示 // visibility が specified かつ自分が指定されていなかったら非表示
if (packedNote.visibility == 'specified') { if (packedNote.visibility === 'specified') {
if (meId == null) { if (meId == null) {
hide = true; hide = true;
} else if (meId === packedNote.userId) { } else if (meId === packedNote.userId) {
hide = false; hide = false;
} else { } else {
// 指定されているかどうか // 指定されているかどうか
const specified = packedNote.visibleUserIds.some((id: any) => meId === id); const specified = packedNote.visibleUserIds!.some((id: any) => meId === id);
if (specified) { if (specified) {
hide = false; hide = false;
@ -40,10 +43,10 @@ export class NoteRepository extends Repository<Note> {
hide = true; hide = true;
} else if (meId === packedNote.userId) { } else if (meId === packedNote.userId) {
hide = false; hide = false;
} else if (packedNote.reply && (meId === packedNote.reply.userId)) { } else if (packedNote.reply && (meId === (packedNote.reply as PackedNote).userId)) {
// 自分の投稿に対するリプライ // 自分の投稿に対するリプライ
hide = false; hide = false;
} else if (packedNote.mentions && packedNote.mentions.some((id: any) => meId === id)) { } else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
// 自分へのメンション // 自分へのメンション
hide = false; hide = false;
} else { } else {
@ -62,14 +65,13 @@ export class NoteRepository extends Repository<Note> {
} }
if (hide) { if (hide) {
packedNote.visibleUserIds = null; packedNote.visibleUserIds = undefined;
packedNote.fileIds = []; packedNote.fileIds = [];
packedNote.files = []; packedNote.files = [];
packedNote.text = null; packedNote.text = null;
packedNote.poll = null; packedNote.poll = undefined;
packedNote.cw = null; packedNote.cw = null;
packedNote.tags = []; packedNote.geo = undefined;
packedNote.geo = null;
packedNote.isHidden = true; packedNote.isHidden = true;
} }
} }
@ -92,7 +94,7 @@ export class NoteRepository extends Repository<Note> {
detail?: boolean; detail?: boolean;
skipHide?: boolean; skipHide?: boolean;
} }
): Promise<Record<string, any>> { ): Promise<PackedNote> {
const opts = Object.assign({ const opts = Object.assign({
detail: true, detail: true,
skipHide: false skipHide: false
@ -159,9 +161,9 @@ export class NoteRepository extends Repository<Note> {
const reactionEmojis = unique(concat([note.emojis, Object.keys(note.reactions)])); const reactionEmojis = unique(concat([note.emojis, Object.keys(note.reactions)]));
const packed = await rap({ const packed = await awaitAll({
id: note.id, id: note.id,
createdAt: note.createdAt, createdAt: note.createdAt.toISOString(),
app: note.appId ? Apps.pack(note.appId) : undefined, app: note.appId ? Apps.pack(note.appId) : undefined,
userId: note.userId, userId: note.userId,
user: Users.pack(note.user || note.userId, meId), user: Users.pack(note.user || note.userId, meId),
@ -178,12 +180,12 @@ export class NoteRepository extends Repository<Note> {
name: In(reactionEmojis), name: In(reactionEmojis),
host: host host: host
}) : [], }) : [],
tags: note.tags,
fileIds: note.fileIds, fileIds: note.fileIds,
files: DriveFiles.packMany(note.fileIds), files: DriveFiles.packMany(note.fileIds),
replyId: note.replyId, replyId: note.replyId,
renoteId: note.renoteId, renoteId: note.renoteId,
uri: note.uri, mentions: note.mentions.length > 0 ? note.mentions : undefined,
uri: note.uri || undefined,
...(opts.detail ? { ...(opts.detail ? {
reply: note.replyId ? this.pack(note.replyId, meId, { reply: note.replyId ? this.pack(note.replyId, meId, {
@ -213,3 +215,127 @@ export class NoteRepository extends Repository<Note> {
return packed; return packed;
} }
} }
export const packedNoteSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this Note.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the Note was created on Misskey.'
},
text: {
type: types.string,
optional: bool.false, nullable: bool.true,
},
cw: {
type: types.string,
optional: bool.true, nullable: bool.true,
},
userId: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
},
user: {
type: types.object,
ref: 'User',
optional: bool.false, nullable: bool.false,
},
replyId: {
type: types.string,
optional: bool.true, nullable: bool.true,
format: 'id',
example: 'xxxxxxxxxx',
},
renoteId: {
type: types.string,
optional: bool.true, nullable: bool.true,
format: 'id',
example: 'xxxxxxxxxx',
},
reply: {
type: types.object,
optional: bool.true, nullable: bool.true,
ref: 'Note'
},
renote: {
type: types.object,
optional: bool.true, nullable: bool.true,
ref: 'Note'
},
viaMobile: {
type: types.boolean,
optional: bool.true, nullable: bool.false,
},
isHidden: {
type: types.boolean,
optional: bool.true, nullable: bool.false,
},
visibility: {
type: types.string,
optional: bool.false, nullable: bool.false,
},
mentions: {
type: types.array,
optional: bool.true, nullable: bool.false,
items: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id'
}
},
visibleUserIds: {
type: types.array,
optional: bool.true, nullable: bool.false,
items: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id'
}
},
fileIds: {
type: types.array,
optional: bool.true, nullable: bool.false,
items: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id'
}
},
files: {
type: types.array,
optional: bool.true, nullable: bool.false,
items: {
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'DriveFile'
}
},
tags: {
type: types.array,
optional: bool.true, nullable: bool.false,
items: {
type: types.string,
optional: bool.false, nullable: bool.false,
}
},
poll: {
type: types.object,
optional: bool.true, nullable: bool.true,
},
geo: {
type: types.object,
optional: bool.true, nullable: bool.true,
},
},
};

View File

@ -1,8 +1,11 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { Users, Notes } from '..'; import { Users, Notes } from '..';
import rap from '@prezzemolo/rap';
import { Notification } from '../entities/notification'; import { Notification } from '../entities/notification';
import { ensure } from '../../prelude/ensure'; import { ensure } from '../../prelude/ensure';
import { awaitAll } from '../../prelude/await-all';
import { types, bool, SchemaType } from '../../misc/schema';
export type PackedNotification = SchemaType<typeof packedNotificationSchema>;
@EntityRepository(Notification) @EntityRepository(Notification)
export class NotificationRepository extends Repository<Notification> { export class NotificationRepository extends Repository<Notification> {
@ -14,12 +17,12 @@ export class NotificationRepository extends Repository<Notification> {
public async pack( public async pack(
src: Notification['id'] | Notification, src: Notification['id'] | Notification,
) { ): Promise<PackedNotification> {
const notification = typeof src === 'object' ? src : await this.findOne(src).then(ensure); const notification = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({ return await awaitAll({
id: notification.id, id: notification.id,
createdAt: notification.createdAt, createdAt: notification.createdAt.toISOString(),
type: notification.type, type: notification.type,
userId: notification.notifierId, userId: notification.notifierId,
user: Users.pack(notification.notifier || notification.notifierId), user: Users.pack(notification.notifier || notification.notifierId),
@ -46,3 +49,39 @@ export class NotificationRepository extends Repository<Notification> {
}); });
} }
} }
export const packedNotificationSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this notification.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the notification was created.'
},
type: {
type: types.string,
optional: bool.false, nullable: bool.false,
enum: ['follow', 'receiveFollowRequest', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote'],
description: 'The type of the notification.'
},
userId: {
type: types.string,
optional: bool.true, nullable: bool.true,
format: 'id',
},
user: {
type: types.object,
ref: 'User',
optional: bool.true, nullable: bool.true,
},
}
};

View File

@ -2,12 +2,15 @@ import { EntityRepository, Repository } from 'typeorm';
import { UserList } from '../entities/user-list'; import { UserList } from '../entities/user-list';
import { ensure } from '../../prelude/ensure'; import { ensure } from '../../prelude/ensure';
import { UserListJoinings } from '..'; import { UserListJoinings } from '..';
import { bool, types, SchemaType } from '../../misc/schema';
export type PackedUserList = SchemaType<typeof packedUserListSchema>;
@EntityRepository(UserList) @EntityRepository(UserList)
export class UserListRepository extends Repository<UserList> { export class UserListRepository extends Repository<UserList> {
public async pack( public async pack(
src: UserList['id'] | UserList, src: UserList['id'] | UserList,
) { ): Promise<PackedUserList> {
const userList = typeof src === 'object' ? src : await this.findOne(src).then(ensure); const userList = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
const users = await UserListJoinings.find({ const users = await UserListJoinings.find({
@ -16,8 +19,43 @@ export class UserListRepository extends Repository<UserList> {
return { return {
id: userList.id, id: userList.id,
createdAt: userList.createdAt.toISOString(),
name: userList.name, name: userList.name,
userIds: users.map(x => x.userId) userIds: users.map(x => x.userId)
}; };
} }
} }
export const packedUserListSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this UserList.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the UserList was created.'
},
name: {
type: types.string,
optional: bool.false, nullable: bool.false,
description: 'The name of the UserList.'
},
userIds: {
type: types.array,
nullable: bool.false, optional: bool.true,
items: {
type: types.string,
nullable: bool.false, optional: bool.false,
format: 'id',
}
},
},
};

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