Compare commits

...

59 Commits

Author SHA1 Message Date
syuilo dea4a7b389 Merge branch 'develop' 2019-04-22 00:50:14 +09:00
syuilo 0caf9b0706 11.2.2 2019-04-22 00:49:36 +09:00
syuilo 5f655c9fd7 Update dependencies 🚀 2019-04-22 00:43:15 +09:00
syuilo 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
syuilo f9ff621d4a 2段階認証を有効にするとログインできない問題を修正 2019-04-22 00:38:37 +09:00
かひわし4(バージョン1) 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
MeiMei a78475844a Fix: リモートユーザーの修復処理が自動的に実行されない など for v11 (#4764)
* Fix #4761

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

* Revert "wip"

This reverts commit 6212831ce3.

* improve list index

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

* Resolve #4554

* Revert "confirm silence"

This reverts commit e1dbdc2bfc.
2019-04-18 03:37:49 +09:00
MeiMei 9ec6afa375 confirm on user menu (#4553) 2019-04-18 03:36:06 +09:00
MeiMei adff5382ca confirm silence (#4560) 2019-04-18 03:33:51 +09:00
tamaina 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
MeiMei f7b1ef0690 アンケートウィジットでもMFMを使用するように v11 (#4741)
* MFM in poll

* use mfm
2019-04-18 03:14:04 +09:00
syuilo 929982117f Merge branch 'develop' 2019-04-18 01:12:21 +09:00
syuilo 56a530d769 11.1.6 2019-04-18 01:12:03 +09:00
syuilo 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
かひわし4(バージョン1) 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
syuilo 71813e03ee Fix bug 2019-04-18 01:05:40 +09:00
syuilo 9b05b6ef28 Improve readability 2019-04-18 00:57:06 +09:00
syuilo 54a87b25b3 Remove unused imports 2019-04-18 00:56:10 +09:00
MeiMei 55e97864bd Fix: v11で未認知ユーザーからActivityが飛んできた場合に処理できない (#4733)
* Fix: inboxに未知のユーザーが来ると処理できない

* こうかな
2019-04-18 00:53:00 +09:00
syuilo 95733c9490 [MFM] Better hashtag parsing 2019-04-18 00:40:56 +09:00
syuilo e19ae644f1 Fix indent 2019-04-18 00:13:31 +09:00
syuilo ac4ea25267 Better error handling 2019-04-18 00:09:08 +09:00
syuilo 611e4f34dc Merge branch 'develop' 2019-04-17 19:37:32 +09:00
syuilo faf017f333 11.1.5 2019-04-17 19:36:58 +09:00
syuilo 5eec896615 Better avgColor 2019-04-17 17:13:49 +09:00
syuilo 17f35174ea 🎨 2019-04-17 17:01:57 +09:00
syuilo bf71b31123 Update CONTRIBUTING.md 2019-04-17 16:59:39 +09:00
syuilo 9399a44c82 Fix error 2019-04-17 16:50:50 +09:00
syuilo c96418806f Disable sql log 2019-04-17 16:45:31 +09:00
syuilo 7945eddef6 Clean up 2019-04-17 14:39:45 +09:00
syuilo 0ede390fef Refactor 2019-04-17 14:32:59 +09:00
syuilo 85959a3b9b Fix #4721 Fix #4722 2019-04-17 14:30:31 +09:00
syuilo 946c3a25b9 Clean up 2019-04-17 07:25:34 +09:00
syuilo f9d697128a Update schemas.ts 2019-04-17 04:32:04 +09:00
92 changed files with 1072 additions and 593 deletions
+2 -32
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:
+3 -4
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
+74
View File
@@ -5,6 +5,80 @@ 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.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
+33 -3
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 {
+2 -3
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
+7 -5
View File
@@ -124,8 +124,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/1.jpeg?token-time=2145916800&token-hash=3aMtpAjhwf01G3Uf8iIKYL8FUXXgxV7NvoQLne7lAKE%3D" alt="Ryosuke Yamamoto" 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 +135,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">Ryosuke Yamamoto</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 +148,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 +158,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:** Thu, 18 Apr 2019 23:38:06 UTC
<!-- PATREON_END --> <!-- PATREON_END -->
:four_leaf_clover: Copyright :four_leaf_clover: Copyright
+19 -7
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."
+12 -7
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"
+47 -13
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"
+53 -2
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:
+17 -13
View File
@@ -68,10 +68,13 @@ 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"
"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 +138,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"
@@ -274,6 +278,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"
@@ -653,6 +658,8 @@ 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:
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 +960,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 +1007,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 +1383,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 +1438,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 +1459,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 +1593,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 "
+25 -5
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: "📦 始めましょう"
+9 -9
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: "権限"
+13 -13
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: "권한"
+6 -1
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:
+4 -1
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"
+9 -7
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:
+79 -40
View File
@@ -70,10 +70,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 +145,7 @@ common:
apps: "应用程序" apps: "应用程序"
tags: "标签" tags: "标签"
mute-and-block: "屏蔽/拉黑" mute-and-block: "屏蔽/拉黑"
blocking: "屏蔽" blocking: "拉黑"
security: "安全性" security: "安全性"
signin: "登录历史" signin: "登录历史"
password: "密码" password: "密码"
@@ -146,7 +162,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 +205,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 +257,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 +297,7 @@ common:
nav: "导航" nav: "导航"
tips: "提示" tips: "提示"
hashtags: "标签" hashtags: "标签"
queue: "队列"
dev: "构建应用程序失败,请再试一次。" dev: "构建应用程序失败,请再试一次。"
ai-chan-kawaii: "小蓝真可爱" ai-chan-kawaii: "小蓝真可爱"
you: "您" you: "您"
@@ -321,8 +338,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 +363,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 +480,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 +489,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 +502,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 +690,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 +735,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 +770,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 +867,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 +891,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 +922,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 +978,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 +994,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 +1041,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 +1218,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 +1414,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 +1433,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 +1486,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 +1511,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 +1617,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 +1647,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访问此处请求的功能。"
+10 -10
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.2.2",
"codename": "daybreak", "codename": "daybreak",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -94,7 +94,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,7 +112,7 @@
"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", "elasticsearch": "15.4.1",
"emojilib": "2.4.0", "emojilib": "2.4.0",
@@ -138,10 +138,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 +161,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 +210,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 +240,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",
+4
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({
@@ -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;
+64
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('@')) {
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)}`);
}
@@ -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
@@ -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'
}); });
@@ -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
}; };
}, },
@@ -32,7 +32,7 @@ export default Vue.extend({
props: { props: {
files: { files: {
type: Object, type: Array,
required: true required: true
}, },
detachMediaFn: { detachMediaFn: {
@@ -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({
@@ -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;
@@ -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: {
@@ -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: title }) => {
if (canceled) return;
const list = await this.$root.api('users/lists/create', {
title
});
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>
@@ -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>
@@ -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>
@@ -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'
}); });
@@ -123,7 +123,7 @@ export default Vue.extend({
}, },
fetchMore() { 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);
@@ -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>
@@ -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();
@@ -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)}`); });
}
} }
} }
}); });
@@ -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() {
@@ -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
@@ -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();
} }
} }
}); });
@@ -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>
@@ -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();
} }
} }
}); });
@@ -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: {
@@ -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>
@@ -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) {
+5 -1
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,
@@ -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);
});
}); });
}, },
@@ -124,7 +124,7 @@ export default Vue.extend({
}, },
fetchMore() { 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);
@@ -66,6 +66,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 +134,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)}`);
}
}); });
}, },
@@ -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();
} }
} }
}); });
@@ -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>
+41 -5
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,
});
} }
} }
}); });
@@ -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();
@@ -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}`);
});
} }
} }
}); });
@@ -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);
} }
} }
}); });
+2 -1
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
}); });
+2 -4
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,
+1 -1
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);
+1 -1
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();
} }
+1
View File
@@ -26,6 +26,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({
+2 -2
View File
@@ -178,12 +178,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, {
+2 -1
View File
@@ -82,7 +82,7 @@ export class UserRepository extends Repository<User> {
const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null; const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null;
const pins = opts.detail ? await UserNotePinings.find({ userId: user.id }) : []; const pins = opts.detail ? await UserNotePinings.find({ userId: user.id }) : [];
const profile = opts.detail ? await UserProfiles.findOne({ userId: user.id }).then(ensure) : null; const profile = opts.detail ? await UserProfiles.findOne(user.id).then(ensure) : null;
const falsy = opts.detail ? false : undefined; const falsy = opts.detail ? false : undefined;
@@ -144,6 +144,7 @@ export class UserRepository extends Repository<User> {
autoWatch: profile!.autoWatch, autoWatch: profile!.autoWatch,
alwaysMarkNsfw: profile!.alwaysMarkNsfw, alwaysMarkNsfw: profile!.alwaysMarkNsfw,
carefulBot: profile!.carefulBot, carefulBot: profile!.carefulBot,
twoFactorEnabled: profile!.twoFactorEnabled,
hasUnreadMessagingMessage: MessagingMessages.count({ hasUnreadMessagingMessage: MessagingMessages.count({
where: { where: {
recipientId: user.id, recipientId: user.id,
+35 -64
View File
@@ -1,6 +1,5 @@
import * as Bull from 'bull'; import * as Bull from 'bull';
import * as httpSignature from 'http-signature'; import * as httpSignature from 'http-signature';
import parseAcct from '../../misc/acct/parse';
import { IRemoteUser } from '../../models/entities/user'; import { IRemoteUser } from '../../models/entities/user';
import perform from '../../remote/activitypub/perform'; import perform from '../../remote/activitypub/perform';
import { resolvePerson, updatePerson } from '../../remote/activitypub/models/person'; import { resolvePerson, updatePerson } from '../../remote/activitypub/models/person';
@@ -12,7 +11,7 @@ import { Instances, Users, UserPublickeys } from '../../models';
import { instanceChart } from '../../services/chart'; import { instanceChart } from '../../services/chart';
import { UserPublickey } from '../../models/entities/user-publickey'; import { UserPublickey } from '../../models/entities/user-publickey';
import fetchMeta from '../../misc/fetch-meta'; import fetchMeta from '../../misc/fetch-meta';
import { toPuny, toPunyNullable } from '../../misc/convert-host'; import { toPuny } from '../../misc/convert-host';
import { validActor } from '../../remote/activitypub/type'; import { validActor } from '../../remote/activitypub/type';
import { ensure } from '../../prelude/ensure'; import { ensure } from '../../prelude/ensure';
@@ -35,68 +34,49 @@ export default async (job: Bull.Job): Promise<void> => {
let key: UserPublickey; let key: UserPublickey;
if (keyIdLower.startsWith('acct:')) { if (keyIdLower.startsWith('acct:')) {
const acct = parseAcct(keyIdLower.slice('acct:'.length)); logger.warn(`Old keyId is no longer supported. ${keyIdLower}`);
const host = toPunyNullable(acct.host); return;
const username = toPuny(acct.username); }
if (host === null) { // アクティビティ内のホストの検証
logger.warn(`request was made by local user: @${username}`); const host = toPuny(new URL(signature.keyId).hostname);
return; try {
ValidateActivity(activity, host);
} catch (e) {
logger.warn(e.message);
return;
}
// ブロックしてたら中断
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
const meta = await fetchMeta();
if (meta.blockedHosts.includes(host)) {
logger.info(`Blocked request: ${host}`);
return;
}
const _key = await UserPublickeys.findOne({
keyId: signature.keyId
});
if (_key) {
// 登録済みユーザー
user = await Users.findOne(_key.userId) as IRemoteUser;
key = _key;
} else {
// 未登録ユーザーの場合はリモート解決
user = await resolvePerson(activity.actor) as IRemoteUser;
if (user == null) {
throw new Error('failed to resolve user');
} }
// アクティビティ内のホストの検証
try {
ValidateActivity(activity, host);
} catch (e) {
logger.warn(e.message);
return;
}
// ブロックしてたら中断
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
const meta = await fetchMeta();
if (meta.blockedHosts.includes(host)) {
logger.info(`Blocked request: ${host}`);
return;
}
user = await Users.findOne({
usernameLower: username.toLowerCase(),
host: host
}) as IRemoteUser;
key = await UserPublickeys.findOne(user.id).then(ensure); key = await UserPublickeys.findOne(user.id).then(ensure);
} else {
// アクティビティ内のホストの検証
const host = toPuny(new URL(signature.keyId).hostname);
try {
ValidateActivity(activity, host);
} catch (e) {
logger.warn(e.message);
return;
}
// ブロックしてたら中断
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
const meta = await fetchMeta();
if (meta.blockedHosts.includes(host)) {
logger.info(`Blocked request: ${host}`);
return;
}
key = await UserPublickeys.findOne({
keyId: signature.keyId
}).then(ensure);
user = await Users.findOne(key.userId) as IRemoteUser;
} }
// Update Person activityの場合は、ここで署名検証/更新処理まで実施して終了 // Update Person activityの場合は、ここで署名検証/更新処理まで実施して終了
if (activity.type === 'Update') { if (activity.type === 'Update') {
if (activity.object && validActor.includes(activity.object.type)) { if (activity.object && validActor.includes(activity.object.type)) {
if (user == null) { if (!httpSignature.verifySignature(signature, key.keyPem)) {
logger.warn('Update activity received, but user not registed.');
} else if (!httpSignature.verifySignature(signature, key.keyPem)) {
logger.warn('Update activity received, but signature verification failed.'); logger.warn('Update activity received, but signature verification failed.');
} else { } else {
updatePerson(activity.actor, null, activity.object); updatePerson(activity.actor, null, activity.object);
@@ -105,15 +85,6 @@ export default async (job: Bull.Job): Promise<void> => {
} }
} }
// アクティビティを送信してきたユーザーがまだMisskeyサーバーに登録されていなかったら登録する
if (user == null) {
user = await resolvePerson(activity.actor) as IRemoteUser;
}
if (user == null) {
throw new Error('failed to resolve user');
}
if (!httpSignature.verifySignature(signature, key.keyPem)) { if (!httpSignature.verifySignature(signature, key.keyPem)) {
logger.error('signature verification failed'); logger.error('signature verification failed');
return; return;
+14 -14
View File
@@ -20,21 +20,21 @@ export default async (actor: IRemoteUser, activity: IDelete): Promise<void> => {
const uri = (object as any).id; const uri = (object as any).id;
switch (object.type) { switch (object.type) {
case 'Note': case 'Note':
case 'Question': case 'Question':
case 'Article': case 'Article':
deleteNote(actor, uri);
break;
case 'Tombstone':
const note = await Notes.findOne({ uri });
if (note != null) {
deleteNote(actor, uri); deleteNote(actor, uri);
} break;
break;
default: case 'Tombstone':
apLogger.warn(`Unknown type: ${object.type}`); const note = await Notes.findOne({ uri });
break; if (note != null) {
deleteNote(actor, uri);
}
break;
default:
apLogger.warn(`Unknown type: ${object.type}`);
break;
} }
}; };
+3 -1
View File
@@ -2,6 +2,7 @@ import { IRemoteUser } from '../../../models/entities/user';
import { ILike } from '../type'; import { ILike } from '../type';
import create from '../../../services/note/reaction/create'; import create from '../../../services/note/reaction/create';
import { Notes } from '../../../models'; import { Notes } from '../../../models';
import { apLogger } from '../logger';
export default async (actor: IRemoteUser, activity: ILike) => { export default async (actor: IRemoteUser, activity: ILike) => {
const id = typeof activity.object == 'string' ? activity.object : activity.object.id; const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
@@ -14,7 +15,8 @@ export default async (actor: IRemoteUser, activity: ILike) => {
const note = await Notes.findOne(noteId); const note = await Notes.findOne(noteId);
if (note == null) { if (note == null) {
throw new Error(); apLogger.warn(`Like activity recivied, but no such note: ${id}`, { id });
return;
} }
await create(actor, note, activity._misskey_reaction); await create(actor, note, activity._misskey_reaction);
+10 -16
View File
@@ -271,11 +271,6 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
} }
//#endregion //#endregion
// 繋がらないインスタンスに何回も試行するのを防ぐ, 後続の同様処理の連続試行を防ぐ ため 試行前にも更新する
await Users.update(exist.id, {
lastFetchedAt: new Date(),
});
if (resolver == null) resolver = new Resolver(); if (resolver == null) resolver = new Resolver();
const object = hint || await resolver.resolve(uri) as any; const object = hint || await resolver.resolve(uri) as any;
@@ -318,11 +313,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined), sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
featured: person.featured, featured: person.featured,
emojis: emojiNames, emojis: emojiNames,
description: person.summary ? fromHtml(person.summary) : null,
name: person.name, name: person.name,
url: person.url,
endpoints: person.endpoints,
fields,
tags, tags,
isBot: object.type == 'Service', isBot: object.type == 'Service',
isCat: (person as any).isCat === true, isCat: (person as any).isCat === true,
@@ -350,13 +341,16 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
}); });
await UserProfiles.update({ userId: exist.id }, { await UserProfiles.update({ userId: exist.id }, {
twitterUserId: services.twitter.userId, url: person.url,
twitterScreenName: services.twitter.screenName, fields,
githubId: services.github.id, description: person.summary ? fromHtml(person.summary) : null,
githubLogin: services.github.login, twitterUserId: services.twitter ? services.twitter.userId : null,
discordId: services.discord.id, twitterScreenName: services.twitter ? services.twitter.screenName : null,
discordUsername: services.discord.username, githubId: services.github ? services.github.id : null,
discordDiscriminator: services.discord.discriminator, githubLogin: services.github ? services.github.login : null,
discordId: services.discord ? services.discord.id : null,
discordUsername: services.discord ? services.discord.username : null,
discordDiscriminator: services.discord ? services.discord.discriminator : null,
}); });
// ハッシュタグ更新 // ハッシュタグ更新
+1 -1
View File
@@ -17,7 +17,7 @@ export async function renderPerson(user: ILocalUser) {
const [avatar, banner, profile] = await Promise.all([ const [avatar, banner, profile] = await Promise.all([
user.avatarId ? DriveFiles.findOne(user.avatarId) : Promise.resolve(undefined), user.avatarId ? DriveFiles.findOne(user.avatarId) : Promise.resolve(undefined),
user.bannerId ? DriveFiles.findOne(user.bannerId) : Promise.resolve(undefined), user.bannerId ? DriveFiles.findOne(user.bannerId) : Promise.resolve(undefined),
UserProfiles.findOne({ userId: user.id }).then(ensure) UserProfiles.findOne(user.id).then(ensure)
]); ]);
const attachment: { const attachment: {
+10 -4
View File
@@ -37,7 +37,7 @@ export async function resolveUser(username: string, host: string | null, option?
}); });
} }
const user = await Users.findOne({ usernameLower, host }, option); const user = await Users.findOne({ usernameLower, host }, option) as IRemoteUser;
const acctLower = `${usernameLower}@${host}`; const acctLower = `${usernameLower}@${host}`;
@@ -48,14 +48,20 @@ export async function resolveUser(username: string, host: string | null, option?
return await createPerson(self.href); return await createPerson(self.href);
} }
if (resync) { // resyncオプション OR ユーザー情報が古い場合は、WebFilgerからやりなおして返す
if (resync || user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) {
// 繋がらないインスタンスに何回も試行するのを防ぐ, 後続の同様処理の連続試行を防ぐ ため 試行前にも更新する
await Users.update(user.id, {
lastFetchedAt: new Date(),
});
logger.info(`try resync: ${acctLower}`); logger.info(`try resync: ${acctLower}`);
const self = await resolveSelf(acctLower); const self = await resolveSelf(acctLower);
if ((user as IRemoteUser).uri !== self.href) { if (user.uri !== self.href) {
// if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping. // if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping.
logger.info(`uri missmatch: ${acctLower}`); logger.info(`uri missmatch: ${acctLower}`);
logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${(user as IRemoteUser).uri} to ${self.href}`); logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`);
// validate uri // validate uri
const uri = new URL(self.href); const uri = new URL(self.href);
+15
View File
@@ -0,0 +1,15 @@
import define from '../define';
import endpoints from '../endpoints';
export const meta = {
requireCredential: false,
tags: ['meta'],
params: {
},
};
export default define(meta, async () => {
return endpoints.map(x => x.name);
});
+1 -1
View File
@@ -19,7 +19,7 @@ export const meta = {
export default define(meta, async (ps, user) => { export default define(meta, async (ps, user) => {
const token = ps.token.replace(/\s/g, ''); const token = ps.token.replace(/\s/g, '');
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); const profile = await UserProfiles.findOne(user.id).then(ensure);
if (profile.twoFactorTempSecret == null) { if (profile.twoFactorTempSecret == null) {
throw new Error('二段階認証の設定が開始されていません'); throw new Error('二段階認証の設定が開始されていません');
+1 -1
View File
@@ -20,7 +20,7 @@ export const meta = {
}; };
export default define(meta, async (ps, user) => { export default define(meta, async (ps, user) => {
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); const profile = await UserProfiles.findOne(user.id).then(ensure);
// Compare password // Compare password
const same = await bcrypt.compare(ps.password, profile.password!); const same = await bcrypt.compare(ps.password, profile.password!);
+1 -1
View File
@@ -17,7 +17,7 @@ export const meta = {
}; };
export default define(meta, async (ps, user) => { export default define(meta, async (ps, user) => {
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); const profile = await UserProfiles.findOne(user.id).then(ensure);
// Compare password // Compare password
const same = await bcrypt.compare(ps.password, profile.password!); const same = await bcrypt.compare(ps.password, profile.password!);
@@ -21,7 +21,7 @@ export const meta = {
}; };
export default define(meta, async (ps, user) => { export default define(meta, async (ps, user) => {
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); const profile = await UserProfiles.findOne(user.id).then(ensure);
// Compare password // Compare password
const same = await bcrypt.compare(ps.currentPassword, profile.password!); const same = await bcrypt.compare(ps.currentPassword, profile.password!);
+1 -1
View File
@@ -17,7 +17,7 @@ export const meta = {
}; };
export default define(meta, async (ps, user) => { export default define(meta, async (ps, user) => {
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); const profile = await UserProfiles.findOne(user.id).then(ensure);
// Compare password // Compare password
const same = await bcrypt.compare(ps.password, profile.password!); const same = await bcrypt.compare(ps.password, profile.password!);
@@ -19,7 +19,7 @@ export const meta = {
}; };
export default define(meta, async (ps, user) => { export default define(meta, async (ps, user) => {
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); const profile = await UserProfiles.findOne(user.id).then(ensure);
// Compare password // Compare password
const same = await bcrypt.compare(ps.password, profile.password!); const same = await bcrypt.compare(ps.password, profile.password!);
+1 -1
View File
@@ -33,7 +33,7 @@ export const meta = {
}; };
export default define(meta, async (ps, user) => { export default define(meta, async (ps, user) => {
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); const profile = await UserProfiles.findOne(user.id).then(ensure);
// Compare password // Compare password
const same = await bcrypt.compare(ps.password, profile.password!); const same = await bcrypt.compare(ps.password, profile.password!);
+20 -13
View File
@@ -13,6 +13,7 @@ import { ApiError } from '../../error';
import { Users, DriveFiles, UserProfiles } from '../../../../models'; import { Users, DriveFiles, UserProfiles } from '../../../../models';
import { User } from '../../../../models/entities/user'; import { User } from '../../../../models/entities/user';
import { UserProfile } from '../../../../models/entities/user-profile'; import { UserProfile } from '../../../../models/entities/user-profile';
import { ensure } from '../../../../prelude/ensure';
export const meta = { export const meta = {
desc: { desc: {
@@ -157,22 +158,24 @@ export default define(meta, async (ps, user, app) => {
const isSecure = user != null && app == null; const isSecure = user != null && app == null;
const updates = {} as Partial<User>; const updates = {} as Partial<User>;
const profile = {} as Partial<UserProfile>; const profileUpdates = {} as Partial<UserProfile>;
const profile = await UserProfiles.findOne(user.id).then(ensure);
if (ps.name !== undefined) updates.name = ps.name; if (ps.name !== undefined) updates.name = ps.name;
if (ps.description !== undefined) profile.description = ps.description; if (ps.description !== undefined) profileUpdates.description = ps.description;
//if (ps.lang !== undefined) updates.lang = ps.lang; //if (ps.lang !== undefined) updates.lang = ps.lang;
if (ps.location !== undefined) profile.location = ps.location; if (ps.location !== undefined) profileUpdates.location = ps.location;
if (ps.birthday !== undefined) profile.birthday = ps.birthday; if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId; if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId;
if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId; if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId;
if (typeof ps.isLocked == 'boolean') updates.isLocked = ps.isLocked; if (typeof ps.isLocked == 'boolean') updates.isLocked = ps.isLocked;
if (typeof ps.isBot == 'boolean') updates.isBot = ps.isBot; if (typeof ps.isBot == 'boolean') updates.isBot = ps.isBot;
if (typeof ps.carefulBot == 'boolean') profile.carefulBot = ps.carefulBot; if (typeof ps.carefulBot == 'boolean') profileUpdates.carefulBot = ps.carefulBot;
if (typeof ps.autoAcceptFollowed == 'boolean') profile.autoAcceptFollowed = ps.autoAcceptFollowed; if (typeof ps.autoAcceptFollowed == 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
if (typeof ps.isCat == 'boolean') updates.isCat = ps.isCat; if (typeof ps.isCat == 'boolean') updates.isCat = ps.isCat;
if (typeof ps.autoWatch == 'boolean') profile.autoWatch = ps.autoWatch; if (typeof ps.autoWatch == 'boolean') profileUpdates.autoWatch = ps.autoWatch;
if (typeof ps.alwaysMarkNsfw == 'boolean') profile.alwaysMarkNsfw = ps.alwaysMarkNsfw; if (typeof ps.alwaysMarkNsfw == 'boolean') profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
if (ps.avatarId) { if (ps.avatarId) {
const avatar = await DriveFiles.findOne(ps.avatarId); const avatar = await DriveFiles.findOne(ps.avatarId);
@@ -201,16 +204,20 @@ export default define(meta, async (ps, user, app) => {
} }
//#region emojis/tags //#region emojis/tags
let emojis = [] as string[]; let emojis = [] as string[];
let tags = [] as string[]; let tags = [] as string[];
if (updates.name != null) { const newName = updates.name === undefined ? user.name : updates.name;
const tokens = parsePlain(updates.name); const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description;
if (newName != null) {
const tokens = parsePlain(newName);
emojis = emojis.concat(extractEmojis(tokens!)); emojis = emojis.concat(extractEmojis(tokens!));
} }
if (profile.description != null) { if (newDescription != null) {
const tokens = parse(profile.description); const tokens = parse(newDescription);
emojis = emojis.concat(extractEmojis(tokens!)); emojis = emojis.concat(extractEmojis(tokens!));
tags = extractHashtags(tokens!).map(tag => tag.toLowerCase()); tags = extractHashtags(tokens!).map(tag => tag.toLowerCase());
} }
@@ -224,7 +231,7 @@ export default define(meta, async (ps, user, app) => {
//#endregion //#endregion
if (Object.keys(updates).length > 0) await Users.update(user.id, updates); if (Object.keys(updates).length > 0) await Users.update(user.id, updates);
if (Object.keys(profile).length > 0) await UserProfiles.update({ userId: user.id }, profile); if (Object.keys(profileUpdates).length > 0) await UserProfiles.update({ userId: user.id }, profileUpdates);
const iObj = await Users.pack(user.id, user, { const iObj = await Users.pack(user.id, user, {
detail: true, detail: true,
@@ -17,6 +17,8 @@ export const meta = {
tags: ['notes'], tags: ['notes'],
requireCredential: true,
params: { params: {
limit: { limit: {
validator: $.optional.num.range(1, 100), validator: $.optional.num.range(1, 100),
+1 -1
View File
@@ -150,7 +150,7 @@ export default define(meta, async (ps, user) => {
} }
}); });
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); const profile = await UserProfiles.findOne(user.id).then(ensure);
// この投稿をWatchする // この投稿をWatchする
if (profile.autoWatch !== false) { if (profile.autoWatch !== false) {
+1 -1
View File
@@ -142,7 +142,7 @@ export default define(meta, async (ps, me) => {
}); });
//#region Construct query //#region Construct query
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('note.userId = :userId', { userId: user.id }) .andWhere('note.userId = :userId', { userId: user.id })
.leftJoinAndSelect('note.user', 'user'); .leftJoinAndSelect('note.user', 'user');
-7
View File
@@ -95,13 +95,6 @@ export default define(meta, async (ps, me) => {
throw new ApiError(meta.errors.noSuchUser); throw new ApiError(meta.errors.noSuchUser);
} }
// ユーザー情報更新
if (Users.isRemoteUser(user)) {
if (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) {
resolveUser(user.username, user.host, { }, true);
}
}
return await Users.pack(user, me, { return await Users.pack(user, me, {
detail: true detail: true
}); });
-1
View File
@@ -1,4 +1,3 @@
export const schemas = { export const schemas = {
Error: { Error: {
type: 'object', type: 'object',
+1 -1
View File
@@ -46,7 +46,7 @@ export default async (ctx: Koa.BaseContext) => {
return; return;
} }
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); const profile = await UserProfiles.findOne(user.id).then(ensure);
// Compare password // Compare password
const same = await bcrypt.compare(password, profile.password!); const same = await bcrypt.compare(password, profile.password!);
@@ -20,11 +20,11 @@ export default class extends Channel {
@autobind @autobind
private async onNote(note: any) { private async onNote(note: any) {
// 自分自身の投稿 または その投稿のユーザーをフォローしている または ローカルの投稿 の場合だけ // 自分自身の投稿 または その投稿のユーザーをフォローしている または 全体公開のローカルの投稿 の場合だけ
if (!( if (!(
this.user!.id === note.userId || this.user!.id === note.userId ||
this.following.includes(note.userId) || this.following.includes(note.userId) ||
note.user.host == null (note.user.host == null && note.visibility === 'public')
)) return; )) return;
if (['followers', 'specified'].includes(note.visibility)) { if (['followers', 'specified'].includes(note.visibility)) {
+1 -1
View File
@@ -11,7 +11,7 @@ export default async function(user: User) {
name: user.name || user.username name: user.name || user.username
}; };
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); const profile = await UserProfiles.findOne(user.id).then(ensure);
const notes = await Notes.find({ const notes = await Notes.find({
where: { where: {
+2 -4
View File
@@ -363,16 +363,14 @@ export default async function(
logger.debug(`average color is calculated: ${r}, ${g}, ${b}`); logger.debug(`average color is calculated: ${r}, ${g}, ${b}`);
const value = info.isOpaque ? `rgba(${r},${g},${b},0)` : `rgba(${r},${g},${b},255)`; properties['avgColor'] = `rgb(${r},${g},${b})`;
properties['avgColor'] = value;
} catch (e) { } } catch (e) { }
}; };
propPromises = [calcWh(), calcAvg()]; propPromises = [calcWh(), calcAvg()];
} }
const profile = await UserProfiles.findOne({ userId: user.id }); const profile = await UserProfiles.findOne(user.id);
const [folder] = await Promise.all([fetchFolder(), Promise.all(propPromises)]); const [folder] = await Promise.all([fetchFolder(), Promise.all(propPromises)]);
+1 -1
View File
@@ -253,7 +253,7 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
deliverNoteToMentionedRemoteUsers(mentionedUsers, user, noteActivity); deliverNoteToMentionedRemoteUsers(mentionedUsers, user, noteActivity);
} }
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); const profile = await UserProfiles.findOne(user.id).then(ensure);
// If has in reply to note // If has in reply to note
if (data.reply) { if (data.reply) {
+1 -1
View File
@@ -67,7 +67,7 @@ export default async function(user: User, note: Note, choice: number) {
} }
}); });
const profile = await UserProfiles.findOne({ userId: user.id }); const profile = await UserProfiles.findOne(user.id);
// ローカルユーザーが投票した場合この投稿をWatchする // ローカルユーザーが投票した場合この投稿をWatchする
if (Users.isLocalUser(user) && profile!.autoWatch) { if (Users.isLocalUser(user) && profile!.autoWatch) {
+1 -1
View File
@@ -80,7 +80,7 @@ export default async (user: User, note: Note, reaction?: string) => {
} }
}); });
const profile = await UserProfiles.findOne({ userId: user.id }); const profile = await UserProfiles.findOne(user.id);
// ユーザーがローカルユーザーかつ自動ウォッチ設定がオンならばこの投稿をWatchする // ユーザーがローカルユーザーかつ自動ウォッチ設定がオンならばこの投稿をWatchする
if (Users.isLocalUser(user) && profile!.autoWatch) { if (Users.isLocalUser(user) && profile!.autoWatch) {
+8
View File
@@ -562,6 +562,14 @@ describe('MFM', () => {
]); ]);
}); });
it('ignore 】', () => {
const tokens = parse('#foo】');
assert.deepStrictEqual(tokens, [
leaf('hashtag', { hashtag: 'foo' }),
text('】'),
]);
});
it('allow including number', () => { it('allow including number', () => {
const tokens = parse('#foo123'); const tokens = parse('#foo123');
assert.deepStrictEqual(tokens, [ assert.deepStrictEqual(tokens, [
+52 -2
View File
@@ -32,7 +32,7 @@ describe('Streaming', () => {
p.on('message', message => { p.on('message', message => {
if (message === 'ok') { if (message === 'ok') {
(p.channel as any).onread = () => {}; (p.channel as any).onread = () => {};
initDb(true).then(async connection => { initDb(true).then(async (connection: any) => {
Followings = connection.getRepository(Following); Followings = connection.getRepository(Following);
done(); done();
}); });
@@ -44,7 +44,7 @@ describe('Streaming', () => {
p.kill(); p.kill();
}); });
const follow = async (follower, followee) => { const follow = async (follower: any, followee: any) => {
await Followings.save({ await Followings.save({
id: 'a', id: 'a',
createdAt: new Date(), createdAt: new Date(),
@@ -484,6 +484,56 @@ describe('Streaming', () => {
}); });
})); }));
it('フォローしているユーザーのホーム投稿が流れる', () => new Promise(async done => {
const alice = await signup({ username: 'alice' });
const bob = await signup({ username: 'bob' });
// Alice が Bob をフォロー
await request('/following/create', {
userId: bob.id
}, alice);
const ws = await connectStream(alice, 'hybridTimeline', ({ type, body }) => {
if (type == 'note') {
assert.deepStrictEqual(body.userId, bob.id);
assert.deepStrictEqual(body.text, 'foo');
ws.close();
done();
}
});
// ホーム投稿
post(bob, {
text: 'foo',
visibility: 'home'
});
}));
it('フォローしていないローカルユーザーのホーム投稿は流れない', () => new Promise(async done => {
const alice = await signup({ username: 'alice' });
const bob = await signup({ username: 'bob' });
let fired = false;
const ws = await connectStream(alice, 'hybridTimeline', ({ type, body }) => {
if (type == 'note') {
fired = true;
}
});
// ホーム投稿
post(bob, {
text: 'foo',
visibility: 'home'
});
setTimeout(() => {
assert.strictEqual(fired, false);
ws.close();
done();
}, 3000);
}));
it('フォローしていないローカルユーザーのフォロワー宛て投稿は流れない', () => new Promise(async done => { it('フォローしていないローカルユーザーのフォロワー宛て投稿は流れない', () => new Promise(async done => {
const alice = await signup({ username: 'alice' }); const alice = await signup({ username: 'alice' });
const bob = await signup({ username: 'bob' }); const bob = await signup({ username: 'bob' });
+1 -1
View File
@@ -76,7 +76,7 @@ export const uploadFile = (user: any, path?: string): Promise<any> => new Promis
}); });
}); });
export function connectStream(user: any, channel: string, listener: any, params?: any): Promise<WebSocket> { export function connectStream(user: any, channel: string, listener: (message: Record<string, any>) => any, params?: any): Promise<WebSocket> {
return new Promise((res, rej) => { return new Promise((res, rej) => {
const ws = new WebSocket(`ws://localhost/streaming?i=${user.token}`); const ws = new WebSocket(`ws://localhost/streaming?i=${user.token}`);