Compare commits

..

56 Commits

Author SHA1 Message Date
syuilo
5b0dfa6130 11.35.0 2019-11-01 06:17:43 +09:00
syuilo
a664173b5b New Crowdin translations (#5503)
* 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 (Korean)

* 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 (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)
2019-11-01 05:46:47 +09:00
MeiMei
93a889df82 Preserve the attached order in AP (#5561)
* Preserve the attached order in AP

* Revert "Preserve the attached order in AP"

This reverts commit 8ac09cc81b.

* use IN
2019-11-01 05:46:20 +09:00
rinsuki
0c1d3e186b 新規投稿のMFMをHTMLに変換する際、リモートユーザーへのメンションのリンク先を(できれば)urlに (#5562)
* 新規投稿のMFMをHTMLに変換する際、リモートユーザーへのメンションのリンク先を(できれば)urlに

Fix #2467
Related #5560

* remove unnecessary import

* Update src/services/note/create.ts

Co-Authored-By: Acid Chicken (硫酸鶏) <root@acid-chicken.com>

* Apply suggestions from code review

Co-Authored-By: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
2019-11-01 05:43:54 +09:00
Acid Chicken (硫酸鶏)
e1cf090440 Update webpack.config.ts (#5567) 2019-11-01 05:40:11 +09:00
MeiMei
ff81648134 Resolve #5560 (#5568) 2019-11-01 05:39:00 +09:00
Acid Chicken (硫酸鶏)
1eed6911f0 Update README.md [AUTOGEN] (#5566) 2019-10-31 18:54:07 +09:00
syuilo
5faf9dc2c9 Clean up 2019-10-31 15:53:35 +09:00
syuilo
59a32e9352 Fix chart bug 2019-10-31 15:51:33 +09:00
Aya Morisawa
bf654c6f42 Componentize modal (#5386) 2019-10-29 09:51:19 +09:00
Acid Chicken (硫酸鶏)
34c82776fc Make issue process of registration tickets better (#5545)
* Make issue process of registration tickets better

* Fix illegal indent

* Bye rangestr

* Update invite.ts

* Fix bug

* Update invite.ts
2019-10-29 09:46:51 +09:00
MeiMei
02bde5150e Fix: プロフィールで他のユーザーにうつってもfieldsが更新されない (#5563) 2019-10-29 09:37:55 +09:00
MeiMei
245b08b624 Talk federation (#5534) 2019-10-29 06:01:14 +09:00
Acid Chicken (硫酸鶏)
1e58b3daff Update README.md [AUTOGEN] (#5556) 2019-10-29 01:06:29 +09:00
MeiMei
4f2b4366f2 Fix #5539 (#5542) 2019-10-28 20:34:01 +09:00
Acid Chicken (硫酸鶏)
7a94117d90 Update README.md [AUTOGEN] (#5553) 2019-10-28 14:54:33 +09:00
rinsuki
121c7542a0 投稿の文字数制限の初期値をmeta側と合わせる (#5546) 2019-10-26 00:16:47 +09:00
Acid Chicken (硫酸鶏)
e0433c6b89 Update README.md [AUTOGEN] (#5541) 2019-10-25 22:35:43 +09:00
syuilo
c9bf99fa66 Fix bug 2019-10-25 16:13:37 +09:00
rinsuki
795b56f8ab .config/example.ymlの不要なセミコロンを削除 (#5537) 2019-10-22 19:30:18 +09:00
tamaina
d2edd67cff Update README.md
joinmisskey.github.io -> join.misskey.page
2019-10-21 14:56:54 +09:00
syuilo
1ca6014284 Resolve #5533 2019-10-21 08:34:09 +09:00
syuilo
7ff8fb027d Fix #4731 2019-10-21 00:52:50 +09:00
MeiMei
d672bb4643 Escでメディアビューワが閉じれるように (#5494)
* EscやBackspaceでメディアビューワが閉じれるように

* Backspaceでは閉じないように
2019-10-21 00:46:14 +09:00
MeiMei
4c6c06c80a Improve emoji-picker (#5515)
* Improve emoji-picker

* remove unimplanted translation

* カテゴリのサジェスト

* use unique
2019-10-21 00:43:39 +09:00
Takeshi Umeda
97b6af62fe Add ssl to elasticsearch config settings (#5527) 2019-10-21 00:41:12 +09:00
syuilo
f180f02dd1 Fix bug on iOS 2019-10-20 23:13:14 +09:00
syuilo
3dbd48f744 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-10-20 23:12:14 +09:00
syuilo
3c99b2fd12 Improve usability 2019-10-20 23:12:07 +09:00
Acid Chicken (硫酸鶏)
3d2cfd10bc Update README.md [AUTOGEN] (#5526) 2019-10-20 11:36:33 +09:00
Acid Chicken (硫酸鶏)
e563798afb Update README.md [AUTOGEN] (#5519) 2019-10-18 14:33:45 +09:00
Acid Chicken (硫酸鶏)
f679926b4f Update README.md [AUTOGEN] (#5517) 2019-10-18 14:32:30 +09:00
Acid Chicken (硫酸鶏)
ff20045e1d Update README.md [AUTOGEN] (#5511) 2019-10-17 18:09:42 +09:00
syuilo
c81ff10d5b Fix bug 2019-10-17 00:42:04 +09:00
MeiMei
a103ae6755 Fix #5508 (#5509) 2019-10-16 04:03:51 +09:00
MeiMei
5c44c75c23 Fix #5504 など (#5507)
* カスタム絵文字を重複登録できないように

* fix

* fix
2019-10-16 04:03:18 +09:00
MeiMei
3a973aee69 みつけるの微修正 (#5506)
* default instance name

* とりあえずリモートの人気のユーザーはリモートのみにするように
2019-10-16 03:45:09 +09:00
syuilo
16d59e9988 11.34.0 2019-10-14 02:31:21 +09:00
syuilo
93ce25db91 Update dependencies 🚀 2019-10-14 02:26:02 +09:00
syuilo
c97a523ada New Crowdin translations (#5463)
* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Korean)

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

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (English)

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

* New translations ja-JP.yml (Danish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

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

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)
2019-10-14 02:15:14 +09:00
MeiMei
488fe2e7bd みつけるに連合分を表示するように (#5490)
* みつける Fediverse

* discovered
2019-10-14 02:00:29 +09:00
MeiMei
8da278a279 Update https-proxy-agent など (#5497)
* Use proxy for web-push

* https-proxy-agent 3.0.0
2019-10-14 01:53:28 +09:00
syuilo
da69650505 Resolve #5500 2019-10-14 01:41:25 +09:00
MeiMei
a85f6edd8a 閉じずに残ってしまうメニューなどの修正 (#5496)
* Fix: ページ移動等してもメニュー等が閉じずに残ってしまう

* Fix: ページ移動してもメディアビューワーが残ってしまう
2019-10-09 21:42:23 +09:00
MeiMei
d17c6adba4 ダウンロードURLにdownloadを付けないなど (#5488) 2019-10-04 20:18:41 +09:00
syuilo
2671c6b4f2 Use node 12.11.1 2019-10-04 16:59:12 +09:00
Satsuki Yanagi
a81d5786f3 Indicate if input value is invalid (#5422)
* Indicate if input value is invalid

* titleのとなり

* となりにメッセージ

* CSS as Suggested

* 同じ色に
2019-10-04 14:18:49 +09:00
Acid Chicken (硫酸鶏)
75e2b075e1 Fix #5468 (#5470)
* Update activitypub.ts

* Update activitypub.ts

* Update activitypub.ts

* Update activitypub.ts

* fix type error

* trust the module

* remove space

* accept charset
2019-10-04 10:29:28 +09:00
MeiMei
533884dcaa Fix: オフライン時に大室櫻子が出てこない (#5480)
* Fix: オフライン時に大室櫻子が出てこない

* use onerror
2019-10-04 10:27:57 +09:00
Acid Chicken (硫酸鶏)
7298b41a16 Update README.md [AUTOGEN] (#5482) 2019-10-04 05:05:48 +09:00
Acid Chicken (硫酸鶏)
39561e858a Update README.md [AUTOGEN] (#5478) 2019-10-03 23:44:20 +09:00
Acid Chicken (硫酸鶏)
96b88ee369 Update README.md [AUTOGEN] (#5474) 2019-10-02 07:53:01 +09:00
Acid Chicken (硫酸鶏)
d94e2025be Update README.md [AUTOGEN] (#5471) 2019-10-02 07:27:25 +09:00
MeiMei
6ba5968861 Apply DB limit to the maximum note text length (#5465) 2019-10-01 01:46:31 +09:00
Aya Morisawa
cea2d621f2 Fix typo: accpet -> accept (#5469) 2019-09-30 19:00:17 +09:00
syuilo
84be4aa91e Update reversi-bot.ja-JP.md 2019-09-28 19:48:08 +09:00
82 changed files with 1607 additions and 1943 deletions

View File

@@ -120,8 +120,8 @@ autoAdmin: true
#clusterLimit: 1 #clusterLimit: 1
# Job concurrency per worker # Job concurrency per worker
# deliverJobConcurrency: 128; # deliverJobConcurrency: 128
# inboxJobConcurrency: 16; # inboxJobConcurrency: 16
# IP address family used for outgoing request (ipv4, ipv6 or dual) # IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4 #outgoingAddressFamily: ipv4

View File

@@ -1 +1 @@
v12.11.0 v12.11.1

View File

@@ -1,6 +1,48 @@
ChangeLog ChangeLog
========= =========
11.35.0 (2019/11/01)
--------------------
### ✨Improvements
* 一対一でのトークを連合するように
* カスタム絵文字をカテゴリ分け出来るように
* カスタム絵文字をカテゴリ, 名前でソートするように
* 絵文字ピッカーに最近使った絵文字を表示するように
* 絵文字ピッカーで最後に使ったページを覚えておくように
* 絵文字ピッカーで正方形じゃない絵文字のアスペクト比を保持するように
* EscやBackspaceでメディアビューワが閉じれるように
### 🐛Fixes
* AP: リモートアカウントを解決するとき、そのActorにendpointsが無いとTypeErrorになる問題を修正
* AP: 新規投稿のMFMをHTMLに変換する際、リモートユーザーへのメンションのリンク先を(できれば)urlに
* AP: menionのclassにu-urlを付けるように
* AP: Noteの添付ファイルを提示する時に添付順を保持するように
* リモートユーザーの更新に失敗することがある問題を修正
* ミュートしているユーザーのプロフィールを開くと投稿が表示されない問題を修正
* チャートで値が減ったときの記録が正しく行われていなかった問題を修正
* カスタム絵文字を登録できない場合がある問題を修正
* カスタム絵文字を重複登録できないように
* みつけるの微修正
* プロフィールで他のユーザーにうつってもfieldsが更新されない問題を修正
* 絵文字ピッカーでアニメーション停止オプションが効かないのを修正
11.34.0 (2019/10/14)
--------------------
### ✨Improvements
* みつけるに連合分を表示するように
* リアクションピッカーをカスタマイズできるように
* inputで不正な値を入力したときにメッセージを表示するように
* ドライブファイルダウンロードURLにdownloadを付けないように
* web-pushでProxyを使用するように
### 🐛Fixes
* 投稿本文が合字を含んで8192文字の場合に制限をパスしても投稿できない問題を修正
* meta.maxNoteTextLengthをnote.textの最大サイズより大きく設定できる問題を修正
* ページ移動等してもメニュー等が閉じずに残ってしまう問題を修正
* ページ移動してもメディアビューワーが残ってしまう問題を修正
* APに関する問題の修正
* オフライン時に大室櫻子が出てこない問題を修正
11.33.0 (2019/09/27) 11.33.0 (2019/09/27)
-------------------- --------------------
### ✨Improvements ### ✨Improvements

View File

@@ -1,4 +1,4 @@
FROM node:12.11.0-alpine AS base FROM node:12.11.1-alpine AS base
ENV NODE_ENV=production ENV NODE_ENV=production

View File

@@ -13,7 +13,7 @@
<a href="https://misskey.io">Misskey</a> is a decentralized microblogging platform born on Earth. <a href="https://misskey.io">Misskey</a> is a decentralized microblogging platform born on Earth.
Since it exists within the Fediverse (a universe where various social media platforms are organized), Since it exists within the Fediverse (a universe where various social media platforms are organized),
it is mutually linked with other social media platforms. it is mutually linked with other social media platforms.
Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet? <a href="https://joinmisskey.github.io/">Find an instance!</a> Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet? <a href="https://join.misskey.page/">Find an instance!</a>
</p> </p>
<a href="https://www.patreon.com/syuilo"><img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" alt="Become a Patron!" width="160" /></a> <a href="https://www.patreon.com/syuilo"><img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" alt="Become a Patron!" width="160" /></a>
@@ -103,14 +103,12 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
---------------------------------------------------------------- ----------------------------------------------------------------
<!-- PATREON_START --> <!-- PATREON_START -->
<table><tr> <table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5888816/36da0f7c15954df0ab13f9abdf227f66/1.jpeg?token-time=2145916800&token-hash=at8QpJXJ8C0zINY_NmoMKv-MhXVoUK-YzTgaJPJzJYU%3D" alt="Hiroshi Seki" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/20010324/b8af4bd31ae34fbf8806cc0e6228e400/1.png?token-time=2145916800&token-hash=iyiocfousNIUwASmatsIDq8EOsmLUdrQNkWyktHlmJg%3D" alt="Nemo" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/20010324/b8af4bd31ae34fbf8806cc0e6228e400/1.png?token-time=2145916800&token-hash=iyiocfousNIUwASmatsIDq8EOsmLUdrQNkWyktHlmJg%3D" alt="Nemo" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi" width="100"></td> <td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze" width="100"></td> <td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze" width="100"></td>
</tr><tr> </tr><tr>
<td><a href="https://www.patreon.com/rane_hs">Hiroshi Seki</a></td>
<td><a href="https://www.patreon.com/user?u=20010324">Nemo</a></td> <td><a href="https://www.patreon.com/user?u=20010324">Nemo</a></td>
<td><a href="https://www.patreon.com/weepjp">weepjp</a></td> <td><a href="https://www.patreon.com/weepjp">weepjp</a></td>
<td><a href="https://www.patreon.com/user?u=19045173">kiritan</a></td> <td><a href="https://www.patreon.com/user?u=19045173">kiritan</a></td>
@@ -123,6 +121,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5788159/af42076ab3354bb49803cfba65f94bee/1.jpg?token-time=2145916800&token-hash=iSaxp_Yr2-ZiU2YVi9rcpZZj9mj3UvNSMrZr4CU4qtA%3D" alt="mewl hayabusa" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5788159/af42076ab3354bb49803cfba65f94bee/1.jpg?token-time=2145916800&token-hash=iSaxp_Yr2-ZiU2YVi9rcpZZj9mj3UvNSMrZr4CU4qtA%3D" alt="mewl hayabusa" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1.png?token-time=2145916800&token-hash=9nEQje_eMvUjq9a7L3uBqW-MQbS-rRMaMgd7UYVoFNM%3D" alt="mydarkstar" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1.png?token-time=2145916800&token-hash=9nEQje_eMvUjq9a7L3uBqW-MQbS-rRMaMgd7UYVoFNM%3D" alt="mydarkstar" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td>
<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>
</tr><tr> </tr><tr>
@@ -131,14 +130,15 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61</a></td> <td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61</a></td>
<td><a href="https://www.patreon.com/hs_sh_net">mewl hayabusa</a></td> <td><a href="https://www.patreon.com/hs_sh_net">mewl hayabusa</a></td>
<td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td> <td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td>
<td><a href="https://www.patreon.com/user?u=16542964">Takumi Sugita</a></td>
<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>
</tr></table> </tr></table>
<table><tr> <table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5827393/59893c191dda408f9cabd0f20a3a5627/1.jpeg?token-time=2145916800&token-hash=i9N05vOph-eP1LTLb9_npATjYOpntL0ZsHNaZFSsPmE%3D" alt="motcha" 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> <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/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%3D" alt="Atsuko Tominaga" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%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>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo" width="100"></td>
@@ -146,9 +146,9 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpe?token-time=2145916800&token-hash=qA8j97lIZNc-74AuZ0p4F3ms6sKPeKjtNt2vEuwpsyo%3D" alt="Hekovic" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpe?token-time=2145916800&token-hash=qA8j97lIZNc-74AuZ0p4F3ms6sKPeKjtNt2vEuwpsyo%3D" alt="Hekovic" width="100"></td>
</tr><tr> </tr><tr>
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td> <td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td>
<td><a href="https://www.patreon.com/user?u=5827393">motcha</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> <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/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>
<td><a href="https://www.patreon.com/noellabo">noellabo</a></td> <td><a href="https://www.patreon.com/noellabo">noellabo</a></td>
@@ -157,19 +157,17 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
</tr></table> </tr></table>
<table><tr> <table><tr>
<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/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://c8.patreon.com/2/200/23932002" alt="nenohi" width="100"></td> <td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi" 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> <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/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/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=23932002">nenohi</a></td> <td><a href="https://www.patreon.com/user?u=23932002">nenohi</a></td>
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td> <td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
</tr></table> </tr></table>
**Last updated:** Wed, 25 Sep 2019 21:27:06 UTC **Last updated:** Thu, 31 Oct 2019 06:09:07 UTC
<!-- PATREON_END --> <!-- PATREON_END -->
:four_leaf_clover: Copyright :four_leaf_clover: Copyright

View File

@@ -12,7 +12,6 @@ const stylus = require('gulp-stylus');
import * as uglifyComposer from 'gulp-uglify/composer'; import * as uglifyComposer from 'gulp-uglify/composer';
import * as rimraf from 'rimraf'; import * as rimraf from 'rimraf';
import chalk from 'chalk'; import chalk from 'chalk';
const imagemin = require('gulp-imagemin');
import * as rename from 'gulp-rename'; import * as rename from 'gulp-rename';
import * as mocha from 'gulp-mocha'; import * as mocha from 'gulp-mocha';
import * as replace from 'gulp-replace'; import * as replace from 'gulp-replace';
@@ -122,7 +121,6 @@ gulp.task('copy:client', () =>
'./src/client/assets/**/*', './src/client/assets/**/*',
'./src/client/app/*/assets/**/*' './src/client/app/*/assets/**/*'
]) ])
.pipe(isProduction ? (imagemin as any)() : gutil.noop())
.pipe(rename(path => { .pipe(rename(path => {
path.dirname = path.dirname!.replace('assets', '.'); path.dirname = path.dirname!.replace('assets', '.');
})) }))

View File

@@ -31,6 +31,7 @@ common:
signup: "Registrovat" signup: "Registrovat"
signout: "Odhlásit" signout: "Odhlásit"
reload-to-apply-the-setting: "Pro uplatnění tohoto nastavení musíte znovu načíst tuto stránku. Chcete ji načíst teď?" reload-to-apply-the-setting: "Pro uplatnění tohoto nastavení musíte znovu načíst tuto stránku. Chcete ji načíst teď?"
fetching-as-ap-object: "Načítám data z Fediversu..."
delete-confirm: "Opravdu chcete smazat tento příspěvek?" delete-confirm: "Opravdu chcete smazat tento příspěvek?"
signin-required: "Přihlašte se, prosím" signin-required: "Přihlašte se, prosím"
notification-type: "Typy oznámení" notification-type: "Typy oznámení"
@@ -101,6 +102,9 @@ common:
"read:reactions": "Prohlížet reakce" "read:reactions": "Prohlížet reakce"
"write:reactions": "Narabět s reakcemi" "write:reactions": "Narabět s reakcemi"
"write:votes": "Hlasovat" "write:votes": "Hlasovat"
"read:pages": "Zhlédnutí stránky"
"write:pages": "Upravit stránky"
"write:user-groups": "Upravit uživatelskou skupinu"
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"
@@ -115,10 +119,12 @@ common:
renote: "Renotovat" renote: "Renotovat"
posting: "Posílání" posting: "Posílání"
attach-media-from-local: "Nahrát soubor z vašeho zařízení" attach-media-from-local: "Nahrát soubor z vašeho zařízení"
attach-media-from-drive: "Přiložit soubory z vašeho Drivu"
insert-a-kao: "v('ω')v" insert-a-kao: "v('ω')v"
create-poll: "Vytvořit anketu" create-poll: "Vytvořit anketu"
text-remain: "zbývá ještě {} znaků" text-remain: "zbývá ještě {} znaků"
recent-tags: "Nejnovější" recent-tags: "Nejnovější"
local-only-message: "Publikovat zprávu pouze lokálně"
click-to-tagging: "Klikni pro otágování" click-to-tagging: "Klikni pro otágování"
visibility: "Viditelnost" visibility: "Viditelnost"
geolocation-alert: "Vaše zařízení nedalo k dispozici lokaci" geolocation-alert: "Vaše zařízení nedalo k dispozici lokaci"
@@ -187,6 +193,7 @@ common:
other: "Ostatní" other: "Ostatní"
appearance: "Vzhled" appearance: "Vzhled"
behavior: "Chování" behavior: "Chování"
reactions: "Reakce"
fetch-on-scroll: "Nekonečné načítaní posuvem" fetch-on-scroll: "Nekonečné načítaní posuvem"
fetch-on-scroll-desc: "Pokud budete rolovat dolů po stránce, automaticky bude načten další obsah." fetch-on-scroll-desc: "Pokud budete rolovat dolů po stránce, automaticky bude načten další obsah."
note-visibility: "Viditelnost příspěvku" note-visibility: "Viditelnost příspěvku"
@@ -195,6 +202,8 @@ common:
web-search-engine: "Webové vyhledávače" web-search-engine: "Webové vyhledávače"
web-search-engine-desc: "Například: https://www.google.com/?#q={{query}}" web-search-engine-desc: "Například: https://www.google.com/?#q={{query}}"
paste: "Vložit" paste: "Vložit"
pasted-file-name-desc: "Například: \"rrrr-MM-dd HH-mm-ss [{{number}}]\" → \"2018-03-20 21-30-24 1\""
paste-dialog: "Upravit název vloženého souboru"
keep-cw: "Zachovat varování o obsahu" keep-cw: "Zachovat varování o obsahu"
keep-cw-desc: "Při odpovědi na příspěvek bude varování o obsahu nastaveno stejně jako původní příspěvek." keep-cw-desc: "Při odpovědi na příspěvek bude varování o obsahu nastaveno stejně jako původní příspěvek."
i-like-sushi: "Mam radši sushi (než puding)" i-like-sushi: "Mam radši sushi (než puding)"
@@ -383,6 +392,7 @@ common/views/components/games/reversi/reversi.game.vue:
surrender: "Vzdát se" surrender: "Vzdát se"
surrendered: "Vzdaním se" surrendered: "Vzdaním se"
looped-map: "Zacyklená mapa" looped-map: "Zacyklená mapa"
can-put-everywhere: "Lze položit kamkoliv"
common/views/components/games/reversi/reversi.index.vue: common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi" title: "Misskey Reversi"
sub-title: "Hrajte Reversi s Vašimi kamarády!" sub-title: "Hrajte Reversi s Vašimi kamarády!"
@@ -432,6 +442,7 @@ common/views/components/connect-failed.troubleshooter.vue:
no-server: "Nelze se připojit k serveru Misskey" no-server: "Nelze se připojit k serveru Misskey"
success: "Úspěšně se podařilo spojit s Misskey serverem" success: "Úspěšně se podařilo spojit s Misskey serverem"
flush: "Vyčistit mezipaměť" flush: "Vyčistit mezipaměť"
set-version: "Vyberte verzi"
common/views/components/media-banner.vue: common/views/components/media-banner.vue:
sensitive: "Choulostivý obsah" sensitive: "Choulostivý obsah"
click-to-show: "Klikněte pro zobrazení" click-to-show: "Klikněte pro zobrazení"
@@ -485,6 +496,7 @@ common/views/components/messaging.vue:
group: "Skupina" group: "Skupina"
start-with-user: "Zahájit konverzaci s uživatelem" start-with-user: "Zahájit konverzaci s uživatelem"
start-with-group: "Zahájit skupinovou konverzaci" start-with-group: "Zahájit skupinovou konverzaci"
select-group: "Vybrat skupinu"
common/views/components/messaging-room.vue: common/views/components/messaging-room.vue:
new-message: "Máte novou zprávu" new-message: "Máte novou zprávu"
common/views/components/messaging-room.form.vue: common/views/components/messaging-room.form.vue:
@@ -556,6 +568,7 @@ common/views/components/poll-editor.vue:
deadline-date: "Termín ukončení" deadline-date: "Termín ukončení"
deadline-time: "Doba trvání" deadline-time: "Doba trvání"
interval: "Trvání" interval: "Trvání"
unit: "Jednotka"
second: "Sekunda" second: "Sekunda"
minute: "Minuta" minute: "Minuta"
hour: "Hodina" hour: "Hodina"
@@ -574,6 +587,12 @@ common/views/components/emoji-picker.vue:
symbols: "Symboly" symbols: "Symboly"
flags: "Vlajky" flags: "Vlajky"
common/views/components/settings/app-type.vue: common/views/components/settings/app-type.vue:
title: "Režim"
intro: "Můžete vybrat zdali chcete použít stolní, nebo mobilní vzhled."
choices:
auto: "Vybrat vzhled automaticky"
desktop: "Vždy použít stolní vzhled"
mobile: "Vždy použít mobilní vzhled"
info: "Pro aktivování změn musíte znovu načíst stránky." info: "Pro aktivování změn musíte znovu načíst stránky."
common/views/components/signin.vue: common/views/components/signin.vue:
username: "Přezdívka" username: "Přezdívka"
@@ -585,6 +604,7 @@ common/views/components/signin.vue:
signin-with-github: "Přihlásit se pomocí účtu GitHub" signin-with-github: "Přihlásit se pomocí účtu GitHub"
signin-with-discord: "Přihlásit se pomocí účtu Discord" signin-with-discord: "Přihlásit se pomocí účtu Discord"
login-failed: "Nelze se přihlásit. Zkontrolujte prosím své uživatelské jméno a heslo." login-failed: "Nelze se přihlásit. Zkontrolujte prosím své uživatelské jméno a heslo."
enter-2fa-code: "Vložte Váš verifikační kód"
common/views/components/signup.vue: common/views/components/signup.vue:
invitation-code: "Kód pozvánky" invitation-code: "Kód pozvánky"
invitation-info: "Pokud máte pozvánku, prosím kontaktujte <a href=\"{}\">administrátora</a>." invitation-info: "Pokud máte pozvánku, prosím kontaktujte <a href=\"{}\">administrátora</a>."
@@ -606,6 +626,7 @@ common/views/components/signup.vue:
password-matched: "OK" password-matched: "OK"
password-not-matched: "Neshodují se" password-not-matched: "Neshodují se"
recaptcha: "Potvrzení" recaptcha: "Potvrzení"
agree-to: "Souhlasím s {0}."
tos: "Podmínky užívání" tos: "Podmínky užívání"
create: "Vytvořit účet" create: "Vytvořit účet"
some-error: "Pokus o vytvoření účtu selhal. Prosím zkuste to znovu." some-error: "Pokus o vytvoření účtu selhal. Prosím zkuste to znovu."
@@ -622,8 +643,10 @@ common/views/components/notification-settings.vue:
mark-as-read-all-unread-notes: "Označit všechny příspěvky za přečtené" mark-as-read-all-unread-notes: "Označit všechny příspěvky za přečtené"
mark-as-read-all-talk-messages: "Označit všechny zprávy za přečtené" mark-as-read-all-talk-messages: "Označit všechny zprávy za přečtené"
common/views/components/integration-settings.vue: common/views/components/integration-settings.vue:
title: "Integrace"
connect: "Připojit" connect: "Připojit"
disconnect: "Odpojit" disconnect: "Odpojit"
connected-to: "Jste připojen k tomuto GitHub účtu"
common/views/components/github-setting.vue: common/views/components/github-setting.vue:
description: "Jakmile spojíte Váš GitHub účet s Vaším Misskey účtem, uvidíte informace o Vašem GitHub účtu na Vašem profilu a budete se moci přihlásit skrze GitHub." description: "Jakmile spojíte Váš GitHub účet s Vaším Misskey účtem, uvidíte informace o Vašem GitHub účtu na Vašem profilu a budete se moci přihlásit skrze GitHub."
connected-to: "Je připojen k tomuto GitHub účtu" connected-to: "Je připojen k tomuto GitHub účtu"
@@ -643,6 +666,8 @@ common/views/components/uploader.vue:
common/views/components/visibility-chooser.vue: common/views/components/visibility-chooser.vue:
public: "Veřejné" public: "Veřejné"
home: "Domů" home: "Domů"
home-desc: "Poslat pouze na domovskou časovou osu"
specified: "Přímá"
specified-desc: "Poslat pouze zmíněným uživatelům" specified-desc: "Poslat pouze zmíněným uživatelům"
local-public: "Veřejná (pouze místní)" local-public: "Veřejná (pouze místní)"
local-public-desc: "Nepublikovat na vzdálených serverech" local-public-desc: "Nepublikovat na vzdálených serverech"
@@ -727,12 +752,14 @@ common/views/components/user-groups.vue:
group-name: "Název skupiny" group-name: "Název skupiny"
owned-groups: "Moje skupiny" owned-groups: "Moje skupiny"
invites: "Pozvat" invites: "Pozvat"
accept-invite: "Přidat se"
reject-invite: "Odmítnout" reject-invite: "Odmítnout"
common/views/widgets/broadcast.vue: common/views/widgets/broadcast.vue:
fetching: "Načítám" fetching: "Načítám"
no-broadcasts: "Žádná nová oznámení" no-broadcasts: "Žádná nová oznámení"
have-a-nice-day: "Přejeme Vám příjemný den!" have-a-nice-day: "Přejeme Vám příjemný den!"
next: "Další" next: "Další"
prev: "Předchozí"
common/views/widgets/calendar.vue: common/views/widgets/calendar.vue:
year: "Rok {}" year: "Rok {}"
month: "{}," month: "{},"
@@ -741,6 +768,7 @@ common/views/widgets/calendar.vue:
this-month: "Měsíc:" this-month: "Měsíc:"
this-year: "Rok:" this-year: "Rok:"
common/views/widgets/photo-stream.vue: common/views/widgets/photo-stream.vue:
title: "Foto stream"
no-photos: "Žádné obrázky" no-photos: "Žádné obrázky"
common/views/widgets/posts-monitor.vue: common/views/widgets/posts-monitor.vue:
title: "Grafy příspěvků" title: "Grafy příspěvků"
@@ -756,6 +784,8 @@ common/views/widgets/memo.vue:
save: "Uložit" save: "Uložit"
common/views/widgets/slideshow.vue: common/views/widgets/slideshow.vue:
no-image: "V této složce nebyly nalezeny žádné fotky." no-image: "V této složce nebyly nalezeny žádné fotky."
common/views/widgets/tips.vue:
tips-line23: "Ai-chan kawaii!"
common/views/pages/not-found.vue: common/views/pages/not-found.vue:
page-not-found: "Stránka nenalezena" page-not-found: "Stránka nenalezena"
common/views/pages/follow.vue: common/views/pages/follow.vue:
@@ -827,6 +857,7 @@ desktop/views/components/drive.folder.vue:
input-new-folder-name: "Zadejte nové jméno" input-new-folder-name: "Zadejte nové jméno"
else-folders: "Ostatní" else-folders: "Ostatní"
desktop/views/components/drive.vue: desktop/views/components/drive.vue:
search: "Vyhledávání"
empty-drive-description: "Klikněte pravým tlačítkem myši pro otevření menu, nebo sem přetáhněte soubor pro nahrání." empty-drive-description: "Klikněte pravým tlačítkem myši pro otevření menu, nebo sem přetáhněte soubor pro nahrání."
empty-folder: "Tato složka je prázdná" empty-folder: "Tato složka je prázdná"
unable-to-process: "Operace nemohla být dokončena." unable-to-process: "Operace nemohla být dokončena."
@@ -842,10 +873,13 @@ desktop/views/components/drive.vue:
url-upload: "Nahrát z URL" url-upload: "Nahrát z URL"
desktop/views/components/media-video.vue: desktop/views/components/media-video.vue:
click-to-show: "Klikněte pro zobrazení" click-to-show: "Klikněte pro zobrazení"
desktop/views/components/followers.vue:
empty: "Vypadá to že Vás nikdo nesleduje."
desktop/views/components/game-window.vue: desktop/views/components/game-window.vue:
game: "Reversi" game: "Reversi"
desktop/views/components/home.vue: desktop/views/components/home.vue:
done: "Hotovo" done: "Hotovo"
add-widget: "Přidat widget:"
add: "Přidat" add: "Přidat"
desktop/views/input-dialog.vue: desktop/views/input-dialog.vue:
cancel: "Zrušit" cancel: "Zrušit"
@@ -853,6 +887,7 @@ desktop/views/input-dialog.vue:
desktop/views/components/note-detail.vue: desktop/views/components/note-detail.vue:
private: "Tento příspěvek je soukromý" private: "Tento příspěvek je soukromý"
deleted: "Tento příspěvek byl odstraněn" deleted: "Tento příspěvek byl odstraněn"
location: "Lokace"
renote: "Renotovat" renote: "Renotovat"
add-reaction: "Přidat reakci" add-reaction: "Přidat reakci"
undo-reaction: "Odebrat reakci" undo-reaction: "Odebrat reakci"
@@ -867,6 +902,8 @@ desktop/views/components/note.vue:
desktop/views/components/notes.vue: desktop/views/components/notes.vue:
error: "Načítání selhalo." error: "Načítání selhalo."
retry: "Opakovat" retry: "Opakovat"
desktop/views/components/notifications.vue:
empty: "Žádné nové notifikace!"
desktop/views/components/post-form.vue: desktop/views/components/post-form.vue:
posted: "Odesláno!" posted: "Odesláno!"
replied: "Odpověděno!" replied: "Odpověděno!"
@@ -891,10 +928,29 @@ desktop/views/components/renote-form-window.vue:
desktop/views/components/settings.2fa.vue: desktop/views/components/settings.2fa.vue:
detail: "Více…" detail: "Více…"
url: "https://www.google.cz/landing/2step/" url: "https://www.google.cz/landing/2step/"
register: "Přidat zařízení"
already-registered: "Toto zařízení je již připojené"
unregister: "Odebrat"
enter-password: "Prosím zadejte heslo"
authenticator: "Nejprve musíte nainstalovat Google Authenticator na Vašem zařízení:"
howtoinstall: "Jak nainstalovat"
token: "Token" token: "Token"
scan: "Poté naskenujte QR kód:"
done: "Prosím vložte kód zobrazený na Vašem zařízení:"
submit: "Uložit"
success: "Nastavení uloženo!"
failed: "Nepodařilo se spárovat. Prosím zkontrolujte správnost bezpečnostního kódu."
totp-header: "Ověřovací aplikace"
security-key-header: "Bezpečnostní klíč"
last-used: "Naposledy použito:"
activate-key: "Klikněte pro aktivaci bezpečnostního klíče"
security-key-name: "Název klíče"
key-unregistered: "Bezpečnostní klíč byl odstraněn"
common/views/components/media-image.vue: common/views/components/media-image.vue:
sensitive: "NSFW"
click-to-show: "Klikněte pro zobrazení" click-to-show: "Klikněte pro zobrazení"
common/views/components/api-settings.vue: common/views/components/api-settings.vue:
caution: "Nepoužívejte tento kód v žádné jiné aplikace nebo ho sdílejte s ostatními (jinak můžete ohrozit svojí bezpečnost)."
token: "Token:" token: "Token:"
enter-password: "Prosím zadejte heslo" enter-password: "Prosím zadejte heslo"
console: console:
@@ -976,20 +1032,48 @@ admin/views/index.vue:
announcements: "Oznámení" announcements: "Oznámení"
queue: "Fronta úloh" queue: "Fronta úloh"
logs: "Logy" logs: "Logy"
db: "Databáze"
back-to-misskey: "Zpět na Misskey" back-to-misskey: "Zpět na Misskey"
admin/views/db.vue:
tables: "Tabulky"
vacuum: "Vysavač"
vacuum-info: "Uklidí databázi. Neohrozí data a sníží využití disku. Tohle se děje automaticky a opakovaně."
vacuum-exclamation: "Vysavač může dočasně přetížit databázi a dočasně omezit akce uživatelů."
admin/views/dashboard.vue: admin/views/dashboard.vue:
dashboard: "Kontrolní panel"
accounts: "Účty" accounts: "Účty"
notes: "Poznámky" notes: "Poznámky"
drive: "Disk" drive: "Disk"
instances: "Instance" instances: "Instance"
this-instance: "Tato instance" this-instance: "Tato instance"
federated: "Z fedivesmíru" federated: "Z fedivesmíru"
admin/views/queue.vue:
title: "Ve frontě"
remove-all-jobs: "Vyčistit frontu"
jobs: "Úkoly"
queue: "Ve frontě"
domains:
inbox: "Obdržené"
db: "Databáze"
states:
active: "V procesu"
delayed: "Naplánováno"
waiting: "Ve frontě"
result-is-truncated: "Zkrácený výsledek"
admin/views/logs.vue: admin/views/logs.vue:
logs: "Logy" logs: "Logy"
domain: "Doména"
level: "Úroveň"
levels: levels:
all: "Vše"
info: "Informace" info: "Informace"
success: "Podařilo se"
warning: "Varování"
error: "Chyba" error: "Chyba"
debug: "Debug"
delete-all: "Smazat vše"
admin/views/abuse.vue: admin/views/abuse.vue:
target: "Cíl"
details: "Popis" details: "Popis"
remove-report: "Odstranit" remove-report: "Odstranit"
admin/views/instance.vue: admin/views/instance.vue:
@@ -997,17 +1081,33 @@ admin/views/instance.vue:
instance-name: "Název instance" instance-name: "Název instance"
instance-description: "Popis instance" instance-description: "Popis instance"
host: "Hostitel" host: "Hostitel"
icon-url: "URL ikonky"
logo-url: "URL loga"
banner-url: "URL pro baner" banner-url: "URL pro baner"
error-image-url: "URL pro chybový obrázek"
languages: "Jazyk této instance" languages: "Jazyk této instance"
languages-desc: "Můžete nastavit více než jeden, oddělte mezerami." languages-desc: "Můžete nastavit více než jeden, oddělte mezerami."
tos-url: "URL pro smluvní podmínky"
repository-url: "URL adresa repositáře"
feedback-url: "URL pro zpětnou vazbu"
maintainer-config: "Informace o administrátorovi" maintainer-config: "Informace o administrátorovi"
maintainer-name: "Jméno administrátora" maintainer-name: "Jméno administrátora"
maintainer-email: "Kontakt na administrátora" maintainer-email: "Kontakt na administrátora"
advanced-config: "Další nastavení"
object-storage-base-url: "URL"
object-storage-prefix: "Předpona"
object-storage-endpoint: "Endpoint" object-storage-endpoint: "Endpoint"
object-storage-region: "Region"
object-storage-port: "Port"
object-storage-access-key: "Přístupový klíč"
object-storage-secret-key: "Tajný Klíč (Secret Key)"
object-storage-use-ssl: "Použít SSL"
object-storage-s3-info-here: "zde"
mb: "V megabajtech" mb: "V megabajtech"
recaptcha-config: "nastavení služby reCAPTCHA" recaptcha-config: "nastavení služby reCAPTCHA"
recaptcha-info: "reCAPTCHA token je povinný. Můžete jej získat na https://www.google.com/recaptcha/intro/" recaptcha-info: "reCAPTCHA token je povinný. Můžete jej získat na https://www.google.com/recaptcha/intro/"
enable-recaptcha: "povolit reCAPTCHA" enable-recaptcha: "povolit reCAPTCHA"
recaptcha-secret-key: "Tajný Klíč (Secret Key)"
recaptcha-preview: "Náhled" recaptcha-preview: "Náhled"
twitter-integration-config: "Nastavení spojení s Twitterem" twitter-integration-config: "Nastavení spojení s Twitterem"
twitter-integration-info: "The callback URL is set on {url}." twitter-integration-info: "The callback URL is set on {url}."
@@ -1206,6 +1306,9 @@ desktop/views/widgets/notifications.vue:
title: "Oznámení" title: "Oznámení"
desktop/views/widgets/polls.vue: desktop/views/widgets/polls.vue:
title: "Ankety" title: "Ankety"
nothing: "Žádné nové notifikace!"
desktop/views/widgets/trends.vue:
nothing: "Žádné nové notifikace!"
desktop/views/widgets/users.vue: desktop/views/widgets/users.vue:
title: "Doporučení uživatelé" title: "Doporučení uživatelé"
mobile/views/components/drive.vue: mobile/views/components/drive.vue:
@@ -1248,6 +1351,8 @@ mobile/views/components/note-sub.vue:
admin: "admin" admin: "admin"
bot: "bot" bot: "bot"
cat: "kočka" cat: "kočka"
mobile/views/components/notifications.vue:
empty: "Žádné nové notifikace!"
mobile/views/components/sub-note-content.vue: mobile/views/components/sub-note-content.vue:
private: "Tento příspěvek je soukromý" private: "Tento příspěvek je soukromý"
deleted: "Tento příspěvek byl odstraněn" deleted: "Tento příspěvek byl odstraněn"
@@ -1291,6 +1396,7 @@ mobile/views/pages/note.vue:
mobile/views/pages/games/reversi.vue: mobile/views/pages/games/reversi.vue:
reversi: "Reversi" reversi: "Reversi"
mobile/views/pages/search.vue: mobile/views/pages/search.vue:
search: "Vyhledávání"
not-found: "Pro '{q}' nebyly nalezeny žádné příspěvky." not-found: "Pro '{q}' nebyly nalezeny žádné příspěvky."
mobile/views/pages/selectdrive.vue: mobile/views/pages/selectdrive.vue:
select-file: "Vybrat soubory" select-file: "Vybrat soubory"

View File

@@ -166,6 +166,7 @@ common:
other: "Andet" other: "Andet"
appearance: "Udseende" appearance: "Udseende"
behavior: "Opførsel" behavior: "Opførsel"
reactions: "Reaktion"
fetch-on-scroll: "Uendeligt scroll" fetch-on-scroll: "Uendeligt scroll"
fetch-on-scroll-desc: "Når du scroller ned ad siden, hentes der automatisk nyt indhold ind" fetch-on-scroll-desc: "Når du scroller ned ad siden, hentes der automatisk nyt indhold ind"
note-visibility: "Post synlighed" note-visibility: "Post synlighed"

View File

@@ -91,7 +91,7 @@ common:
"write:drive": "Work with the Drive" "write:drive": "Work with the Drive"
"read:favorites": "View Favorites" "read:favorites": "View Favorites"
"write:favorites": "Work with Favorites" "write:favorites": "Work with Favorites"
"read:following": "View Follower info" "read:following": "View your Follow info"
"write:following": "Work with Follow info" "write:following": "Work with Follow info"
"read:messaging": "View Messaging" "read:messaging": "View Messaging"
"write:messaging": "Work with Messaging" "write:messaging": "Work with Messaging"
@@ -103,6 +103,12 @@ common:
"read:reactions": "View reactions" "read:reactions": "View reactions"
"write:reactions": "Work with reactions" "write:reactions": "Work with reactions"
"write:votes": "Vote" "write:votes": "Vote"
"read:pages": "View Pages"
"write:pages": "Update Pages"
"read:page-likes": "View Likes on Pages"
"write:page-likes": "Update Likes on Pages"
"read:user-groups": "View User groups"
"write:user-groups": "Edit User groups"
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"
@@ -194,6 +200,8 @@ common:
other: "Other" other: "Other"
appearance: "Appearance" appearance: "Appearance"
behavior: "Behavior" behavior: "Behavior"
reactions: "Reaction"
reactions-description: "Customize Emojis of Reaction picker delimited by line breaks"
fetch-on-scroll: "Endless loading on scroll" fetch-on-scroll: "Endless loading on scroll"
fetch-on-scroll-desc: "When you scroll down the page, it automatically fetches additional content." fetch-on-scroll-desc: "When you scroll down the page, it automatically fetches additional content."
note-visibility: "Post visibility" note-visibility: "Post visibility"
@@ -384,9 +392,11 @@ common/views/pages/explore.vue:
popular-users: "Popular users" popular-users: "Popular users"
recently-updated-users: "Recently active users" recently-updated-users: "Recently active users"
recently-registered-users: "Users who joined recently" recently-registered-users: "Users who joined recently"
recently-discovered-users: "Recently Discovered Users"
popular-tags: "Popular Tags" popular-tags: "Popular Tags"
federated: "From the fediverse" federated: "From the fediverse"
explore: "Explore {host}" explore: "Explore {host}"
explore-fediverse: "Explore Fediverse"
users-info: "Currently, {users} users are registered here" users-info: "Currently, {users} users are registered here"
common/views/components/reactions-viewer.details.vue: common/views/components/reactions-viewer.details.vue:
few-users: "{users} reacted with {reaction}" few-users: "{users} reacted with {reaction}"
@@ -619,7 +629,9 @@ common/views/components/reaction-picker.vue:
choose-reaction: "Send a reaction" choose-reaction: "Send a reaction"
input-reaction-placeholder: "or input Emoji" input-reaction-placeholder: "or input Emoji"
common/views/components/emoji-picker.vue: common/views/components/emoji-picker.vue:
recent-emoji: "Recently used"
custom-emoji: "Custom Emoji" custom-emoji: "Custom Emoji"
no-category: "Uncategorized"
people: "People" people: "People"
animals-and-nature: "Animals & Nature" animals-and-nature: "Animals & Nature"
food-and-drink: "Food & drink" food-and-drink: "Food & drink"
@@ -1294,7 +1306,7 @@ admin/views/instance.vue:
discord-integration-client-id: "Client ID" discord-integration-client-id: "Client ID"
discord-integration-client-secret: "Client Secret" discord-integration-client-secret: "Client Secret"
proxy-account-config: "Proxy account" proxy-account-config: "Proxy account"
proxy-account-info: "Proxy account can follow a remote user to deliver activities if no one in this instance follow him or her. When you add a remote user who is followed by nobody in this instance to your list, in order to get his or her data, proxy account follow him or her instead of your following." proxy-account-info: "A proxy account is an account that acts as a remote follower for users under certain conditions. For example, when a user adds a remote user to the list, the activity will not be delivered to the server if no one is following that user, so the proxy account will follow instead."
proxy-account-username: "Proxy account user name" proxy-account-username: "Proxy account user name"
proxy-account-username-desc: "Specify the user name of the account that is used as a proxy." proxy-account-username-desc: "Specify the user name of the account that is used as a proxy."
proxy-account-warn: "You must make an account having this username before this action." proxy-account-warn: "You must make an account having this username before this action."
@@ -1445,6 +1457,7 @@ admin/views/emoji.vue:
title: "Add emoji" title: "Add emoji"
name: "Emoji name" name: "Emoji name"
name-desc: "You can use the characters a~z 0~9 _" name-desc: "You can use the characters a~z 0~9 _"
category: "Categories"
aliases: "Aliases" aliases: "Aliases"
aliases-desc: "You can add more than one, separated by spaces." aliases-desc: "You can add more than one, separated by spaces."
url: "Image URL" url: "Image URL"

View File

@@ -99,6 +99,7 @@ common:
"read:reactions": "Lire les réactions" "read:reactions": "Lire les réactions"
"write:reactions": "Gérer vos réactions" "write:reactions": "Gérer vos réactions"
"write:votes": "Vote" "write:votes": "Vote"
"read:pages": "Afficher la page"
empty-timeline-info: empty-timeline-info:
follow-users-to-make-your-timeline: "Les utilisateur·rice·s suivant·e·s afficheront leurs publications sur votre fil." follow-users-to-make-your-timeline: "Les utilisateur·rice·s suivant·e·s afficheront leurs publications sur votre fil."
explore: "Trouver des utilisateur·rice·s" explore: "Trouver des utilisateur·rice·s"
@@ -188,6 +189,7 @@ common:
other: "Avancé" other: "Avancé"
appearance: "Apparence" appearance: "Apparence"
behavior: "Comportement" behavior: "Comportement"
reactions: "Réaction"
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." 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"

View File

@@ -212,6 +212,8 @@ common:
other: "その他" other: "その他"
appearance: "デザイン" appearance: "デザイン"
behavior: "動作" behavior: "動作"
reactions: "リアクション"
reactions-description: "リアクションピッカーに表示するリアクションを改行で区切って設定します。"
fetch-on-scroll: "スクロールで自動読み込み" fetch-on-scroll: "スクロールで自動読み込み"
fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。" fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
note-visibility: "投稿の公開範囲" note-visibility: "投稿の公開範囲"
@@ -411,9 +413,11 @@ common/views/pages/explore.vue:
popular-users: "人気のユーザー" popular-users: "人気のユーザー"
recently-updated-users: "最近投稿したユーザー" recently-updated-users: "最近投稿したユーザー"
recently-registered-users: "新規ユーザー" recently-registered-users: "新規ユーザー"
recently-discovered-users: "最近発見されたユーザー"
popular-tags: "人気のタグ" popular-tags: "人気のタグ"
federated: "連合" federated: "連合"
explore: "{host}を探索" explore: "{host}を探索"
explore-fediverse: "Fediverseを探索"
users-info: "現在{users}ユーザーが登録されています" users-info: "現在{users}ユーザーが登録されています"
common/views/components/reactions-viewer.details.vue: common/views/components/reactions-viewer.details.vue:
@@ -669,7 +673,9 @@ common/views/components/reaction-picker.vue:
input-reaction-placeholder: "または絵文字を入力" input-reaction-placeholder: "または絵文字を入力"
common/views/components/emoji-picker.vue: common/views/components/emoji-picker.vue:
recent-emoji: "最近使った絵文字"
custom-emoji: "カスタム絵文字" custom-emoji: "カスタム絵文字"
no-category: "カテゴリなし"
people: "人" people: "人"
animals-and-nature: "動物&自然" animals-and-nature: "動物&自然"
food-and-drink: "食べ物&飲み物" food-and-drink: "食べ物&飲み物"
@@ -1587,6 +1593,7 @@ admin/views/emoji.vue:
title: "絵文字の登録" title: "絵文字の登録"
name: "絵文字名" name: "絵文字名"
name-desc: "a~z 0~9 _ の文字が使えます。" name-desc: "a~z 0~9 _ の文字が使えます。"
category: "カテゴリ"
aliases: "エイリアス" aliases: "エイリアス"
aliases-desc: "スペースで区切って複数設定できます。" aliases-desc: "スペースで区切って複数設定できます。"
url: "絵文字画像URL" url: "絵文字画像URL"

View File

@@ -126,6 +126,7 @@ common:
blocking: "ブロック" blocking: "ブロック"
password: "パスワード" password: "パスワード"
other: "その他" other: "その他"
reactions: "リアクション"
timeline: "タイムライン" timeline: "タイムライン"
save: "保存" save: "保存"
saved: "保存したで!" saved: "保存したで!"

View File

@@ -83,14 +83,14 @@ common:
followers: "팔로워" followers: "팔로워"
favorites: "즐겨찾기" favorites: "즐겨찾기"
permissions: permissions:
"read:account": "계정 정보를 볼 수 있습니다." "read:account": "계정 정보 보기"
"write:account": "계정 정보 변경할 수 있습니다." "write:account": "계정 정보 변경"
"read:blocks": "차단 목록을 볼 수 있습니다." "read:blocks": "차단 보기"
"write:blocks": "차단 목록을 조작할 수 있습니다." "write:blocks": "차단 수정"
"read:drive": "드라이브의 파일들을 볼 수 있습니다." "read:drive": "드라이브 보기"
"write:drive": "드라이브의 파일들을 조작할 수 있습니다." "write:drive": "드라이브 수정"
"read:favorites": "즐겨찾기 목록을 볼 수 있습니다." "read:favorites": "즐겨찾기 보기"
"write:favorites": "즐겨찾기 목록을 조작할 수 있습니다." "write:favorites": "즐겨찾기 수정"
"read:following": "팔로우 정보 보기" "read:following": "팔로우 정보 보기"
"write:following": "팔로잉, 팔로우 수정" "write:following": "팔로잉, 팔로우 수정"
"read:messaging": "대화 보기" "read:messaging": "대화 보기"
@@ -103,6 +103,12 @@ common:
"read:reactions": "리액션 보기" "read:reactions": "리액션 보기"
"write:reactions": "리액션 수정" "write:reactions": "리액션 수정"
"write:votes": "투표하기" "write:votes": "투표하기"
"read:pages": "페이지 보기"
"write:pages": "페이지 변경"
"read:page-likes": "페이지의 좋아요 보기"
"write:page-likes": "페이지의 좋아요 변경"
"read:user-groups": "유저 그룹 보기"
"write:user-groups": "유저 그룹을 변경"
empty-timeline-info: empty-timeline-info:
follow-users-to-make-your-timeline: "사용자를 팔로우하면 글이 타임라인에 표시됩니다." follow-users-to-make-your-timeline: "사용자를 팔로우하면 글이 타임라인에 표시됩니다."
explore: "사용자 탐색" explore: "사용자 탐색"
@@ -194,6 +200,8 @@ common:
other: "기타" other: "기타"
appearance: "디자인" appearance: "디자인"
behavior: "동작" behavior: "동작"
reactions: "리액션"
reactions-description: "리액션 선택창에 표시할 리액션을 줄바꿈으로 구분해 설정합니다."
fetch-on-scroll: "스크롤하여 자동으로 불러오기" fetch-on-scroll: "스크롤하여 자동으로 불러오기"
fetch-on-scroll-desc: "페이지를 아래로 스크롤하였을 때 자동으로 추가 콘텐츠를 불러옵니다." fetch-on-scroll-desc: "페이지를 아래로 스크롤하였을 때 자동으로 추가 콘텐츠를 불러옵니다."
note-visibility: "게시물의 공개 범위" note-visibility: "게시물의 공개 범위"
@@ -384,9 +392,11 @@ common/views/pages/explore.vue:
popular-users: "인기 사용자" popular-users: "인기 사용자"
recently-updated-users: "최근 게시한 사용자" recently-updated-users: "최근 게시한 사용자"
recently-registered-users: "신규 사용자" recently-registered-users: "신규 사용자"
recently-discovered-users: "최근 발견된 유저"
popular-tags: "인기 태그" popular-tags: "인기 태그"
federated: "연합" federated: "연합"
explore: "{host}을(를) 탐색" explore: "{host}을(를) 탐색"
explore-fediverse: "연합 우주를 탐색"
users-info: "현재 {users} 사용자가 등록되어 있습니다" users-info: "현재 {users} 사용자가 등록되어 있습니다"
common/views/components/reactions-viewer.details.vue: common/views/components/reactions-viewer.details.vue:
few-users: "{users}님이 {reaction} 리액션" few-users: "{users}님이 {reaction} 리액션"
@@ -619,7 +629,9 @@ common/views/components/reaction-picker.vue:
choose-reaction: "리액션 선택" choose-reaction: "리액션 선택"
input-reaction-placeholder: "또는 이모지 입력" input-reaction-placeholder: "또는 이모지 입력"
common/views/components/emoji-picker.vue: common/views/components/emoji-picker.vue:
recent-emoji: "최근 사용한 이모지"
custom-emoji: "커스텀 이모지" custom-emoji: "커스텀 이모지"
no-category: "카테고리 없음"
people: "사람들" people: "사람들"
animals-and-nature: "동물 & 자연" animals-and-nature: "동물 & 자연"
food-and-drink: "음식 & 음료" food-and-drink: "음식 & 음료"
@@ -1445,6 +1457,7 @@ admin/views/emoji.vue:
title: "이모지 등록" title: "이모지 등록"
name: "이모지 이름" name: "이모지 이름"
name-desc: "a~z 0~9 _ 의 문자를 사용할 수 있습니다." name-desc: "a~z 0~9 _ 의 문자를 사용할 수 있습니다."
category: "카테고리"
aliases: "별칭" aliases: "별칭"
aliases-desc: "공백으로 구분하여 여러 개 설정할 수 있습니다." aliases-desc: "공백으로 구분하여 여러 개 설정할 수 있습니다."
url: "이모지 이미지 URL" url: "이모지 이미지 URL"

View File

@@ -3,16 +3,58 @@ meta:
lang: "Nederlands" lang: "Nederlands"
common: common:
misskey: "Deel alles met anderen die ook Misskey gebruiken." misskey: "Deel alles met anderen die ook Misskey gebruiken."
about-title: "Een ster van het fediverse"
about: "Bedankt voor het ontdekken van Misskey. Misskey is een <b>gedecentraliseerd microblogging platform</b> geboren op aarde. Omdat het bestaat binnen het Fediverse (een georganiseerd universum van verschillende sociale mediaplatformen), staat het verbonden met andere sociale medieplatformen. Neem een pauze van de stedelijke drukte, en duik in het nieuwe intenet?"
intro: intro:
title: "Wat is Misskey?"
about: "Misskey is een open source <b>gedecentraliseerd blogplatform</b>. Het heeft een gesofisticeerde, volledig aanpasbare gebruikersinterface, uitgebreide reactiemogelijkheden voor posts, gratis geïntegreerd bestandsoplagbeheer en andere geavanceerde mogelijkheden. Daarnaast staat Misskey verbonden aan een netwerksysteem genaam het \"Fediverse\", hiermee kunnen we communiceren met andere gebruikers op andere SNSs. Dit betekent dat wanneer je iets post het niet enkel verstuurd wordt naar andere Misskey gebruikers, maar ook naar gebruikers op Mastodon en Pleroma. Stel je voor dat een planeet een radiosignaal verzendt naar een andere planeet als manier van communiceren."
features: "Kenmerken"
rich-contents: "Bericht" rich-contents: "Bericht"
rich-contents-desc: "Post jouw idee, hot topic, wat je ook maar wil delen. Maak jouw teksten aantrekkelijk met je favoriete foto's, verzend bestanden, zelfs video's, of maak een poll. Dit zijn enkele van de mogelijkheden die Misskey aanbiedt!"
reaction: "Reactie" reaction: "Reactie"
reaction-desc: "Dé makkelijkste manier om jouw gevoelens uit te drukken. Met Misskey kan je verschillende reacties toevoegen aan jouw posts. Andere SNSs hebben enkel maar een \"vind ik leuk\" reactie."
ui: "Interface"
ui-desc: "Niet één UI past nij iedereen. Daarom heeft Misskey een uitgebreide keuze om de UI naar jouw hand te zetten. Je kan jouw nieuwe thuis zo origineel maken als je zelf wil door jouw tijdslijn aan te passen door widgets te verplaatsen en hun look te veranderen. Zo maak je van Misskey jouw eigen stek."
drive: "Drive"
drive-desc: "Wil je een foto posten die je reeds het geüpload? Wens je georganiseerde map met zelfgekozen naam maken voor al jouw bestanden? De beste oplossing voor jou is Misskey Drive. Dit maakt het supermakkelijk om jouw bestanden online te delen."
application-authorization: "Geauthoriseerde applicaties"
close: "Sluiten" close: "Sluiten"
do-not-copy-paste: "Gelieve de code hier niet in te geven of te plakken. De account kan gecompromiseerd zijn."
load-more: "Laad meer resultaten"
enter-password: "Voer het wachtwoord in" enter-password: "Voer het wachtwoord in"
2fa: "Tweestapsverificatie"
customize-home: "Layout aanpassen"
featured-notes: "Uitgelicht"
dark-mode: "Donker thema"
signin: "Aanmelden"
signup: "Registreren"
signout: "Afmelden"
reload-to-apply-the-setting: "Herlaad de pagina om je aanpassingen te bekijken. Wil je de pagina nu herladen?"
fetching-as-ap-object: "Verzenden naar Fediverse"
unfollow-confirm: "Wil stoppen met {name} te volgen?"
delete-confirm: "Ben je zeker dat je dit bericht wil verwijderen?"
signin-required: "Gelieve in te loggen"
notification-type: "Notificatietype"
notification-types: notification-types:
all: "Alle" all: "Alle"
pollVote: "Stemmen"
follow: "Volgend" follow: "Volgend"
receiveFollowRequest: "Volgverzoeken"
reply: "Beantwoorden" reply: "Beantwoorden"
quote: "Bron"
mention: "Vermeldingen"
reaction: "Reactie" reaction: "Reactie"
got-it: "Ik snap het!"
customization-tips:
title: "Aanpassingstips"
gotit: "Ik snap het!"
notification:
file-uploaded: "Je bestand is geüpload"
message-from: "Bericht van {}:"
reversi-invited: "Uitgenodigd voor spel"
notified-by: "Bemerkt door: {}"
reply-from: "Antwoord van: {}"
quoted-by: "Geciteerd door: {}"
time: time:
unknown: "onbekend" unknown: "onbekend"
future: "toekomstig" future: "toekomstig"
@@ -24,7 +66,13 @@ common:
weeks_ago: "{}week/weken geleden" weeks_ago: "{}week/weken geleden"
months_ago: "{}maand(en) geleden" months_ago: "{}maand(en) geleden"
years_ago: "{}jaar geleden" years_ago: "{}jaar geleden"
month-and-day: "{day} {month}"
trash: "Prullenbak"
drive: "Drive"
pages: "Pagina's"
messaging: "Gesprekken"
home: "Startpagina" home: "Startpagina"
deck: "Deck"
timeline: "Tijdlijn" timeline: "Tijdlijn"
followers: "Volgers" followers: "Volgers"
favorites: "Deze notitie toevoegen aan favorieten" favorites: "Deze notitie toevoegen aan favorieten"
@@ -59,7 +107,11 @@ common:
profile: "Je profiel" profile: "Je profiel"
notification: "Meldingen" notification: "Meldingen"
password: "Wachtwoord" password: "Wachtwoord"
reactions: "Reactie"
deck-column-align-center: "Centreren"
deck-column-align-left: "Links"
timeline: "Tijdlijn" timeline: "Tijdlijn"
navbar-position-left: "Links"
search: "Zoeken" search: "Zoeken"
delete: "Verwijderen" delete: "Verwijderen"
loading: "Bezig met laden" loading: "Bezig met laden"
@@ -423,11 +475,13 @@ admin/views/index.vue:
users: "Gebruiker" users: "Gebruiker"
admin/views/dashboard.vue: admin/views/dashboard.vue:
notes: "Bericht" notes: "Bericht"
drive: "Drive"
admin/views/abuse.vue: admin/views/abuse.vue:
remove-report: "Verwijderen" remove-report: "Verwijderen"
admin/views/charts.vue: admin/views/charts.vue:
notes: "Bericht" notes: "Bericht"
users: "Gebruiker" users: "Gebruiker"
drive: "Drive"
admin/views/drive.vue: admin/views/drive.vue:
origin: origin:
local: "Lokaal" local: "Lokaal"
@@ -563,6 +617,7 @@ mobile/views/pages/home.vue:
global: "Algemeen" global: "Algemeen"
mobile/views/pages/widgets.vue: mobile/views/pages/widgets.vue:
add-widget: "Toevoegen" add-widget: "Toevoegen"
customization-tips: "Aanpassingstips"
mobile/views/pages/widgets/activity.vue: mobile/views/pages/widgets/activity.vue:
activity: "Activiteit" activity: "Activiteit"
mobile/views/pages/note.vue: mobile/views/pages/note.vue:
@@ -643,3 +698,4 @@ room:
translate: "Verplaatsen" translate: "Verplaatsen"
furnitures: furnitures:
moon: "Maan" moon: "Maan"
bin: "Prullenbak"

View File

@@ -157,6 +157,7 @@ common:
other: "Inne" other: "Inne"
appearance: "Wygląd" appearance: "Wygląd"
behavior: "Zachowanie" behavior: "Zachowanie"
reactions: "Reakcja"
fetch-on-scroll: "Automatycznie ładuj po przeciągnięciu w dół" fetch-on-scroll: "Automatycznie ładuj po przeciągnięciu w dół"
note-visibility: "Widoczność wpisów" note-visibility: "Widoczność wpisów"
remember-note-visibility: "Zapamiętaj widoczność wpisów" remember-note-visibility: "Zapamiętaj widoczność wpisów"

View File

@@ -2,12 +2,12 @@
meta: meta:
lang: "中文(简体)" lang: "中文(简体)"
common: common:
misskey: "Fediverse中的一颗⭐" misskey: "联邦宇宙中的一颗⭐"
about-title: "Fediverse中的一颗⭐" about-title: "联邦宇宙中的一颗⭐"
about: "非常感谢您找到了Misskey。 Misskey是诞生于地球的<b>分布式微博SNS</b>。因为她处于Fediverse(由各种SNS组成的宇宙)中所以她与其他SNS相互连接。想要远离喧嚣的城市,不如深入这新的互联网来探索一下吧。" about: "非常感谢您找到了Misskey。 Misskey是诞生于地球的<b>分布式微博SNS</b>。因为她处于联邦宇宙(由各种SNS组成的宇宙)中所以她与其他SNS相互连接。为什么不试试远离喧嚣的城市,入这新的网络海洋之中呢?"
intro: intro:
title: "什么是 Misskey 呢?" title: "什么是 Misskey 呢?"
about: "Misskey是开源的<b>分散式微博SNS</b>。复杂的完全可定制的Ui各种各样的帖子反应提供集成管理系统和其他先进功能的免费文件存储。此外称为“Fediverse”的网络系统使我们能够与其他SNS的用户进行通信。比如如果你张贴一些东西那么你的帖子不仅会发送给Misskey还会发送到其他SNS平台。想象一下正如一颗行星和另一颗行星通过电磁波来进行通信一样。" about: "Misskey是开源的<b>分散式微博SNS</b>。丰富且可以高度定制的Ui对别人的帖子进行各种回应集成管理系统的网盘等先进功能。此外称为“联邦世界”的网络系统使我们能够与其他SNS的用户进行通信。比如如果你发布一个帖子那么你的帖子不仅会发送给Misskey还会发送到其他SNS平台。想象一下正如一颗行星和另一颗行星通过电磁波来进行通信一样。"
features: "功能" features: "功能"
rich-contents: "发布" rich-contents: "发布"
rich-contents-desc: "请分享您的想法,热门话题,以及任何您想与大家分享的内容。如果有需要的话,您可以使用各种语法来修饰文章,发布问卷调查,或者添加各种您喜欢的图像和视频等文件。" rich-contents-desc: "请分享您的想法,热门话题,以及任何您想与大家分享的内容。如果有需要的话,您可以使用各种语法来修饰文章,发布问卷调查,或者添加各种您喜欢的图像和视频等文件。"
@@ -16,7 +16,7 @@ common:
ui: "交互界面" ui: "交互界面"
ui-desc: "世界上没有一个UI可以适合每一个人. 所以, Misskey 提供一个可以高度定制的UI交互界面. 您可以通过编辑, 调整布局, 放置可选择的小部件来轻松定制您的专属UI界面。" ui-desc: "世界上没有一个UI可以适合每一个人. 所以, Misskey 提供一个可以高度定制的UI交互界面. 您可以通过编辑, 调整布局, 放置可选择的小部件来轻松定制您的专属UI界面。"
drive: "网盘" drive: "网盘"
drive-desc: "想要发布一张您已经上传过的照片吗想要管理文件或为上传的文件创建文件夹吗Misskey的网盘是一个最好的解决方案。" drive-desc: "想要发布一张您已经上传过的照片吗想要管理文件或为上传的文件创建文件夹吗Misskey的内置网盘将为您完美解决这些问题。简单地分享您的文件。"
outro: "Misskey还有其他更多功能请亲身体验一下吧。因为 Misskey 是一个分布式的 SNS如果您感觉某个功能不适合自己试试其他的吧。祝您玩得开心" outro: "Misskey还有其他更多功能请亲身体验一下吧。因为 Misskey 是一个分布式的 SNS如果您感觉某个功能不适合自己试试其他的吧。祝您玩得开心"
application-authorization: "应用程序授权" application-authorization: "应用程序授权"
close: "关闭" close: "关闭"
@@ -103,6 +103,12 @@ common:
"read:reactions": "查看回应" "read:reactions": "查看回应"
"write:reactions": "回应操作" "write:reactions": "回应操作"
"write:votes": "投票" "write:votes": "投票"
"read:pages": "查看页面"
"write:pages": "操作页面"
"read:page-likes": "查看喜欢的页面"
"write:page-likes": "操作喜欢的页面"
"read:user-groups": "查看用户组"
"write:user-groups": "操作用户组"
empty-timeline-info: empty-timeline-info:
follow-users-to-make-your-timeline: "关注其他用户时,帖子将显示在时间线中。" follow-users-to-make-your-timeline: "关注其他用户时,帖子将显示在时间线中。"
explore: "查找用户" explore: "查找用户"
@@ -185,7 +191,7 @@ common:
profile: "个人资料" profile: "个人资料"
notification: "通知" notification: "通知"
apps: "应用程序" apps: "应用程序"
tags: "哈希标签" tags: "话题标签"
mute-and-block: "屏蔽/拉黑" mute-and-block: "屏蔽/拉黑"
blocking: "拉黑" blocking: "拉黑"
security: "安全性" security: "安全性"
@@ -194,6 +200,8 @@ common:
other: "其他" other: "其他"
appearance: "设计" appearance: "设计"
behavior: "行为" behavior: "行为"
reactions: "回应"
reactions-description: "快速选择回应中的自定义表情符号,以换行符分隔。"
fetch-on-scroll: "向下滚动时自动加载" fetch-on-scroll: "向下滚动时自动加载"
fetch-on-scroll-desc: "向下滚动页面时,它会自动提取其他内容。" fetch-on-scroll-desc: "向下滚动页面时,它会自动提取其他内容。"
note-visibility: "帖子可见性" note-visibility: "帖子可见性"
@@ -384,9 +392,11 @@ common/views/pages/explore.vue:
popular-users: "热门用户" popular-users: "热门用户"
recently-updated-users: "活跃用户" recently-updated-users: "活跃用户"
recently-registered-users: "新用户" recently-registered-users: "新用户"
recently-discovered-users: "最近发现的用户"
popular-tags: "热门标签" popular-tags: "热门标签"
federated: "联" federated: "联"
explore: "查找{host}" explore: "查找{host}"
explore-fediverse: "探索Fediverse"
users-info: "当前有{users}个注册用户" users-info: "当前有{users}个注册用户"
common/views/components/reactions-viewer.details.vue: common/views/components/reactions-viewer.details.vue:
few-users: "{users}作出了{reaction}的回应" few-users: "{users}作出了{reaction}的回应"
@@ -619,7 +629,9 @@ common/views/components/reaction-picker.vue:
choose-reaction: "选择回应" choose-reaction: "选择回应"
input-reaction-placeholder: "表情符号输入" input-reaction-placeholder: "表情符号输入"
common/views/components/emoji-picker.vue: common/views/components/emoji-picker.vue:
recent-emoji: "最近使用的表情符号"
custom-emoji: "自定义表情符号" custom-emoji: "自定义表情符号"
no-category: "未分类"
people: "人" people: "人"
animals-and-nature: "动物与自然" animals-and-nature: "动物与自然"
food-and-drink: "食物与饮品" food-and-drink: "食物与饮品"
@@ -640,7 +652,7 @@ common/views/components/signin.vue:
username: "用户名" username: "用户名"
password: "密码" password: "密码"
token: "Token (令牌)" token: "Token (令牌)"
signing-in: "正在登录..." signing-in: "在弄了在弄了..."
or: "或者" or: "或者"
signin-with-twitter: "用 Twitter 登录" signin-with-twitter: "用 Twitter 登录"
signin-with-github: "用 GitHub 登录" signin-with-github: "用 GitHub 登录"
@@ -1170,16 +1182,16 @@ desktop/views/components/window.vue:
popout: "弹出" popout: "弹出"
close: "关闭" close: "关闭"
admin/views/index.vue: admin/views/index.vue:
dashboard: "Dashboard" dashboard: "仪表盘"
instance: "例" instance: "例"
emoji: "Emoji" emoji: "自定义Emoji"
moderators: "版主" moderators: "版主"
users: "用户" users: "用户"
federation: "联" federation: "联"
announcements: "公告" announcements: "公告"
abuse: "举报垃圾信息" abuse: "举报垃圾信息"
queue: "作业队列" queue: "作业队列"
logs: "登录" logs: "日志"
db: "数据库" db: "数据库"
back-to-misskey: "返回 Misskey" back-to-misskey: "返回 Misskey"
admin/views/db.vue: admin/views/db.vue:
@@ -1192,7 +1204,7 @@ admin/views/dashboard.vue:
accounts: "账户" accounts: "账户"
notes: "帖子" notes: "帖子"
drive: "网盘" drive: "网盘"
instances: "例" instances: "例"
this-instance: "此实例" this-instance: "此实例"
federated: "联合" federated: "联合"
admin/views/queue.vue: admin/views/queue.vue:
@@ -1213,7 +1225,7 @@ admin/views/queue.vue:
result-is-truncated: "结果已省略" result-is-truncated: "结果已省略"
other-queues: "其他队列" other-queues: "其他队列"
admin/views/logs.vue: admin/views/logs.vue:
logs: "登录" logs: "日志"
domain: "域" domain: "域"
level: "级别" level: "级别"
levels: levels:
@@ -1231,7 +1243,7 @@ admin/views/abuse.vue:
details: "详情" details: "详情"
remove-report: "删除" remove-report: "删除"
admin/views/instance.vue: admin/views/instance.vue:
instance: "例" instance: "例"
instance-name: "实例名称" instance-name: "实例名称"
instance-description: "实例介绍" instance-description: "实例介绍"
host: "主机名" host: "主机名"
@@ -1435,7 +1447,7 @@ admin/views/moderators.vue:
remove: "取消" remove: "取消"
removed: "取消注册版主" removed: "取消注册版主"
logs: logs:
title: "登录" title: "日志"
moderator: "版主" moderator: "版主"
type: "操作" type: "操作"
at: "日期和时间" at: "日期和时间"
@@ -1445,6 +1457,7 @@ admin/views/emoji.vue:
title: "添加emoji" title: "添加emoji"
name: "Emoji 名称" name: "Emoji 名称"
name-desc: "你可以使用字符a~z 0~9 _" name-desc: "你可以使用字符a~z 0~9 _"
category: "类别"
aliases: "别名" aliases: "别名"
aliases-desc: "您可以添加多个,以空格分隔。" aliases-desc: "您可以添加多个,以空格分隔。"
url: "emoji 地址" url: "emoji 地址"
@@ -1489,7 +1502,7 @@ admin/views/federation.vue:
block: "拉黑" block: "拉黑"
marked-as-closed: "标记为已关闭" marked-as-closed: "标记为已关闭"
lookup: "查询" lookup: "查询"
instances: "联" instances: "联"
instance-not-registered: "实例未注册" instance-not-registered: "实例未注册"
sort: "排序" sort: "排序"
sorts: sorts:
@@ -1609,7 +1622,7 @@ desktop/views/widgets/trends.vue:
desktop/views/widgets/users.vue: desktop/views/widgets/users.vue:
title: "推荐用户" title: "推荐用户"
refresh: "更多" refresh: "更多"
no-one: "不是" no-one: "没有任何推荐用户"
mobile/views/components/drive.vue: mobile/views/components/drive.vue:
used: "已使用" used: "已使用"
folder-count: "文件夹" folder-count: "文件夹"

View File

@@ -0,0 +1,13 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class CustomEmojiCategory1571220798684 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`ALTER TABLE "emoji" ADD "category" character varying(128)`, undefined);
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "category"`, undefined);
}
}

View File

@@ -1,7 +1,7 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "11.33.0", "version": "11.35.0",
"codename": "daybreak", "codename": "daybreak",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -27,10 +27,11 @@
}, },
"resolutions": { "resolutions": {
"gulp-cssnano/cssnano/postcss-svgo/svgo/js-yaml": "^3.13.1", "gulp-cssnano/cssnano/postcss-svgo/svgo/js-yaml": "^3.13.1",
"https-proxy-agent": "^3.0.0",
"lodash": "^4.17.13" "lodash": "^4.17.13"
}, },
"dependencies": { "dependencies": {
"@elastic/elasticsearch": "7.3.0", "@elastic/elasticsearch": "7.4.0",
"@fortawesome/fontawesome-svg-core": "1.2.25", "@fortawesome/fontawesome-svg-core": "1.2.25",
"@fortawesome/free-brands-svg-icons": "5.11.2", "@fortawesome/free-brands-svg-icons": "5.11.2",
"@fortawesome/free-regular-svg-icons": "5.11.2", "@fortawesome/free-regular-svg-icons": "5.11.2",
@@ -38,7 +39,7 @@
"@fortawesome/vue-fontawesome": "0.1.7", "@fortawesome/vue-fontawesome": "0.1.7",
"@koa/cors": "3.0.0", "@koa/cors": "3.0.0",
"@koa/multer": "2.0.0", "@koa/multer": "2.0.0",
"@koa/router": "8.0.1", "@koa/router": "8.0.2",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/bull": "3.10.3", "@types/bull": "3.10.3",
"@types/cbor": "2.0.0", "@types/cbor": "2.0.0",
@@ -69,7 +70,7 @@
"@types/koa__router": "8.0.0", "@types/koa__router": "8.0.0",
"@types/lolex": "3.1.1", "@types/lolex": "3.1.1",
"@types/mocha": "5.2.7", "@types/mocha": "5.2.7",
"@types/node": "12.7.8", "@types/node": "12.7.12",
"@types/nodemailer": "6.2.1", "@types/nodemailer": "6.2.1",
"@types/nprogress": "0.2.0", "@types/nprogress": "0.2.0",
"@types/oauth": "0.9.1", "@types/oauth": "0.9.1",
@@ -95,21 +96,21 @@
"@types/tmp": "0.1.0", "@types/tmp": "0.1.0",
"@types/uuid": "3.4.5", "@types/uuid": "3.4.5",
"@types/web-push": "3.3.0", "@types/web-push": "3.3.0",
"@types/webpack": "4.39.1", "@types/webpack": "4.39.3",
"@types/webpack-stream": "3.2.10", "@types/webpack-stream": "3.2.10",
"@types/websocket": "0.0.40", "@types/websocket": "0.0.40",
"@types/ws": "6.0.3", "@types/ws": "6.0.3",
"@typescript-eslint/parser": "2.3.1", "@typescript-eslint/parser": "2.3.3",
"agentkeepalive": "4.0.2", "agentkeepalive": "4.1.0",
"animejs": "3.1.0", "animejs": "3.1.0",
"apexcharts": "3.8.6", "apexcharts": "3.10.0",
"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",
"aws-sdk": "2.537.0", "aws-sdk": "2.548.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"bootstrap": "4.3.1", "bootstrap": "4.3.1",
"bootstrap-vue": "2.0.2", "bootstrap-vue": "2.0.4",
"bull": "3.11.0", "bull": "3.11.0",
"cafy": "15.1.1", "cafy": "15.1.1",
"cbor": "5.0.1", "cbor": "5.0.1",
@@ -125,7 +126,7 @@
"deep-equal": "1.1.0", "deep-equal": "1.1.0",
"diskusage": "1.1.3", "diskusage": "1.1.3",
"double-ended-queue": "2.1.0-0", "double-ended-queue": "2.1.0-0",
"eslint": "6.4.0", "eslint": "6.5.1",
"eslint-plugin-vue": "5.2.3", "eslint-plugin-vue": "5.2.3",
"eventemitter3": "4.0.0", "eventemitter3": "4.0.0",
"feed": "4.0.0", "feed": "4.0.0",
@@ -133,8 +134,7 @@
"fluent-ffmpeg": "2.1.2", "fluent-ffmpeg": "2.1.2",
"gulp": "4.0.2", "gulp": "4.0.2",
"gulp-cssnano": "2.1.3", "gulp-cssnano": "2.1.3",
"gulp-imagemin": "6.1.0", "gulp-mocha": "7.0.2",
"gulp-mocha": "7.0.1",
"gulp-rename": "1.4.0", "gulp-rename": "1.4.0",
"gulp-replace": "1.0.0", "gulp-replace": "1.0.0",
"gulp-sourcemaps": "2.6.5", "gulp-sourcemaps": "2.6.5",
@@ -146,17 +146,17 @@
"hard-source-webpack-plugin": "0.13.1", "hard-source-webpack-plugin": "0.13.1",
"html-minifier": "4.0.0", "html-minifier": "4.0.0",
"http-signature": "1.2.0", "http-signature": "1.2.0",
"https-proxy-agent": "2.2.2", "https-proxy-agent": "3.0.0",
"insert-text-at-cursor": "0.3.0", "insert-text-at-cursor": "0.3.0",
"is-root": "2.1.0", "is-root": "2.1.0",
"is-svg": "4.2.0", "is-svg": "4.2.0",
"js-yaml": "3.13.1", "js-yaml": "3.13.1",
"jsdom": "15.1.1", "jsdom": "15.1.1",
"json5": "2.1.0", "json5": "2.1.1",
"json5-loader": "3.0.0", "json5-loader": "3.0.0",
"jsrsasign": "8.0.12", "jsrsasign": "8.0.12",
"katex": "0.11.1", "katex": "0.11.1",
"koa": "2.8.1", "koa": "2.10.0",
"koa-bodyparser": "4.2.1", "koa-bodyparser": "4.2.1",
"koa-compress": "3.0.0", "koa-compress": "3.0.0",
"koa-favicon": "2.0.1", "koa-favicon": "2.0.1",
@@ -170,13 +170,13 @@
"loader-utils": "1.2.3", "loader-utils": "1.2.3",
"lolex": "4.2.0", "lolex": "4.2.0",
"lookup-dns-cache": "2.1.0", "lookup-dns-cache": "2.1.0",
"mocha": "6.2.0", "mocha": "6.2.1",
"moji": "0.5.1", "moji": "0.5.1",
"ms": "2.1.2", "ms": "2.1.2",
"multer": "1.4.2", "multer": "1.4.2",
"nested-property": "1.0.1", "nested-property": "1.0.1",
"node-fetch": "2.6.0", "node-fetch": "2.6.0",
"nodemailer": "6.3.0", "nodemailer": "6.3.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",
@@ -220,22 +220,22 @@
"stylus-loader": "3.0.2", "stylus-loader": "3.0.2",
"summaly": "2.3.1", "summaly": "2.3.1",
"syslog-pro": "1.0.0", "syslog-pro": "1.0.0",
"systeminformation": "4.14.8", "systeminformation": "4.14.11",
"syuilo-password-strength": "0.0.1", "syuilo-password-strength": "0.0.1",
"terser-webpack-plugin": "2.1.0", "terser-webpack-plugin": "2.1.3",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.108.0", "three": "0.109.0",
"tinycolor2": "1.4.1", "tinycolor2": "1.4.1",
"tmp": "0.1.0", "tmp": "0.1.0",
"ts-loader": "6.1.2", "ts-loader": "6.2.0",
"ts-node": "8.4.1", "ts-node": "8.4.1",
"tslint": "5.20.0", "tslint": "5.20.0",
"tslint-sonarts": "1.9.0", "tslint-sonarts": "1.9.0",
"typeorm": "0.2.19", "typeorm": "0.2.19",
"typescript": "3.6.3", "typescript": "3.6.4",
"uglify-es": "3.3.9", "uglify-es": "3.3.9",
"ulid": "2.3.0", "ulid": "2.3.0",
"url-loader": "2.1.0", "url-loader": "2.2.0",
"uuid": "3.3.3", "uuid": "3.3.3",
"v-animate-css": "0.0.3", "v-animate-css": "0.0.3",
"v-debounce": "0.1.2", "v-debounce": "0.1.2",
@@ -252,14 +252,14 @@
"vue-router": "3.1.3", "vue-router": "3.1.3",
"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.3.0", "vue-svg-inline-loader": "1.3.3",
"vue-template-compiler": "2.6.10", "vue-template-compiler": "2.6.10",
"vuedraggable": "2.23.1", "vuedraggable": "2.23.2",
"vuewordcloud": "18.7.11", "vuewordcloud": "18.7.11",
"vuex": "3.1.1", "vuex": "3.1.1",
"vuex-persistedstate": "2.5.4", "vuex-persistedstate": "2.5.4",
"web-push": "3.3.5", "web-push": "3.4.0",
"webpack": "4.41.0", "webpack": "4.41.1",
"webpack-cli": "3.3.9", "webpack-cli": "3.3.9",
"websocket": "1.0.30", "websocket": "1.0.30",
"ws": "7.1.2", "ws": "7.1.2",

View File

@@ -8,6 +8,9 @@
<span>{{ $t('add-emoji.name') }}</span> <span>{{ $t('add-emoji.name') }}</span>
<template #desc>{{ $t('add-emoji.name-desc') }}</template> <template #desc>{{ $t('add-emoji.name-desc') }}</template>
</ui-input> </ui-input>
<ui-input v-model="category" :datalist="categoryList">
<span>{{ $t('add-emoji.category') }}</span>
</ui-input>
<ui-input v-model="aliases"> <ui-input v-model="aliases">
<span>{{ $t('add-emoji.aliases') }}</span> <span>{{ $t('add-emoji.aliases') }}</span>
<template #desc>{{ $t('add-emoji.aliases-desc') }}</template> <template #desc>{{ $t('add-emoji.aliases-desc') }}</template>
@@ -24,7 +27,7 @@
<ui-card> <ui-card>
<template #title><fa :icon="faGrin"/> {{ $t('emojis.title') }}</template> <template #title><fa :icon="faGrin"/> {{ $t('emojis.title') }}</template>
<section v-for="emoji in emojis" class="oryfrbft"> <section v-for="emoji in emojis" :key="emoji.name" class="oryfrbft">
<div> <div>
<img :src="emoji.url" :alt="emoji.name" style="width: 64px;"/> <img :src="emoji.url" :alt="emoji.name" style="width: 64px;"/>
</div> </div>
@@ -33,6 +36,9 @@
<ui-input v-model="emoji.name"> <ui-input v-model="emoji.name">
<span>{{ $t('add-emoji.name') }}</span> <span>{{ $t('add-emoji.name') }}</span>
</ui-input> </ui-input>
<ui-input v-model="emoji.category" :datalist="categoryList">
<span>{{ $t('add-emoji.category') }}</span>
</ui-input>
<ui-input v-model="emoji.aliases"> <ui-input v-model="emoji.aliases">
<span>{{ $t('add-emoji.aliases') }}</span> <span>{{ $t('add-emoji.aliases') }}</span>
</ui-input> </ui-input>
@@ -55,12 +61,14 @@
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../i18n'; import i18n from '../../i18n';
import { faGrin } from '@fortawesome/free-regular-svg-icons'; import { faGrin } from '@fortawesome/free-regular-svg-icons';
import { unique } from '../../../../prelude/array';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('admin/views/emoji.vue'), i18n: i18n('admin/views/emoji.vue'),
data() { data() {
return { return {
name: '', name: '',
category: '',
url: '', url: '',
aliases: '', aliases: '',
emojis: [], emojis: [],
@@ -72,10 +80,17 @@ export default Vue.extend({
this.fetchEmojis(); this.fetchEmojis();
}, },
computed: {
categoryList() {
return unique(this.emojis.map((x: any) => x.category || '').filter((x: string) => x !== ''));
}
},
methods: { methods: {
add() { add() {
this.$root.api('admin/emoji/add', { this.$root.api('admin/emoji/add', {
name: this.name, name: this.name,
category: this.category,
url: this.url, url: this.url,
aliases: this.aliases.split(' ').filter(x => x.length > 0) aliases: this.aliases.split(' ').filter(x => x.length > 0)
}).then(() => { }).then(() => {
@@ -94,7 +109,6 @@ export default Vue.extend({
fetchEmojis() { fetchEmojis() {
this.$root.api('admin/emoji/list').then(emojis => { this.$root.api('admin/emoji/list').then(emojis => {
emojis.reverse();
for (const e of emojis) { for (const e of emojis) {
e.aliases = (e.aliases || []).join(' '); e.aliases = (e.aliases || []).join(' ');
} }
@@ -106,6 +120,7 @@ export default Vue.extend({
this.$root.api('admin/emoji/update', { this.$root.api('admin/emoji/update', {
id: emoji.id, id: emoji.id,
name: emoji.name, name: emoji.name,
category: emoji.category,
url: emoji.url, url: emoji.url,
aliases: emoji.aliases.split(' ').filter(x => x.length > 0) aliases: emoji.aliases.split(' ').filter(x => x.length > 0)
}).then(() => { }).then(() => {

View File

@@ -43,7 +43,7 @@ export default (opts: Opts = {}) => ({
'ctrl+q': this.renoteDirectly, 'ctrl+q': this.renoteDirectly,
'up|k|shift+tab': this.focusBefore, 'up|k|shift+tab': this.focusBefore,
'down|j|tab': this.focusAfter, 'down|j|tab': this.focusAfter,
'esc': this.blur, //'esc': this.blur,
'm|o': () => this.menu(true), 'm|o': () => this.menu(true),
's': this.toggleShowContent, 's': this.toggleShowContent,
'1': () => this.reactDirectly('like'), '1': () => this.reactDirectly('like'),
@@ -143,12 +143,15 @@ export default (opts: Opts = {}) => ({
react(viaKeyboard = false) { react(viaKeyboard = false) {
pleaseLogin(this.$root); pleaseLogin(this.$root);
this.blur(); this.blur();
this.$root.new(MkReactionPicker, { const w = this.$root.new(MkReactionPicker, {
source: this.$refs.reactButton, source: this.$refs.reactButton,
note: this.appearNote, note: this.appearNote,
showFocus: viaKeyboard, showFocus: viaKeyboard,
animation: !viaKeyboard animation: !viaKeyboard
}).$once('closed', this.focus); }).$once('closed', this.focus);
this.$once('hook:beforeDestroy', () => {
w.close();
});
}, },
reactDirectly(reaction) { reactDirectly(reaction) {
@@ -195,7 +198,7 @@ export default (opts: Opts = {}) => ({
menu(viaKeyboard = false) { menu(viaKeyboard = false) {
if (this.openingMenu) return; if (this.openingMenu) return;
this.openingMenu = true; this.openingMenu = true;
this.$root.new(MkNoteMenu, { const w = this.$root.new(MkNoteMenu, {
source: this.$refs.menuButton, source: this.$refs.menuButton,
note: this.appearNote, note: this.appearNote,
animation: !viaKeyboard animation: !viaKeyboard
@@ -203,6 +206,9 @@ export default (opts: Opts = {}) => ({
this.openingMenu = false; this.openingMenu = false;
this.focus(); this.focus();
}); });
this.$once('hook:beforeDestroy', () => {
w.destroyDom();
});
}, },
toggleShowContent() { toggleShowContent() {

View File

@@ -328,6 +328,9 @@ export default (opts) => ({
w.$once('chosen', v => { w.$once('chosen', v => {
this.applyVisibility(v); this.applyVisibility(v);
}); });
this.$once('hook:beforeDestroy', () => {
w.close();
});
}, },
applyVisibility(v: string) { applyVisibility(v: string) {
@@ -457,6 +460,9 @@ export default (opts) => ({
vm.$once('chosen', emoji => { vm.$once('chosen', emoji => {
insertTextAtCursor(this.$refs.text, emoji); insertTextAtCursor(this.$refs.text, emoji);
}); });
this.$once('hook:beforeDestroy', () => {
vm.close();
});
}, },
saveDraft() { saveDraft() {

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="mk-connect-failed"> <div class="mk-connect-failed">
<img src="https://raw.githubusercontent.com/syuilo/misskey/develop/src/client/assets/error.jpg" alt=""/> <img src="/assets/error.jpg" onerror="this.src='https://raw.githubusercontent.com/syuilo/misskey/develop/src/client/assets/error.jpg';" alt=""/>
<h1>{{ $t('title') }}</h1> <h1>{{ $t('title') }}</h1>
<p class="text"> <p class="text">
<span>{{ this.$t('description').substr(0, this.$t('description').indexOf('{')) }}</span> <span>{{ this.$t('description').substr(0, this.$t('description').indexOf('{')) }}</span>

View File

@@ -1,6 +1,12 @@
<template> <template>
<div class="felqjxyj" :class="{ splash }"> <ui-modal
<div class="bg" ref="bg" @click="onBgClick"></div> ref="modal"
class="modal"
:class="{ splash }"
:close-anime-duration="300"
:close-on-bg-click="false"
@bg-click="onBgClick"
@before-close="onBeforeClose">
<div class="main" ref="main" :class="{ round: $store.state.device.roundedCorners }"> <div class="main" ref="main" :class="{ round: $store.state.device.roundedCorners }">
<template v-if="type == 'signin'"> <template v-if="type == 'signin'">
<mk-signin/> <mk-signin/>
@@ -38,7 +44,7 @@
</ui-horizon-group> </ui-horizon-group>
</template> </template>
</div> </div>
</div> </ui-modal>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -120,14 +126,6 @@ export default Vue.extend({
if (this.user) this.canOk = false; if (this.user) this.canOk = false;
this.$nextTick(() => { this.$nextTick(() => {
(this.$refs.bg as any).style.pointerEvents = 'auto';
anime({
targets: this.$refs.bg,
opacity: 1,
duration: 100,
easing: 'linear'
});
anime({ anime({
targets: this.$refs.main, targets: this.$refs.main,
opacity: 1, opacity: 1,
@@ -170,33 +168,27 @@ export default Vue.extend({
this.close(); this.close();
}, },
onBgClick() {
if (this.cancelableByBgClick) this.cancel();
}
close() { close() {
this.$refs.modal.close();
},
onBeforeClose() {
this.$el.style.pointerEvents = 'none'; this.$el.style.pointerEvents = 'none';
(this.$refs.bg as any).style.pointerEvents = 'none';
(this.$refs.main as any).style.pointerEvents = 'none'; (this.$refs.main as any).style.pointerEvents = 'none';
anime({
targets: this.$refs.bg,
opacity: 0,
duration: 300,
easing: 'linear'
});
anime({ anime({
targets: this.$refs.main, targets: this.$refs.main,
opacity: 0, opacity: 0,
scale: 0.8, scale: 0.8,
duration: 300, duration: 300,
easing: 'cubicBezier(0, 0.5, 0.5, 1)', easing: 'cubicBezier(0, 0.5, 0.5, 1)',
complete: () => this.destroyDom()
}); });
}, },
onBgClick() {
if (this.cancelableByBgClick) {
this.cancel();
}
},
onInputKeydown(e) { onInputKeydown(e) {
if (e.which == 13) { // Enter if (e.which == 13) { // Enter
e.preventDefault(); e.preventDefault();
@@ -209,80 +201,63 @@ export default Vue.extend({
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.felqjxyj .modal
display flex display flex
align-items center align-items center
justify-content center justify-content center
position fixed
z-index 30000
top 0
left 0
width 100%
height 100%
&.splash &.splash
> .main > .main
min-width 0 min-width 0
width initial width initial
> .bg .main
display block display block
position fixed position fixed
top 0 margin auto
left 0 padding 32px
width 100% min-width 320px
height 100% max-width 480px
background rgba(#000, 0.7) width calc(100% - 32px)
opacity 0 text-align center
pointer-events none background var(--face)
color var(--faceText)
opacity 0
> .main &.round
display block border-radius 8px
position fixed
margin auto
padding 32px
min-width 320px
max-width 480px
width calc(100% - 32px)
text-align center
background var(--face)
color var(--faceText)
opacity 0
&.round > .icon
border-radius 8px font-size 32px
> .icon &.success
font-size 32px color #85da5a
&.success &.error
color #85da5a color #ec4137
&.error &.warning
color #ec4137 color #ecb637
&.warning > *
color #ecb637 display block
margin 0 auto
> * & + header
display block
margin 0 auto
& + header
margin-top 16px
> header
margin 0 0 8px 0
font-weight bold
font-size 20px
& + .body
margin-top 8px
> .body
margin 16px 0 0 0
> .buttons
margin-top 16px margin-top 16px
> header
margin 0 0 8px 0
font-weight bold
font-size 20px
& + .body
margin-top 8px
> .body
margin 16px 0 0 0
> .buttons
margin-top 16px
</style> </style>

View File

@@ -11,25 +11,46 @@
</button> </button>
</header> </header>
<div class="emojis"> <div class="emojis">
<header><fa :icon="categories.find(x => x.isActive).icon" fixed-width/> {{ categories.find(x => x.isActive).text }}</header> <template v-if="categories[0].isActive">
<div v-if="categories.find(x => x.isActive).name"> <header class="category"><fa :icon="faHistory" fixed-width/> {{ $t('recent-emoji') }}</header>
<button v-for="emoji in emojilist.filter(e => e.category === categories.find(x => x.isActive).name)" <div class="list">
:title="emoji.name" <button v-for="(emoji, i) in ($store.state.device.recentEmojis || [])"
@click="chosen(emoji.char)" :title="emoji.name"
:key="emoji.name" @click="chosen(emoji)"
> :key="i"
<mk-emoji :emoji="emoji.char"/> >
</button> <mk-emoji v-if="emoji.char != null" :emoji="emoji.char"/>
</div> <img v-else :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
<div v-else> </button>
<button v-for="emoji in customEmojis" </div>
:title="emoji.name" </template>
@click="chosen(`:${emoji.name}:`)"
:key="emoji.name" <header class="category"><fa :icon="categories.find(x => x.isActive).icon" fixed-width/> {{ categories.find(x => x.isActive).text }}</header>
> <template v-if="categories.find(x => x.isActive).name">
<img :src="emoji.url" :alt="emoji.name"/> <div class="list">
</button> <button v-for="emoji in emojilist.filter(e => e.category === categories.find(x => x.isActive).name)"
</div> :title="emoji.name"
@click="chosen(emoji)"
:key="emoji.name"
>
<mk-emoji :emoji="emoji.char"/>
</button>
</div>
</template>
<template v-else>
<div v-for="(key, i) in Object.keys(customEmojis)" :key="i">
<header class="sub">{{ key || $t('no-category') }}</header>
<div class="list">
<button v-for="emoji in customEmojis[key]"
:title="emoji.name"
@click="chosen(emoji)"
:key="emoji.name"
>
<img :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
</button>
</div>
</div>
</template>
</div> </div>
</div> </div>
</template> </template>
@@ -38,8 +59,10 @@
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import { emojilist } from '../../../../../misc/emojilist'; import { emojilist } from '../../../../../misc/emojilist';
import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice } from '@fortawesome/free-solid-svg-icons'; import { getStaticImageUrl } from '../../../common/scripts/get-static-image-url';
import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice, faGlobe, faHistory } from '@fortawesome/free-solid-svg-icons';
import { faHeart, faFlag } from '@fortawesome/free-regular-svg-icons'; import { faHeart, faFlag } from '@fortawesome/free-regular-svg-icons';
import { groupByX } from '../../../../../prelude/array';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('common/views/components/emoji-picker.vue'), i18n: i18n('common/views/components/emoji-picker.vue'),
@@ -47,7 +70,9 @@ export default Vue.extend({
data() { data() {
return { return {
emojilist, emojilist,
customEmojis: [], getStaticImageUrl,
customEmojis: {},
faGlobe, faHistory,
categories: [{ categories: [{
text: this.$t('custom-emoji'), text: this.$t('custom-emoji'),
icon: faAsterisk, icon: faAsterisk,
@@ -97,18 +122,43 @@ export default Vue.extend({
}, },
created() { created() {
this.customEmojis = (this.$root.getMetaSync() || { emojis: [] }).emojis || []; let local = (this.$root.getMetaSync() || { emojis: [] }).emojis || [];
local = groupByX(local, (x: any) => x.category || '');
this.customEmojis = local;
if (this.$store.state.device.activeEmojiCategoryName) {
this.goCategory(this.$store.state.device.activeEmojiCategoryName);
}
}, },
methods: { methods: {
go(category) { go(category: any) {
this.goCategory(category.name);
},
goCategory(name: string) {
let matched = false;
for (const c of this.categories) { for (const c of this.categories) {
c.isActive = c.name === category.name; c.isActive = c.name === name;
if (c.isActive) {
matched = true;
this.$store.commit('device/set', { key: 'activeEmojiCategoryName', value: c.name });
}
}
if (!matched) {
this.categories[0].isActive = true;
} }
}, },
chosen(emoji) { chosen(emoji: any) {
this.$emit('chosen', emoji); const getKey = (emoji: any) => emoji.char || `:${emoji.name}:`;
let recents = this.$store.state.device.recentEmojis || [];
recents = recents.filter((e: any) => getKey(e) !== getKey(emoji));
recents.unshift(emoji)
this.$store.commit('device/set', { key: 'recentEmojis', value: recents.splice(0, 16) });
this.$emit('chosen', getKey(emoji));
} }
} }
}); });
@@ -142,7 +192,7 @@ export default Vue.extend({
overflow-y auto overflow-y auto
overflow-x hidden overflow-x hidden
> header > header.category
position sticky position sticky
top 0 top 0
left 0 left 0
@@ -152,7 +202,12 @@ export default Vue.extend({
color var(--text) color var(--text)
font-size 12px font-size 12px
> div >>> header.sub
padding 4px 8px
color var(--text)
font-size 12px
>>> div.list
display grid display grid
grid-template-columns 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr grid-template-columns 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr
gap 4px gap 4px
@@ -180,6 +235,7 @@ export default Vue.extend({
left 0 left 0
width 100% width 100%
height 100% height 100%
object-fit contain
font-size 28px font-size 28px
transition transform 0.2s ease transition transform 0.2s ease
pointer-events none pointer-events none

View File

@@ -1,70 +1,41 @@
<template> <template>
<div class="dkjvrdxtkvqrwmhfickhndpmnncsgacq"> <ui-modal ref="modal" v-hotkey.global="keymap">
<div class="bg" @click="close"></div> <img :src="image.url" :alt="image.name" :title="image.name" @click="close" />
<img :src="image.url" :alt="image.name" :title="image.name" @click="close"/> </ui-modal>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import anime from 'animejs';
export default Vue.extend({ export default Vue.extend({
props: ['image'], props: ['image'],
mounted() { computed: {
anime({ keymap(): any {
targets: this.$el, return {
opacity: 1, 'esc': this.close,
duration: 100, };
easing: 'linear' }
});
}, },
methods: { methods: {
close() { close() {
anime({ (this.$refs.modal as any).close();
targets: this.$el,
opacity: 0,
duration: 100,
easing: 'linear',
complete: () => this.destroyDom()
});
} }
} }
}); });
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.dkjvrdxtkvqrwmhfickhndpmnncsgacq img
display block
position fixed position fixed
z-index 2048 z-index 2
top 0 top 0
right 0
bottom 0
left 0 left 0
width 100% max-width 100%
height 100% max-height 100%
opacity 0 margin auto
cursor zoom-out
> .bg image-orientation from-image
display block
position fixed
z-index 1
top 0
left 0
width 100%
height 100%
background rgba(#000, 0.7)
> img
position fixed
z-index 2
top 0
right 0
bottom 0
left 0
max-width 100%
max-height 100%
margin auto
cursor zoom-out
image-orientation from-image
</style> </style>

View File

@@ -47,6 +47,7 @@ import uiInfo from './ui/info.vue';
import uiMargin from './ui/margin.vue'; import uiMargin from './ui/margin.vue';
import uiHr from './ui/hr.vue'; import uiHr from './ui/hr.vue';
import uiPagination from './ui/pagination.vue'; import uiPagination from './ui/pagination.vue';
import uiModal from './ui/modal.vue';
import formButton from './ui/form/button.vue'; import formButton from './ui/form/button.vue';
import formRadio from './ui/form/radio.vue'; import formRadio from './ui/form/radio.vue';
@@ -97,5 +98,6 @@ Vue.component('ui-info', uiInfo);
Vue.component('ui-margin', uiMargin); Vue.component('ui-margin', uiMargin);
Vue.component('ui-hr', uiHr); Vue.component('ui-hr', uiHr);
Vue.component('ui-pagination', uiPagination); Vue.component('ui-pagination', uiPagination);
Vue.component('ui-modal', uiModal);
Vue.component('form-button', formButton); Vue.component('form-button', formButton);
Vue.component('form-radio', formRadio); Vue.component('form-radio', formRadio);

View File

@@ -59,9 +59,12 @@ export default Vue.extend({
}, },
methods: { methods: {
onClick() { onClick() {
this.$root.new(ImageViewer, { const viewer = this.$root.new(ImageViewer, {
image: this.image image: this.image
}); });
this.$once('hook:beforeDestroy', () => {
viewer.close();
});
} }
} }
}); });

View File

@@ -138,7 +138,7 @@ export default Vue.extend({
} }
this.$root.api('users/search', { this.$root.api('users/search', {
query: this.q, query: this.q,
localOnly: true, localOnly: false,
limit: 10, limit: 10,
detail: false detail: false
}).then(users => { }).then(users => {

View File

@@ -19,6 +19,7 @@ export default Vue.extend({
&.nowrap &.nowrap
white-space pre white-space pre
word-wrap normal // https://codeday.me/jp/qa/20190424/690106.html
>>> .title >>> .title
display block display block

View File

@@ -4,16 +4,7 @@
<div class="popover" :class="{ isMobile: $root.isMobile }" ref="popover"> <div class="popover" :class="{ isMobile: $root.isMobile }" ref="popover">
<p v-if="!$root.isMobile">{{ title }}</p> <p v-if="!$root.isMobile">{{ title }}</p>
<div class="buttons" ref="buttons" :class="{ showFocus }"> <div class="buttons" ref="buttons" :class="{ showFocus }">
<button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" :title="$t('@.reactions.like')" v-particle><mk-reaction-icon reaction="like"/></button> <button v-for="(reaction, i) in $store.state.settings.reactions" :key="reaction" @click="react(reaction)" @mouseover="onMouseover" @mouseout="onMouseout" :tabindex="i + 1" :title="/^[a-z]+$/.test(reaction) ? $t('@.reactions.' + reaction) : reaction" v-particle><mk-reaction-icon :reaction="reaction"/></button>
<button @click="react('love')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="2" :title="$t('@.reactions.love')" v-particle><mk-reaction-icon reaction="love"/></button>
<button @click="react('laugh')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="3" :title="$t('@.reactions.laugh')" v-particle><mk-reaction-icon reaction="laugh"/></button>
<button @click="react('hmm')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="4" :title="$t('@.reactions.hmm')" v-particle><mk-reaction-icon reaction="hmm"/></button>
<button @click="react('surprise')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="5" :title="$t('@.reactions.surprise')" v-particle><mk-reaction-icon reaction="surprise"/></button>
<button @click="react('congrats')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="6" :title="$t('@.reactions.congrats')" v-particle><mk-reaction-icon reaction="congrats"/></button>
<button @click="react('angry')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="7" :title="$t('@.reactions.angry')" v-particle><mk-reaction-icon reaction="angry"/></button>
<button @click="react('confused')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="8" :title="$t('@.reactions.confused')" v-particle><mk-reaction-icon reaction="confused"/></button>
<button @click="react('rip')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="9" :title="$t('@.reactions.rip')" v-particle><mk-reaction-icon reaction="rip"/></button>
<button @click="react('pudding')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="10" :title="$t('@.reactions.pudding')" v-particle><mk-reaction-icon reaction="pudding"/></button>
</div> </div>
<div v-if="enableEmojiReaction" class="text"> <div v-if="enableEmojiReaction" class="text">
<input v-model="text" :placeholder="$t('input-reaction-placeholder')" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }"> <input v-model="text" :placeholder="$t('input-reaction-placeholder')" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }">

View File

@@ -108,6 +108,13 @@
<ui-switch v-if="$root.isMobile" v-model="disableViaMobile">{{ $t('@._settings.disable-via-mobile') }}</ui-switch> <ui-switch v-if="$root.isMobile" v-model="disableViaMobile">{{ $t('@._settings.disable-via-mobile') }}</ui-switch>
</section> </section>
<section>
<header>{{ $t('@._settings.reactions') }}</header>
<ui-textarea v-model="reactions">
{{ $t('@._settings.reactions') }}<template #desc>{{ $t('@._settings.reactions-description') }}</template>
</ui-textarea>
</section>
<section> <section>
<header>{{ $t('@._settings.timeline') }}</header> <header>{{ $t('@._settings.timeline') }}</header>
<ui-switch v-model="showMyRenotes">{{ $t('@._settings.show-my-renotes') }}</ui-switch> <ui-switch v-model="showMyRenotes">{{ $t('@._settings.show-my-renotes') }}</ui-switch>
@@ -407,6 +414,11 @@ export default Vue.extend({
set(value) { this.$store.dispatch('settings/set', { key: 'disableViaMobile', value }); } set(value) { this.$store.dispatch('settings/set', { key: 'disableViaMobile', value }); }
}, },
reactions: {
get() { return this.$store.state.settings.reactions.join('\n'); },
set(value: string) { this.$store.dispatch('settings/set', { key: 'reactions', value: value.trim().split('\n') }); }
},
useShadow: { useShadow: {
get() { return this.$store.state.device.useShadow; }, get() { return this.$store.state.device.useShadow; },
set(value) { this.$store.commit('device/set', { key: 'useShadow', value }); } set(value) { this.$store.commit('device/set', { key: 'useShadow', value }); }

View File

@@ -6,7 +6,10 @@
<div class="value" ref="passwordMetar"></div> <div class="value" ref="passwordMetar"></div>
</div> </div>
<span class="label" ref="label"><slot></slot></span> <span class="label" ref="label"><slot></slot></span>
<span class="title" ref="title"><slot name="title"></slot></span> <span class="title" ref="title">
<slot name="title"></slot>
<span class="warning" v-if="invalid"><fa :icon="['fa', 'exclamation-circle']"/>{{ $refs.input.validationMessage }}</span>
</span>
<div class="prefix" ref="prefix"><slot name="prefix"></slot></div> <div class="prefix" ref="prefix"><slot name="prefix"></slot></div>
<template v-if="type != 'file'"> <template v-if="type != 'file'">
<input v-if="debounce" ref="input" <input v-if="debounce" ref="input"
@@ -158,6 +161,7 @@ export default Vue.extend({
return { return {
v: this.value, v: this.value,
focused: false, focused: false,
invalid: false,
passwordStrength: '', passwordStrength: '',
id: Math.random().toString() id: Math.random().toString()
}; };
@@ -200,6 +204,8 @@ export default Vue.extend({
this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low'; this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
(this.$refs.passwordMetar as any).style.width = `${strength * 100}%`; (this.$refs.passwordMetar as any).style.width = `${strength * 100}%`;
} }
this.invalid = this.$refs.input.validity.badInput;
} }
}, },
mounted() { mounted() {
@@ -365,6 +371,17 @@ root(fill)
//will-change transform //will-change transform
transform-origin top left transform-origin top left
transform scale(.75) transform scale(.75)
white-space nowrap
width 133%
overflow hidden
text-overflow ellipsis
> .warning
margin-left 0.5em
color var(--infoWarnFg)
> svg
margin-right 0.1em
> input > input
display block display block

View File

@@ -0,0 +1,80 @@
<template>
<div class="modal">
<div class="bg" ref="bg" @click="onBgClick" />
<slot class="main" />
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import anime from 'animejs';
export default Vue.extend({
props: {
closeOnBgClick: {
type: Boolean,
required: false,
default: true
},
openAnimeDuration: {
type: Number,
required: false,
default: 100
},
closeAnimeDuration: {
type: Number,
required: false,
default: 100
}
},
mounted() {
anime({
targets: this.$refs.bg,
opacity: 1,
duration: this.openAnimeDuration,
easing: 'linear'
});
},
methods: {
onBgClick() {
this.$emit('bg-click');
if (this.closeOnBgClick) this.close();
},
close() {
this.$emit('before-close');
anime({
targets: this.$refs.bg,
opacity: 0,
duration: this.closeAnimeDuration,
easing: 'linear',
complete: () => (this as any).destroyDom()
});
}
}
});
</script>
<style lang="stylus" scoped>
.modal
position fixed
z-index 2048
top 0
left 0
width 100%
height 100%
.bg
display block
position fixed
z-index 1
top 0
left 0
width 100%
height 100%
background rgba(#000, 0.7)
opacity 0
.main
z-index 1
</style>

View File

@@ -1,5 +1,5 @@
<template> <template>
<ui-container :body-togglable="true"> <ui-container :body-togglable="true" :expanded="expanded">
<template #header><slot></slot></template> <template #header><slot></slot></template>
<mk-error v-if="error" @retry="init()"/> <mk-error v-if="error" @retry="init()"/>
@@ -50,7 +50,11 @@ export default Vue.extend({
iconOnly: { iconOnly: {
type: Boolean, type: Boolean,
default: false default: false
} },
expanded: {
type: Boolean,
default: true
},
}, },
computed: { computed: {

View File

@@ -27,7 +27,7 @@
<div class="description"> <div class="description">
<mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :key="user.id"/> <mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :key="user.id"/>
</div> </div>
<div class="fields" v-if="user.fields"> <div class="fields" v-if="user.fields" :key="user.id">
<dl class="field" v-for="(field, i) in user.fields" :key="i"> <dl class="field" v-for="(field, i) in user.fields" :key="i">
<dt class="name"> <dt class="name">
<mfm :text="field.name" :plain="true" :custom-emojis="user.emojis"/> <mfm :text="field.name" :plain="true" :custom-emojis="user.emojis"/>
@@ -112,10 +112,13 @@ export default Vue.extend({
}, },
menu() { menu() {
this.$root.new(XUserMenu, { const w = this.$root.new(XUserMenu, {
source: this.$refs.menu, source: this.$refs.menu,
user: this.user user: this.user
}); });
this.$once('hook:beforeDestroy', () => {
w.destroyDom();
});
} }
} }
}); });

View File

@@ -1,15 +1,30 @@
<template> <template>
<div> <div>
<ui-container :show-header="false" v-if="meta && stats"> <div class="localfedi7" v-if="meta && stats && tag == null" :style="{ backgroundImage: meta.bannerUrl ? `url(${meta.bannerUrl})` : null }">
<div class="kpdsmpnk" :style="{ backgroundImage: meta.bannerUrl ? `url(${meta.bannerUrl})` : null }"> <header>{{ $t('explore', { host: meta.name || 'Misskey' }) }}</header>
<div> <div>{{ $t('users-info', { users: num(stats.originalUsersCount) }) }}</div>
<router-link to="/explore" class="title">{{ $t('explore', { host: meta.name || 'Misskey' }) }}</router-link> </div>
<span>{{ $t('users-info', { users: num(stats.originalUsersCount) }) }}</span>
</div>
</div>
</ui-container>
<ui-container :body-togglable="true" :expanded="tag == null" ref="tags"> <template v-if="tag == null">
<mk-user-list :pagination="pinnedUsers" :expanded="false">
<fa :icon="faBookmark" fixed-width/>{{ $t('pinned-users') }}
</mk-user-list>
<mk-user-list :pagination="popularUsers" :expanded="false">
<fa :icon="faChartLine" fixed-width/>{{ $t('popular-users') }}
</mk-user-list>
<mk-user-list :pagination="recentlyUpdatedUsers" :expanded="false">
<fa :icon="faCommentAlt" fixed-width/>{{ $t('recently-updated-users') }}
</mk-user-list>
<mk-user-list :pagination="recentlyRegisteredUsers" :expanded="false">
<fa :icon="faPlus" fixed-width/>{{ $t('recently-registered-users') }}
</mk-user-list>
</template>
<div class="localfedi7" v-if="tag == null" :style="{ backgroundImage: `url(/assets/fedi.jpg)` }">
<header>{{ $t('explore-fediverse') }}</header>
</div>
<ui-container :body-togglable="true" :expanded="false" ref="tags">
<template #header><fa :icon="faHashtag" fixed-width/>{{ $t('popular-tags') }}</template> <template #header><fa :icon="faHashtag" fixed-width/>{{ $t('popular-tags') }}</template>
<div class="vxjfqztj"> <div class="vxjfqztj">
@@ -18,25 +33,18 @@
</div> </div>
</ui-container> </ui-container>
<mk-user-list v-if="tag != null" :pagination="tagUsers" :key="`${tag}-local`"> <mk-user-list v-if="tag != null" :pagination="tagUsers" :key="`${tag}`">
<fa :icon="faHashtag" fixed-width/>{{ tag }} <fa :icon="faHashtag" fixed-width/>{{ tag }}
</mk-user-list> </mk-user-list>
<mk-user-list v-if="tag != null" :pagination="tagRemoteUsers" :key="`${tag}-remote`">
<fa :icon="faHashtag" fixed-width/>{{ tag }} ({{ $t('federated') }})
</mk-user-list>
<template v-if="tag == null"> <template v-if="tag == null">
<mk-user-list :pagination="pinnedUsers"> <mk-user-list :pagination="popularUsersF" :expanded="false">
<fa :icon="faBookmark" fixed-width/>{{ $t('pinned-users') }}
</mk-user-list>
<mk-user-list :pagination="popularUsers">
<fa :icon="faChartLine" fixed-width/>{{ $t('popular-users') }} <fa :icon="faChartLine" fixed-width/>{{ $t('popular-users') }}
</mk-user-list> </mk-user-list>
<mk-user-list :pagination="recentlyUpdatedUsers"> <mk-user-list :pagination="recentlyUpdatedUsersF" :expanded="false">
<fa :icon="faCommentAlt" fixed-width/>{{ $t('recently-updated-users') }} <fa :icon="faCommentAlt" fixed-width/>{{ $t('recently-updated-users') }}
</mk-user-list> </mk-user-list>
<mk-user-list :pagination="recentlyRegisteredUsers"> <mk-user-list :pagination="recentlyRegisteredUsersF" :expanded="false">
<fa :icon="faPlus" fixed-width/>{{ $t('recently-registered-users') }} <fa :icon="faRocket" fixed-width/>{{ $t('recently-discovered-users') }}
</mk-user-list> </mk-user-list>
</template> </template>
</div> </div>
@@ -45,7 +53,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import { faChartLine, faPlus, faHashtag } from '@fortawesome/free-solid-svg-icons'; import { faChartLine, faPlus, faHashtag, faRocket } from '@fortawesome/free-solid-svg-icons';
import { faBookmark, faCommentAlt } from '@fortawesome/free-regular-svg-icons'; import { faBookmark, faCommentAlt } from '@fortawesome/free-regular-svg-icons';
export default Vue.extend({ export default Vue.extend({
@@ -58,6 +66,12 @@ export default Vue.extend({
} }
}, },
inject: {
inNakedDeckColumn: {
default: false
}
},
data() { data() {
return { return {
pinnedUsers: { endpoint: 'pinned-users' }, pinnedUsers: { endpoint: 'pinned-users' },
@@ -75,12 +89,25 @@ export default Vue.extend({
state: 'alive', state: 'alive',
sort: '+createdAt', sort: '+createdAt',
} }, } },
popularUsersF: { endpoint: 'users', limit: 10, params: {
state: 'alive',
origin: 'remote',
sort: '+follower',
} },
recentlyUpdatedUsersF: { endpoint: 'users', limit: 10, params: {
origin: 'combined',
sort: '+updatedAt',
} },
recentlyRegisteredUsersF: { endpoint: 'users', limit: 10, params: {
origin: 'combined',
sort: '+createdAt',
} },
tagsLocal: [], tagsLocal: [],
tagsRemote: [], tagsRemote: [],
stats: null, stats: null,
meta: null, meta: null,
num: Vue.filter('number'), num: Vue.filter('number'),
faBookmark, faChartLine, faCommentAlt, faPlus, faHashtag faBookmark, faChartLine, faCommentAlt, faPlus, faHashtag, faRocket
}; };
}, },
@@ -91,21 +118,7 @@ export default Vue.extend({
limit: 30, limit: 30,
params: { params: {
tag: this.tag, tag: this.tag,
state: 'alive', origin: 'combined',
origin: 'local',
sort: '+follower',
}
};
},
tagRemoteUsers(): any {
return {
endpoint: 'hashtags/users',
limit: 30,
params: {
tag: this.tag,
state: 'alive',
origin: 'remote',
sort: '+follower', sort: '+follower',
} }
}; };
@@ -152,6 +165,28 @@ export default Vue.extend({
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.localfedi7
overflow hidden
background var(--face)
color #fff
text-shadow 0 0 8px #000
border-radius 6px
padding 16px
margin-top 16px
margin-bottom 16px
height 80px
background-position 50%
background-size cover
> header
font-size 20px
font-weight bold
> div
font-size 14px
opacity 0.8
.localfedi7:first-child
margin-top 0
.vxjfqztj .vxjfqztj
padding 16px padding 16px
@@ -160,35 +195,4 @@ export default Vue.extend({
&.local &.local
font-weight bold font-weight bold
.kpdsmpnk
min-height 100px
padding 16px
background-position center
background-size cover
&:before
content ""
display block
position absolute
top 0
left 0
width 100%
height 100%
background rgba(0, 0, 0, 0.3)
> div
color #fff
text-shadow 0 0 8px #00
> .title
display block
font-size 20px
font-weight bold
color inherit
> span
font-size 14px
opacity 0.8
</style> </style>

View File

@@ -37,7 +37,6 @@ import i18n from '../../../i18n';
import copyToClipboard from '../../../common/scripts/copy-to-clipboard'; import copyToClipboard from '../../../common/scripts/copy-to-clipboard';
import updateAvatar from '../../api/update-avatar'; import updateAvatar from '../../api/update-avatar';
import updateBanner from '../../api/update-banner'; import updateBanner from '../../api/update-banner';
import { appendQuery } from '../../../../../prelude/url';
import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue'; import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue';
export default Vue.extend({ export default Vue.extend({
@@ -87,7 +86,8 @@ export default Vue.extend({
action: this.copyUrl action: this.copyUrl
}, { }, {
type: 'link', type: 'link',
href: appendQuery(this.file.url, 'download'), href: this.file.url,
target: '_blank',
text: this.$t('contextmenu.download'), text: this.$t('contextmenu.download'),
icon: 'download', icon: 'download',
download: this.file.name download: this.file.name

View File

@@ -1,36 +1,28 @@
<template> <template>
<div class="mk-media-video-dialog"> <ui-modal v-hotkey.global="keymap">
<div class="bg" @click="close"></div> <video :src="video.url" :title="video.name" controls autoplay ref="video" @volumechange="volumechange" />
<video :src="video.url" :title="video.name" controls autoplay ref="video" @volumechange="volumechange"/> </ui-modal>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import anime from 'animejs';
export default Vue.extend({ export default Vue.extend({
props: ['video', 'start'], props: ['video', 'start'],
mounted() { mounted() {
anime({
targets: this.$el,
opacity: 1,
duration: 100,
easing: 'linear'
});
const videoTag = this.$refs.video as HTMLVideoElement; const videoTag = this.$refs.video as HTMLVideoElement;
if (this.start) videoTag.currentTime = this.start if (this.start) videoTag.currentTime = this.start
videoTag.volume = this.$store.state.device.mediaVolume; videoTag.volume = this.$store.state.device.mediaVolume;
}, },
computed: {
keymap(): any {
return {
'esc': this.close,
};
}
},
methods: { methods: {
close() { close() {
anime({
targets: this.$el,
opacity: 0,
duration: 100,
easing: 'linear',
complete: () => this.destroyDom()
});
}, },
volumechange() { volumechange() {
const videoTag = this.$refs.video as HTMLVideoElement; const videoTag = this.$refs.video as HTMLVideoElement;
@@ -41,35 +33,15 @@ export default Vue.extend({
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.mk-media-video-dialog video
display block
position fixed position fixed
z-index 2048 z-index 2
top 0 top 0
right 0
bottom 0
left 0 left 0
width 100% max-width 80vw
height 100% max-height 80vh
opacity 0 margin auto
> .bg
display block
position fixed
z-index 1
top 0
left 0
width 100%
height 100%
background rgba(#000, 0.7)
> video
position fixed
z-index 2
top 0
right 0
bottom 0
left 0
max-width 80vw
max-height 80vh
margin auto
</style> </style>

View File

@@ -53,10 +53,13 @@ export default Vue.extend({
start = videoTag.currentTime start = videoTag.currentTime
videoTag.pause() videoTag.pause()
} }
this.$root.new(MkMediaVideoDialog, { const viewer = this.$root.new(MkMediaVideoDialog, {
video: this.video, video: this.video,
start, start,
}) });
this.$once('hook:beforeDestroy', () => {
viewer.close();
});
} }
} }
}) })

View File

@@ -1,9 +1,9 @@
<template> <template>
<div class="kedshtep" :class="{ naked, inNakedDeckColumn, shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }"> <div class="kedshtep" :class="{ naked, inNakedDeckColumn, shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
<header v-if="showHeader"> <header v-if="showHeader" :class="{ bodyTogglable }" @click="toggleContent(!showBody)">
<div class="title"><slot name="header"></slot></div> <div class="title"><slot name="header"></slot></div>
<slot name="func"></slot> <slot name="func"></slot>
<button v-if="bodyTogglable" @click="toggleContent(!showBody)"> <button v-if="bodyTogglable">
<template v-if="showBody"><fa icon="angle-up"/></template> <template v-if="showBody"><fa icon="angle-up"/></template>
<template v-else><fa icon="angle-down"/></template> <template v-else><fa icon="angle-down"/></template>
</button> </button>
@@ -47,6 +47,7 @@ export default Vue.extend({
}, },
methods: { methods: {
toggleContent(show: boolean) { toggleContent(show: boolean) {
if (!this.bodyTogglable) return;
this.showBody = show; this.showBody = show;
this.$emit('toggle', show); this.$emit('toggle', show);
} }
@@ -77,6 +78,9 @@ export default Vue.extend({
> header > header
background var(--faceHeader) background var(--faceHeader)
&.bodyTogglable
cursor pointer
> .title > .title
z-index 1 z-index 1
margin 0 margin 0
@@ -120,6 +124,9 @@ export default Vue.extend({
color var(--text) color var(--text)
background var(--deckColumnBg) background var(--deckColumnBg)
&.bodyTogglable
cursor pointer
> button > button
position absolute position absolute
top 0 top 0

View File

@@ -25,7 +25,7 @@
<p v-else class="empty">{{ $t('no-description') }}</p> <p v-else class="empty">{{ $t('no-description') }}</p>
<x-integrations :user="user" style="margin-top:16px;"/> <x-integrations :user="user" style="margin-top:16px;"/>
</div> </div>
<div class="fields" v-if="user.fields"> <div class="fields" v-if="user.fields" :key="user.id">
<dl class="field" v-for="(field, i) in user.fields" :key="i"> <dl class="field" v-for="(field, i) in user.fields" :key="i">
<dt class="name"> <dt class="name">
<mfm :text="field.name" :plain="true" :custom-emojis="user.emojis"/> <mfm :text="field.name" :plain="true" :custom-emojis="user.emojis"/>
@@ -106,10 +106,13 @@ export default Vue.extend({
}, },
menu() { menu() {
this.$root.new(XUserMenu, { const w = this.$root.new(XUserMenu, {
source: this.$refs.menu, source: this.$refs.menu,
user: this.user user: this.user
}); });
this.$once('hook:beforeDestroy', () => {
w.destroyDom();
});
} }
} }
}); });

View File

@@ -57,7 +57,6 @@
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import { gcd } from '../../../../../prelude/math'; import { gcd } from '../../../../../prelude/math';
import { appendQuery } from '../../../../../prelude/url';
import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue'; import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue';
export default Vue.extend({ export default Vue.extend({
@@ -91,7 +90,7 @@ export default Vue.extend({
}, },
dlUrl(): string { dlUrl(): string {
return appendQuery(this.file.url, 'download'); return this.file.url;
} }
}, },

View File

@@ -1,6 +1,9 @@
<template> <template>
<div class="ulveipglmagnxfgvitaxyszerjwiqmwl"> <ui-modal
<div class="bg" ref="bg"></div> ref="modal"
:close-on-bg-click="false"
:close-anime-duration="300"
@before-close="onBeforeClose">
<div class="main" ref="main"> <div class="main" ref="main">
<x-post-form ref="form" <x-post-form ref="form"
:reply="reply" :reply="reply"
@@ -12,7 +15,7 @@
@posted="onPosted" @posted="onPosted"
@cancel="onCanceled"/> @cancel="onCanceled"/>
</div> </div>
</div> </ui-modal>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -55,14 +58,6 @@ export default Vue.extend({
mounted() { mounted() {
this.$nextTick(() => { this.$nextTick(() => {
(this.$refs.bg as any).style.pointerEvents = 'auto';
anime({
targets: this.$refs.bg,
opacity: 1,
duration: 100,
easing: 'linear'
});
anime({ anime({
targets: this.$refs.main, targets: this.$refs.main,
opacity: 1, opacity: 1,
@@ -78,26 +73,22 @@ export default Vue.extend({
this.$refs.form.focus(); this.$refs.form.focus();
}, },
close() { onBeforeClose() {
(this.$refs.bg as any).style.pointerEvents = 'none';
anime({
targets: this.$refs.bg,
opacity: 0,
duration: 300,
easing: 'linear'
});
(this.$refs.main as any).style.pointerEvents = 'none'; (this.$refs.main as any).style.pointerEvents = 'none';
anime({ anime({
targets: this.$refs.main, targets: this.$refs.main,
opacity: 0, opacity: 0,
translateY: 16, translateY: 16,
duration: 300, duration: 300,
easing: 'easeOutQuad', easing: 'easeOutQuad'
complete: () => this.destroyDom()
}); });
}, },
close() {
(this.$refs.modal as any).close();
},
onPosted() { onPosted() {
this.$emit('posted'); this.$emit('posted');
this.close(); this.close();
@@ -112,30 +103,18 @@ export default Vue.extend({
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.ulveipglmagnxfgvitaxyszerjwiqmwl
> .bg
display block
position fixed
z-index 10000
top 0
left 0
width 100%
height 100%
background rgba(#000, 0.7)
opacity 0
pointer-events none
> .main .main
display block display block
position fixed position fixed
z-index 10000 z-index 10000
top 0 top 0
left 0 left 0
right 0 right 0
height 100% height 100%
overflow auto overflow auto
margin 0 auto 0 auto margin 0 auto 0 auto
opacity 0 opacity 0
transform translateY(-16px) transform translateY(-16px)
</style> </style>

View File

@@ -1,9 +1,9 @@
<template> <template>
<div class="ukygtjoj" :class="{ naked, inNakedDeckColumn, hideHeader: !showHeader, shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }"> <div class="ukygtjoj" :class="{ naked, inNakedDeckColumn, hideHeader: !showHeader, shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
<header v-if="showHeader"> <header v-if="showHeader" @click="() => showBody = !showBody">
<div class="title"><slot name="header"></slot></div> <div class="title"><slot name="header"></slot></div>
<slot name="func"></slot> <slot name="func"></slot>
<button v-if="bodyTogglable" @click="() => showBody = !showBody"> <button v-if="bodyTogglable">
<template v-if="showBody"><fa icon="angle-up"/></template> <template v-if="showBody"><fa icon="angle-up"/></template>
<template v-else><fa icon="angle-down"/></template> <template v-else><fa icon="angle-down"/></template>
</button> </button>
@@ -47,6 +47,7 @@ export default Vue.extend({
}, },
methods: { methods: {
toggleContent(show: boolean) { toggleContent(show: boolean) {
if (!this.bodyTogglable) return;
this.showBody = show; this.showBody = show;
} }
} }

View File

@@ -25,7 +25,7 @@
<mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :key="user.id"/> <mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :key="user.id"/>
<x-integrations :user="user" style="margin:20px 0;"/> <x-integrations :user="user" style="margin:20px 0;"/>
</div> </div>
<div class="fields" v-if="user.fields"> <div class="fields" v-if="user.fields" :key="user.id">
<dl class="field" v-for="(field, i) in user.fields" :key="i"> <dl class="field" v-for="(field, i) in user.fields" :key="i">
<dt class="name"> <dt class="name">
<mfm :text="field.name" :plain="true" :custom-emojis="user.emojis"/> <mfm :text="field.name" :plain="true" :custom-emojis="user.emojis"/>

View File

@@ -23,6 +23,7 @@ button
border none border none
cursor pointer cursor pointer
color inherit color inherit
touch-action manipulation
* *
pointer-events none pointer-events none

View File

@@ -41,6 +41,7 @@ const defaultSettings = {
uploadFolder: null, uploadFolder: null,
pastedFileName: 'yyyy-MM-dd HH-mm-ss [{{number}}]', pastedFileName: 'yyyy-MM-dd HH-mm-ss [{{number}}]',
pasteDialog: false, pasteDialog: false,
reactions: ['like', 'love', 'laugh', 'hmm', 'surprise', 'congrats', 'angry', 'confused', 'rip', 'pudding']
}; };
const defaultDeviceSettings = { const defaultDeviceSettings = {
@@ -78,6 +79,8 @@ const defaultDeviceSettings = {
enableMobileQuickNotificationView: false, enableMobileQuickNotificationView: false,
roomGraphicsQuality: 'medium', roomGraphicsQuality: 'medium',
roomUseOrthographicCamera: true, roomUseOrthographicCamera: true,
activeEmojiCategoryName: undefined,
recentEmojis: [],
}; };
export default (os: MiOS) => new Vuex.Store({ export default (os: MiOS) => new Vuex.Store({

BIN
src/client/assets/fedi.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -29,6 +29,7 @@ export type Source = {
port: number; port: number;
pass: string; pass: string;
index?: string; index?: string;
ssl?: boolean;
}; };
autoAdmin?: boolean; autoAdmin?: boolean;

View File

@@ -32,7 +32,7 @@ const index = {
// Init ElasticSearch connection // Init ElasticSearch connection
const client = config.elasticsearch ? new elasticsearch.Client({ const client = config.elasticsearch ? new elasticsearch.Client({
node: `http://${config.elasticsearch.host}:${config.elasticsearch.port}`, node: `${config.elasticsearch.ssl ? 'https://' : 'http://'}${config.elasticsearch.host}:${config.elasticsearch.port}`,
pingTimeout: 30000 pingTimeout: 30000
}) : null; }) : null;

View File

@@ -27,7 +27,7 @@ Misskeyのリバーシ機能に対応したBotの開発方法をここに記し
* `pos`として位置情報が含まれている * `pos`として位置情報が含まれている
## 位置の計算法 ## 位置の計算法
8x8のマップを考える場合、各マスの位置(Posと呼びます)は次のようになっています: 8x8のマップを考える場合、各マスの位置(インデックスと呼びます)は次のようになっています:
``` ```
+--+--+--+--+--+--+--+--+ +--+--+--+--+--+--+--+--+
| 0| 1| 2| 3| 4| 5| 6| 7| | 0| 1| 2| 3| 4| 5| 6| 7|
@@ -38,7 +38,7 @@ Misskeyのリバーシ機能に対応したBotの開発方法をここに記し
... ...
``` ```
### X,Y座標 から Pos に変換する ### X,Y座標 から インデックス に変換する
``` ```
pos = x + (y * mapWidth) pos = x + (y * mapWidth)
``` ```
@@ -47,7 +47,7 @@ pos = x + (y * mapWidth)
mapWidth = map[0].length mapWidth = map[0].length
``` ```
### Pos から X,Y座標 に変換する ### インデックス から X,Y座標 に変換する
``` ```
x = pos % mapWidth x = pos % mapWidth
y = Math.floor(pos / mapWidth) y = Math.floor(pos / mapWidth)

View File

@@ -135,8 +135,8 @@ export function toHtml(tokens: MfmForest | null, mentionedRemoteUsers: IMentione
break; break;
default: default:
const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host); const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
a.href = remoteUserInfo ? remoteUserInfo.uri : `${config.url}/${acct}`; a.href = remoteUserInfo ? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri) : `${config.url}/${acct}`;
a.className = 'mention'; a.className = 'u-url mention';
break; break;
} }
a.textContent = acct; a.textContent = acct;

8
src/misc/hard-limits.ts Normal file
View File

@@ -0,0 +1,8 @@
// If you change DB_* values, you must also change the DB schema.
/**
* Maximum note text length that can be stored in DB.
* Surrogate pairs count as one
*/
export const DB_MAX_NOTE_TEXT_LENGTH = 8192;

View File

@@ -24,6 +24,11 @@ export class Emoji {
}) })
public host: string | null; public host: string | null;
@Column('varchar', {
length: 128, nullable: true
})
public category: string | null;
@Column('varchar', { @Column('varchar', {
length: 512, length: 512,
}) })

View File

@@ -228,6 +228,7 @@ export class Note {
export type IMentionedRemoteUsers = { export type IMentionedRemoteUsers = {
uri: string; uri: string;
url?: string;
username: string; username: string;
host: string; host: string;
}[]; }[];

View File

@@ -84,6 +84,19 @@ export function groupOn<T, S>(f: (x: T) => S, xs: T[]): T[][] {
return groupBy((a, b) => f(a) === f(b), xs); return groupBy((a, b) => f(a) === f(b), xs);
} }
export function groupByX<T>(collections: T[], keySelector: (x: T) => string) {
return collections.reduce((obj: Record<string, T[]>, item: T) => {
const key = keySelector(item);
if (!obj.hasOwnProperty(key)) {
obj[key] = [];
}
obj[key].push(item);
return obj;
}, {});
}
/** /**
* Compare two arrays by lexicographical order * Compare two arrays by lexicographical order
*/ */

View File

@@ -23,6 +23,7 @@ import { genId } from '../../../misc/gen-id';
import { fetchMeta } from '../../../misc/fetch-meta'; import { fetchMeta } from '../../../misc/fetch-meta';
import { ensure } from '../../../prelude/ensure'; import { ensure } from '../../../prelude/ensure';
import { getApLock } from '../../../misc/app-lock'; import { getApLock } from '../../../misc/app-lock';
import { createMessage } from '../../../services/messages/create';
const logger = apLogger; const logger = apLogger;
@@ -223,6 +224,13 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
if (actor.uri) updatePerson(actor.uri); if (actor.uri) updatePerson(actor.uri);
} }
if (note._misskey_talk && visibility === 'specified') {
for (const recipient of visibleUsers) {
await createMessage(actor, recipient, undefined, text || undefined, (files && files.length > 0) ? files[0] : null);
return null;
}
}
return await post(actor, { return await post(actor, {
createdAt: note.published ? new Date(note.published) : null, createdAt: note.published ? new Date(note.published) : null,
files, files,

View File

@@ -3,7 +3,7 @@ import * as promiseLimit from 'promise-limit';
import config from '../../../config'; import config from '../../../config';
import Resolver from '../resolver'; import Resolver from '../resolver';
import { resolveImage } from './image'; import { resolveImage } from './image';
import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type'; import { isCollectionOrOrderedCollection, isCollection, IPerson, getApId } from '../type';
import { DriveFile } from '../../../models/entities/drive-file'; import { DriveFile } from '../../../models/entities/drive-file';
import { fromHtml } from '../../../mfm/fromHtml'; import { fromHtml } from '../../../mfm/fromHtml';
import { resolveNote, extractEmojis } from './note'; import { resolveNote, extractEmojis } from './note';
@@ -150,13 +150,13 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
createdAt: new Date(), createdAt: new Date(),
lastFetchedAt: new Date(), lastFetchedAt: new Date(),
name: person.name, name: person.name,
isLocked: person.manuallyApprovesFollowers, isLocked: !!person.manuallyApprovesFollowers,
username: person.preferredUsername, username: person.preferredUsername,
usernameLower: person.preferredUsername.toLowerCase(), usernameLower: person.preferredUsername!.toLowerCase(),
host, host,
inbox: person.inbox, inbox: person.inbox,
sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined), sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
featured: person.featured, featured: person.featured ? getApId(person.featured) : undefined,
uri: person.id, uri: person.id,
tags, tags,
isBot, isBot,
@@ -319,7 +319,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
tags, tags,
isBot: object.type == 'Service', isBot: object.type == 'Service',
isCat: (person as any).isCat === true, isCat: (person as any).isCat === true,
isLocked: person.manuallyApprovesFollowers, isLocked: !!person.manuallyApprovesFollowers,
} as Partial<User>; } as Partial<User>;
if (avatar) { if (avatar) {

View File

@@ -12,10 +12,12 @@ import { Emoji } from '../../../models/entities/emoji';
import { Poll } from '../../../models/entities/poll'; import { Poll } from '../../../models/entities/poll';
import { ensure } from '../../../prelude/ensure'; import { ensure } from '../../../prelude/ensure';
export default async function renderNote(note: Note, dive = true): Promise<any> { export default async function renderNote(note: Note, dive = true, isTalk = false): Promise<any> {
const promisedFiles: Promise<DriveFile[]> = note.fileIds.length > 0 const getPromisedFiles = async (ids: string[]) => {
? DriveFiles.find({ id: In(note.fileIds) }) if (!ids || ids.length === 0) return [];
: Promise.resolve([]); const items = await DriveFiles.find({ id: In(ids) });
return ids.map(id => items.find(item => item.id === id)).filter(item => item != null) as DriveFile[];
};
let inReplyTo; let inReplyTo;
let inReplyToNote: Note | undefined; let inReplyToNote: Note | undefined;
@@ -81,7 +83,7 @@ export default async function renderNote(note: Note, dive = true): Promise<any>
const hashtagTags = (note.tags || []).map(tag => renderHashtag(tag)); const hashtagTags = (note.tags || []).map(tag => renderHashtag(tag));
const mentionTags = mentionedUsers.map(u => renderMention(u)); const mentionTags = mentionedUsers.map(u => renderMention(u));
const files = await promisedFiles; const files = await getPromisedFiles(note.fileIds);
let text = note.text; let text = note.text;
let poll: Poll | undefined; let poll: Poll | undefined;
@@ -145,6 +147,10 @@ export default async function renderNote(note: Note, dive = true): Promise<any>
})) }))
} : {}; } : {};
const asTalk = isTalk ? {
_misskey_talk: true
} : {};
return { return {
id: `${config.url}/notes/${note.id}`, id: `${config.url}/notes/${note.id}`,
type: 'Note', type: 'Note',
@@ -160,7 +166,8 @@ export default async function renderNote(note: Note, dive = true): Promise<any>
attachment: files.map(renderDocument), attachment: files.map(renderDocument),
sensitive: note.cw != null || files.some(file => file.isSensitive), sensitive: note.cw != null || files.some(file => file.isSensitive),
tag, tag,
...asPoll ...asPoll,
...asTalk
}; };
} }

View File

@@ -75,6 +75,7 @@ export interface INote extends IObject {
type: 'Note' | 'Question' | 'Article' | 'Audio' | 'Document' | 'Image' | 'Page' | 'Video'; type: 'Note' | 'Question' | 'Article' | 'Audio' | 'Document' | 'Image' | 'Page' | 'Video';
_misskey_content?: string; _misskey_content?: string;
_misskey_quote?: string; _misskey_quote?: string;
_misskey_talk: boolean;
} }
export interface IQuestion extends IObject { export interface IQuestion extends IObject {
@@ -100,17 +101,22 @@ export const validActor = ['Person', 'Service'];
export interface IPerson extends IObject { export interface IPerson extends IObject {
type: 'Person'; type: 'Person';
name: string; name?: string;
preferredUsername: string; preferredUsername?: string;
manuallyApprovesFollowers: boolean; manuallyApprovesFollowers?: boolean;
inbox: string; inbox?: string;
sharedInbox?: string; sharedInbox?: string; // 後方互換性のため
publicKey: any; publicKey: {
followers: any; id: string;
following: any; publicKeyPem: string;
featured?: any; };
outbox: any; followers?: string | ICollection | IOrderedCollection;
endpoints: any; following?: string | ICollection | IOrderedCollection;
featured?: string | IOrderedCollection;
outbox?: string | IOrderedCollection;
endpoints?: {
sharedInbox?: string;
};
} }
export const isCollection = (object: IObject): object is ICollection => export const isCollection = (object: IObject): object is ICollection =>

View File

@@ -40,18 +40,21 @@ function inbox(ctx: Router.RouterContext) {
ctx.status = 202; ctx.status = 202;
} }
const ACTIVITY_JSON = 'application/activity+json; charset=utf-8';
const LD_JSON = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8';
function isActivityPubReq(ctx: Router.RouterContext) { function isActivityPubReq(ctx: Router.RouterContext) {
ctx.response.vary('Accept'); ctx.response.vary('Accept');
const accepted = ctx.accepts('html', 'application/activity+json', 'application/ld+json'); const accepted = ctx.accepts('html', ACTIVITY_JSON, LD_JSON);
return ['application/activity+json', 'application/ld+json'].includes(accepted as string); return typeof accepted === 'string' && !accepted.match(/html/);
} }
export function setResponseType(ctx: Router.RouterContext) { export function setResponseType(ctx: Router.RouterContext) {
const accpet = ctx.accepts('application/activity+json', 'application/ld+json'); const accept = ctx.accepts(ACTIVITY_JSON, LD_JSON);
if (accpet === 'application/ld+json') { if (accept === LD_JSON) {
ctx.response.type = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8'; ctx.response.type = LD_JSON;
} else { } else {
ctx.response.type = 'application/activity+json; charset=utf-8'; ctx.response.type = ACTIVITY_JSON;
} }
} }

View File

@@ -2,11 +2,15 @@ import { User } from '../../../models/entities/user';
import { Mutings } from '../../../models'; import { Mutings } from '../../../models';
import { SelectQueryBuilder, Brackets } from 'typeorm'; import { SelectQueryBuilder, Brackets } from 'typeorm';
export function generateMuteQuery(q: SelectQueryBuilder<any>, me: User) { export function generateMuteQuery(q: SelectQueryBuilder<any>, me: User, exclude?: User) {
const mutingQuery = Mutings.createQueryBuilder('muting') const mutingQuery = Mutings.createQueryBuilder('muting')
.select('muting.muteeId') .select('muting.muteeId')
.where('muting.muterId = :muterId', { muterId: me.id }); .where('muting.muterId = :muterId', { muterId: me.id });
if (exclude) {
mutingQuery.andWhere('muting.muteeId != :excludeId', { excludeId: exclude.id });
}
// 投稿の作者をミュートしていない かつ // 投稿の作者をミュートしていない かつ
// 投稿の返信先の作者をミュートしていない かつ // 投稿の返信先の作者をミュートしていない かつ
// 投稿の引用元の作者をミュートしていない // 投稿の引用元の作者をミュートしていない

View File

@@ -5,6 +5,7 @@ import { Emojis } from '../../../../../models';
import { genId } from '../../../../../misc/gen-id'; import { genId } from '../../../../../misc/gen-id';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
import { insertModerationLog } from '../../../../../services/insert-moderation-log'; import { insertModerationLog } from '../../../../../services/insert-moderation-log';
import { ApiError } from '../../../error';
export const meta = { export const meta = {
desc: { desc: {
@@ -25,20 +26,40 @@ export const meta = {
validator: $.str.min(1) validator: $.str.min(1)
}, },
category: {
validator: $.optional.str
},
aliases: { aliases: {
validator: $.optional.arr($.str.min(1)), validator: $.optional.arr($.str.min(1)),
default: [] as string[] default: [] as string[]
} }
},
errors: {
emojiAlredyExists: {
message: 'Emoji already exists.',
code: 'EMOJI_ALREADY_EXISTS',
id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf'
}
} }
}; };
export default define(meta, async (ps, me) => { export default define(meta, async (ps, me) => {
const type = await detectUrlMine(ps.url); const type = await detectUrlMine(ps.url);
const exists = await Emojis.findOne({
name: ps.name,
host: null
});
if (exists != null) throw new ApiError(meta.errors.emojiAlredyExists);
const emoji = await Emojis.save({ const emoji = await Emojis.save({
id: genId(), id: genId(),
updatedAt: new Date(), updatedAt: new Date(),
name: ps.name, name: ps.name,
category: ps.category,
host: null, host: null,
aliases: ps.aliases, aliases: ps.aliases,
url: ps.url, url: ps.url,

View File

@@ -23,12 +23,19 @@ export const meta = {
export default define(meta, async (ps) => { export default define(meta, async (ps) => {
const emojis = await Emojis.find({ const emojis = await Emojis.find({
host: toPunyNullable(ps.host) where: {
host: toPunyNullable(ps.host)
},
order: {
category: 'ASC',
name: 'ASC'
}
}); });
return emojis.map(e => ({ return emojis.map(e => ({
id: e.id, id: e.id,
name: e.name, name: e.name,
category: e.category,
aliases: e.aliases, aliases: e.aliases,
host: e.host, host: e.host,
url: e.url url: e.url

View File

@@ -4,6 +4,7 @@ import { ID } from '../../../../../misc/cafy-id';
import { Emojis } from '../../../../../models'; import { Emojis } from '../../../../../models';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
import { insertModerationLog } from '../../../../../services/insert-moderation-log'; import { insertModerationLog } from '../../../../../services/insert-moderation-log';
import { ApiError } from '../../../error';
export const meta = { export const meta = {
desc: { desc: {
@@ -19,13 +20,21 @@ export const meta = {
id: { id: {
validator: $.type(ID) validator: $.type(ID)
} }
},
errors: {
noSuchEmoji: {
message: 'No such emoji.',
code: 'NO_SUCH_EMOJI',
id: 'be83669b-773a-44b7-b1f8-e5e5170ac3c2'
}
} }
}; };
export default define(meta, async (ps, me) => { export default define(meta, async (ps, me) => {
const emoji = await Emojis.findOne(ps.id); const emoji = await Emojis.findOne(ps.id);
if (emoji == null) throw new Error('emoji not found'); if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji);
await Emojis.delete(emoji.id); await Emojis.delete(emoji.id);

View File

@@ -4,6 +4,7 @@ import { detectUrlMine } from '../../../../../misc/detect-url-mine';
import { ID } from '../../../../../misc/cafy-id'; import { ID } from '../../../../../misc/cafy-id';
import { Emojis } from '../../../../../models'; import { Emojis } from '../../../../../models';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
import { ApiError } from '../../../error';
export const meta = { export const meta = {
desc: { desc: {
@@ -24,6 +25,10 @@ export const meta = {
validator: $.str validator: $.str
}, },
category: {
validator: $.optional.str
},
url: { url: {
validator: $.str validator: $.str
}, },
@@ -31,19 +36,28 @@ export const meta = {
aliases: { aliases: {
validator: $.arr($.str) validator: $.arr($.str)
} }
},
errors: {
noSuchEmoji: {
message: 'No such emoji.',
code: 'NO_SUCH_EMOJI',
id: '684dec9d-a8c2-4364-9aa8-456c49cb1dc8'
}
} }
}; };
export default define(meta, async (ps) => { export default define(meta, async (ps) => {
const emoji = await Emojis.findOne(ps.id); const emoji = await Emojis.findOne(ps.id);
if (emoji == null) throw new Error('emoji not found'); if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji);
const type = await detectUrlMine(ps.url); const type = await detectUrlMine(ps.url);
await Emojis.update(emoji.id, { await Emojis.update(emoji.id, {
updatedAt: new Date(), updatedAt: new Date(),
name: ps.name, name: ps.name,
category: ps.category,
aliases: ps.aliases, aliases: ps.aliases,
url: ps.url, url: ps.url,
type, type,

View File

@@ -16,16 +16,19 @@ export const meta = {
params: {} params: {}
}; };
export default define(meta, async (ps) => { export default define(meta, async () => {
const code = rndstr({ length: 5, chars: '0-9' }); const code = rndstr({
length: 8,
chars: '2-9A-HJ-NP-Z', // [0-9A-Z] w/o [01IO] (32 patterns)
});
await RegistrationTickets.save({ await RegistrationTickets.save({
id: genId(), id: genId(),
createdAt: new Date(), createdAt: new Date(),
code: code code,
}); });
return { return {
code: code code,
}; };
}); });

View File

@@ -3,6 +3,7 @@ import define from '../../define';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
import { Meta } from '../../../../models/entities/meta'; import { Meta } from '../../../../models/entities/meta';
import { insertModerationLog } from '../../../../services/insert-moderation-log'; import { insertModerationLog } from '../../../../services/insert-moderation-log';
import { DB_MAX_NOTE_TEXT_LENGTH } from '../../../../misc/hard-limits';
export const meta = { export const meta = {
desc: { desc: {
@@ -121,7 +122,7 @@ export const meta = {
}, },
maxNoteTextLength: { maxNoteTextLength: {
validator: $.optional.num.min(0), validator: $.optional.num.min(0).max(DB_MAX_NOTE_TEXT_LENGTH),
desc: { desc: {
'ja-JP': '投稿の最大文字数' 'ja-JP': '投稿の最大文字数'
} }

View File

@@ -1,17 +1,12 @@
import $ from 'cafy'; import $ from 'cafy';
import { ID } from '../../../../../misc/cafy-id'; import { ID } from '../../../../../misc/cafy-id';
import { publishMainStream, publishGroupMessagingStream } from '../../../../../services/stream';
import { publishMessagingStream, publishMessagingIndexStream } from '../../../../../services/stream';
import pushSw from '../../../../../services/push-notification';
import define from '../../../define'; import define from '../../../define';
import { ApiError } from '../../../error'; import { ApiError } from '../../../error';
import { getUser } from '../../../common/getters'; import { getUser } from '../../../common/getters';
import { MessagingMessages, DriveFiles, Mutings, UserGroups, UserGroupJoinings } from '../../../../../models'; import { MessagingMessages, DriveFiles, UserGroups, UserGroupJoinings } from '../../../../../models';
import { MessagingMessage } from '../../../../../models/entities/messaging-message';
import { genId } from '../../../../../misc/gen-id';
import { User } from '../../../../../models/entities/user'; import { User } from '../../../../../models/entities/user';
import { UserGroup } from '../../../../../models/entities/user-group'; import { UserGroup } from '../../../../../models/entities/user-group';
import { Not } from 'typeorm'; import { createMessage } from '../../../../../services/messages/create';
export const meta = { export const meta = {
desc: { desc: {
@@ -147,68 +142,5 @@ export default define(meta, async (ps, user) => {
throw new ApiError(meta.errors.contentRequired); throw new ApiError(meta.errors.contentRequired);
} }
const message = await MessagingMessages.save({ return await createMessage(user, recipientUser, recipientGroup, ps.text, file);
id: genId(),
createdAt: new Date(),
fileId: file ? file.id : null,
recipientId: recipientUser ? recipientUser.id : null,
groupId: recipientGroup ? recipientGroup.id : null,
text: ps.text ? ps.text.trim() : null,
userId: user.id,
isRead: false,
reads: [] as any[]
} as MessagingMessage);
const messageObj = await MessagingMessages.pack(message);
if (recipientUser) {
// 自分のストリーム
publishMessagingStream(message.userId, recipientUser.id, 'message', messageObj);
publishMessagingIndexStream(message.userId, 'message', messageObj);
publishMainStream(message.userId, 'messagingMessage', messageObj);
// 相手のストリーム
publishMessagingStream(recipientUser.id, message.userId, 'message', messageObj);
publishMessagingIndexStream(recipientUser.id, 'message', messageObj);
publishMainStream(recipientUser.id, 'messagingMessage', messageObj);
} else if (recipientGroup) {
// グループのストリーム
publishGroupMessagingStream(recipientGroup.id, 'message', messageObj);
// メンバーのストリーム
const joinings = await UserGroupJoinings.find({ userGroupId: recipientGroup.id });
for (const joining of joinings) {
publishMessagingIndexStream(joining.userId, 'message', messageObj);
publishMainStream(joining.userId, 'messagingMessage', messageObj);
}
}
// 2秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する
setTimeout(async () => {
const freshMessage = await MessagingMessages.findOne(message.id);
if (freshMessage == null) return; // メッセージが削除されている場合もある
if (recipientUser) {
if (freshMessage.isRead) return; // 既読
//#region ただしミュートされているなら発行しない
const mute = await Mutings.find({
muterId: recipientUser.id,
});
if (mute.map(m => m.muteeId).includes(user.id)) return;
//#endregion
publishMainStream(recipientUser.id, 'unreadMessagingMessage', messageObj);
pushSw(recipientUser.id, 'unreadMessagingMessage', messageObj);
} else if (recipientGroup) {
const joinings = await UserGroupJoinings.find({ userGroupId: recipientGroup.id, userId: Not(user.id) });
for (const joining of joinings) {
if (freshMessage.reads.includes(joining.userId)) return; // 既読
publishMainStream(joining.userId, 'unreadMessagingMessage', messageObj);
pushSw(joining.userId, 'unreadMessagingMessage', messageObj);
}
}
}, 2000);
return messageObj;
}); });

View File

@@ -7,6 +7,7 @@ import * as pkg from '../../../../package.json';
import { Emojis } from '../../../models'; import { Emojis } from '../../../models';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
import redis from '../../../db/redis'; import redis from '../../../db/redis';
import { DB_MAX_NOTE_TEXT_LENGTH } from '../../../misc/hard-limits';
export const meta = { export const meta = {
stability: 'stable', stability: 'stable',
@@ -95,7 +96,19 @@ export const meta = {
export default define(meta, async (ps, me) => { export default define(meta, async (ps, me) => {
const instance = await fetchMeta(true); const instance = await fetchMeta(true);
const emojis = await Emojis.find({ where: { host: null }, cache: { id: 'meta_emojis', milliseconds: 3600000 } }); // 1 hour const emojis = await Emojis.find({
where: {
host: null
},
order: {
category: 'ASC',
name: 'ASC'
},
cache: {
id: 'meta_emojis',
milliseconds: 3600000 // 1 hour
}
});
const response: any = { const response: any = {
maintainerName: instance.maintainerName, maintainerName: instance.maintainerName,
@@ -138,11 +151,12 @@ export default define(meta, async (ps, me) => {
bannerUrl: instance.bannerUrl, bannerUrl: instance.bannerUrl,
errorImageUrl: instance.errorImageUrl, errorImageUrl: instance.errorImageUrl,
iconUrl: instance.iconUrl, iconUrl: instance.iconUrl,
maxNoteTextLength: instance.maxNoteTextLength, maxNoteTextLength: Math.min(instance.maxNoteTextLength, DB_MAX_NOTE_TEXT_LENGTH),
emojis: emojis.map(e => ({ emojis: emojis.map(e => ({
id: e.id, id: e.id,
aliases: e.aliases, aliases: e.aliases,
name: e.name, name: e.name,
category: e.category,
url: e.url, url: e.url,
})), })),
enableEmail: instance.enableEmail, enableEmail: instance.enableEmail,

View File

@@ -10,8 +10,9 @@ import { User } from '../../../../models/entities/user';
import { Users, DriveFiles, Notes } from '../../../../models'; import { Users, DriveFiles, Notes } from '../../../../models';
import { DriveFile } from '../../../../models/entities/drive-file'; import { DriveFile } from '../../../../models/entities/drive-file';
import { Note } from '../../../../models/entities/note'; import { Note } from '../../../../models/entities/note';
import { DB_MAX_NOTE_TEXT_LENGTH } from '../../../../misc/hard-limits';
let maxNoteTextLength = 1000; let maxNoteTextLength = 500;
setInterval(() => { setInterval(() => {
fetchMeta().then(m => { fetchMeta().then(m => {
@@ -55,7 +56,9 @@ export const meta = {
text: { text: {
validator: $.optional.nullable.str.pipe(text => validator: $.optional.nullable.str.pipe(text =>
length(text.trim()) <= maxNoteTextLength && text.trim() != '' text.trim() != ''
&& length(text.trim()) <= maxNoteTextLength
&& Array.from(text.trim()).length <= DB_MAX_NOTE_TEXT_LENGTH // DB limit
), ),
default: null as any, default: null as any,
desc: { desc: {

View File

@@ -134,7 +134,7 @@ export default define(meta, async (ps, me) => {
.leftJoinAndSelect('note.user', 'user'); .leftJoinAndSelect('note.user', 'user');
if (me) generateVisibilityQuery(query, me); if (me) generateVisibilityQuery(query, me);
if (me) generateMuteQuery(query, me); if (me) generateMuteQuery(query, me, user);
if (ps.withFiles) { if (ps.withFiles) {
query.andWhere('note.fileIds != \'{}\''); query.andWhere('note.fileIds != \'{}\'');

View File

@@ -46,9 +46,7 @@ export default async function(ctx: Koa.BaseContext) {
ctx.set('Content-Disposition', contentDisposition('inline', `${rename(file.name, { suffix: '-web' })}`)); ctx.set('Content-Disposition', contentDisposition('inline', `${rename(file.name, { suffix: '-web' })}`));
ctx.body = InternalStorage.read(key); ctx.body = InternalStorage.read(key);
} else { } else {
if ('download' in ctx.query) { ctx.set('Content-Disposition', contentDisposition('inline', `${file.name}`));
ctx.set('Content-Disposition', contentDisposition('attachment', `${file.name}`));
}
const readable = InternalStorage.read(file.accessKey!); const readable = InternalStorage.read(file.accessKey!);
readable.on('error', commonReadableHandlerGenerator(ctx)); readable.on('error', commonReadableHandlerGenerator(ctx));

View File

@@ -42,4 +42,17 @@ export default class TestChart extends Chart<TestLog> {
foo: update foo: update
}); });
} }
@autobind
public async decrement() {
const update: Obj = {};
update.total = -1;
update.dec = 1;
this.total--;
await this.inc({
foo: update
});
}
} }

View File

@@ -122,7 +122,7 @@ export default abstract class Chart<T extends Record<string, any>> {
for (const [k, v] of Object.entries(columns)) { for (const [k, v] of Object.entries(columns)) {
if (v > 0) query[k] = () => `"${k}" + ${v}`; if (v > 0) query[k] = () => `"${k}" + ${v}`;
if (v < 0) query[k] = () => `"${k}" - ${v}`; if (v < 0) query[k] = () => `"${k}" - ${Math.abs(v)}`;
} }
return query; return query;

View File

@@ -0,0 +1,105 @@
import { User } from '../../models/entities/user';
import { UserGroup } from '../../models/entities/user-group';
import { DriveFile } from '../../models/entities/drive-file';
import { MessagingMessages, UserGroupJoinings, Mutings, Users } from '../../models';
import { genId } from '../../misc/gen-id';
import { MessagingMessage } from '../../models/entities/messaging-message';
import { publishMessagingStream, publishMessagingIndexStream, publishMainStream, publishGroupMessagingStream } from '../stream';
import pushNotification from '../push-notification';
import { Not } from 'typeorm';
import { Note } from '../../models/entities/note';
import renderNote from '../../remote/activitypub/renderer/note';
import renderCreate from '../../remote/activitypub/renderer/create';
import { renderActivity } from '../../remote/activitypub/renderer';
import { deliver } from '../../queue';
export async function createMessage(user: User, recipientUser: User | undefined, recipientGroup: UserGroup | undefined, text: string | undefined, file: DriveFile | null) {
const message = await MessagingMessages.save({
id: genId(),
createdAt: new Date(),
fileId: file ? file.id : null,
recipientId: recipientUser ? recipientUser.id : null,
groupId: recipientGroup ? recipientGroup.id : null,
text: text ? text.trim() : null,
userId: user.id,
isRead: false,
reads: [] as any[]
} as MessagingMessage);
const messageObj = await MessagingMessages.pack(message);
if (recipientUser) {
if (Users.isLocalUser(user)) {
// 自分のストリーム
publishMessagingStream(message.userId, recipientUser.id, 'message', messageObj);
publishMessagingIndexStream(message.userId, 'message', messageObj);
publishMainStream(message.userId, 'messagingMessage', messageObj);
}
if (Users.isLocalUser(recipientUser)) {
// 相手のストリーム
publishMessagingStream(recipientUser.id, message.userId, 'message', messageObj);
publishMessagingIndexStream(recipientUser.id, 'message', messageObj);
publishMainStream(recipientUser.id, 'messagingMessage', messageObj);
}
} else if (recipientGroup) {
// グループのストリーム
publishGroupMessagingStream(recipientGroup.id, 'message', messageObj);
// メンバーのストリーム
const joinings = await UserGroupJoinings.find({ userGroupId: recipientGroup.id });
for (const joining of joinings) {
publishMessagingIndexStream(joining.userId, 'message', messageObj);
publishMainStream(joining.userId, 'messagingMessage', messageObj);
}
}
// 2秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する
setTimeout(async () => {
const freshMessage = await MessagingMessages.findOne(message.id);
if (freshMessage == null) return; // メッセージが削除されている場合もある
if (recipientUser && Users.isLocalUser(recipientUser)) {
if (freshMessage.isRead) return; // 既読
//#region ただしミュートされているなら発行しない
const mute = await Mutings.find({
muterId: recipientUser.id,
});
if (mute.map(m => m.muteeId).includes(user.id)) return;
//#endregion
publishMainStream(recipientUser.id, 'unreadMessagingMessage', messageObj);
pushNotification(recipientUser.id, 'unreadMessagingMessage', messageObj);
} else if (recipientGroup) {
const joinings = await UserGroupJoinings.find({ userGroupId: recipientGroup.id, userId: Not(user.id) });
for (const joining of joinings) {
if (freshMessage.reads.includes(joining.userId)) return; // 既読
publishMainStream(joining.userId, 'unreadMessagingMessage', messageObj);
pushNotification(joining.userId, 'unreadMessagingMessage', messageObj);
}
}
}, 2000);
if (recipientUser && Users.isLocalUser(user) && Users.isRemoteUser(recipientUser)) {
const note = {
id: message.id,
createdAt: message.createdAt,
fileIds: message.fileId ? [ message.fileId ] : [],
text: message.text,
userId: message.userId,
visibility: 'specified',
mentions: [ recipientUser ].map(u => u.id),
mentionedRemoteUsers: JSON.stringify([ recipientUser ].map(u => ({
uri: u.uri,
username: u.username,
host: u.host
}))),
} as Note;
const activity = renderActivity(renderCreate(await renderNote(note, false, true), note));
deliver(user, activity, recipientUser.inbox);
}
return messageObj;
}

View File

@@ -16,11 +16,11 @@ import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
import extractMentions from '../../misc/extract-mentions'; import extractMentions from '../../misc/extract-mentions';
import extractEmojis from '../../misc/extract-emojis'; import extractEmojis from '../../misc/extract-emojis';
import extractHashtags from '../../misc/extract-hashtags'; import extractHashtags from '../../misc/extract-hashtags';
import { Note } from '../../models/entities/note'; import { Note, IMentionedRemoteUsers } from '../../models/entities/note';
import { Mutings, Users, NoteWatchings, Followings, Notes, Instances, UserProfiles } from '../../models'; import { Mutings, Users, NoteWatchings, Followings, Notes, Instances, UserProfiles } from '../../models';
import { DriveFile } from '../../models/entities/drive-file'; import { DriveFile } from '../../models/entities/drive-file';
import { App } from '../../models/entities/app'; import { App } from '../../models/entities/app';
import { Not, getConnection } from 'typeorm'; import { Not, getConnection, In } from 'typeorm';
import { User, ILocalUser, IRemoteUser } from '../../models/entities/user'; import { User, ILocalUser, IRemoteUser } from '../../models/entities/user';
import { genId } from '../../misc/gen-id'; import { genId } from '../../misc/gen-id';
import { notesChart, perUserNotesChart, activeUsersChart, instanceChart } from '../chart'; import { notesChart, perUserNotesChart, activeUsersChart, instanceChart } from '../chart';
@@ -383,11 +383,17 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri
// Append mentions data // Append mentions data
if (mentionedUsers.length > 0) { if (mentionedUsers.length > 0) {
insert.mentions = mentionedUsers.map(u => u.id); insert.mentions = mentionedUsers.map(u => u.id);
insert.mentionedRemoteUsers = JSON.stringify(mentionedUsers.filter(u => Users.isRemoteUser(u)).map(u => ({ const profiles = await UserProfiles.find({ userId: In(insert.mentions) });
uri: (u as IRemoteUser).uri, insert.mentionedRemoteUsers = JSON.stringify(mentionedUsers.filter(u => Users.isRemoteUser(u)).map(u => {
username: u.username, const profile = profiles.find(p => p.userId == u.id);
host: u.host const url = profile != null ? profile.url : null;
}))); return {
uri: u.uri,
url: url == null ? undefined : url,
username: u.username,
host: u.host
} as IMentionedRemoteUsers[0];
}));
} }
// 投稿を作成 // 投稿を作成

View File

@@ -29,7 +29,9 @@ export default async function(userId: string, type: string, body?: any) {
push.sendNotification(pushSubscription, JSON.stringify({ push.sendNotification(pushSubscription, JSON.stringify({
type, body type, body
})).catch((err: any) => { }), {
proxy: config.proxy
}).catch((err: any) => {
//swLogger.info(err.statusCode); //swLogger.info(err.statusCode);
//swLogger.info(err.headers); //swLogger.info(err.headers);
//swLogger.info(err.body); //swLogger.info(err.body);

View File

@@ -106,6 +106,29 @@ describe('Chart', () => {
}); });
})); }));
it('Can updates (dec)', async(async () => {
await testChart.decrement();
const chartHours = await testChart.getChart('hour', 3);
const chartDays = await testChart.getChart('day', 3);
assert.deepStrictEqual(chartHours, {
foo: {
dec: [1, 0, 0],
inc: [0, 0, 0],
total: [-1, 0, 0]
},
});
assert.deepStrictEqual(chartDays, {
foo: {
dec: [1, 0, 0],
inc: [0, 0, 0],
total: [-1, 0, 0]
},
});
}));
it('Empty chart', async(async () => { it('Empty chart', async(async () => {
const chartHours = await testChart.getChart('hour', 3); const chartHours = await testChart.getChart('hour', 3);
const chartDays = await testChart.getChart('day', 3); const chartDays = await testChart.getChart('day', 3);

View File

@@ -6,7 +6,7 @@ import * as fs from 'fs';
import * as webpack from 'webpack'; import * as webpack from 'webpack';
import chalk from 'chalk'; import chalk from 'chalk';
const { VueLoaderPlugin } = require('vue-loader'); const { VueLoaderPlugin } = require('vue-loader');
//const HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
const ProgressBarPlugin = require('progress-bar-webpack-plugin'); const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin');
@@ -20,6 +20,7 @@ class WebpackOnBuildPlugin {
} }
const isProduction = process.env.NODE_ENV == 'production'; const isProduction = process.env.NODE_ENV == 'production';
const useHardSource = process.env.MISSKEY_USE_HARD_SOURCE;
const constants = require('./src/const.json'); const constants = require('./src/const.json');
@@ -113,7 +114,7 @@ module.exports = {
}] }]
}, },
plugins: [ plugins: [
//new HardSourceWebpackPlugin(), ...(useHardSource ? [new HardSourceWebpackPlugin()] : []),
new ProgressBarPlugin({ new ProgressBarPlugin({
format: chalk` {cyan.bold yes we can} {bold [}:bar{bold ]} {green.bold :percent} {gray (:current/:total)} :elapseds`, format: chalk` {cyan.bold yes we can} {bold [}:bar{bold ]} {green.bold :percent} {gray (:current/:total)} :elapseds`,
clear: false clear: false

1846
yarn.lock

File diff suppressed because it is too large Load Diff