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
# Job concurrency per worker
# deliverJobConcurrency: 128;
# inboxJobConcurrency: 16;
# deliverJobConcurrency: 128
# inboxJobConcurrency: 16
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4

View File

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

View File

@@ -1,6 +1,48 @@
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)
--------------------
### ✨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

View File

@@ -13,7 +13,7 @@
<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),
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>
<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 -->
<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/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://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>
</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/weepjp">weepjp</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/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://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://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>
@@ -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/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/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/yukimochi">YUKIMOCHI</a></td>
</tr></table>
<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/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/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/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>
@@ -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>
</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=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/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=4389829">natalie</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>
<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/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://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>
</tr><tr>
<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/user?u=23932002">nenohi</a></td>
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
</tr></table>
**Last updated:** Wed, 25 Sep 2019 21:27:06 UTC
**Last updated:** Thu, 31 Oct 2019 06:09:07 UTC
<!-- PATREON_END -->
:four_leaf_clover: Copyright

View File

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

View File

@@ -31,6 +31,7 @@ common:
signup: "Registrovat"
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ď?"
fetching-as-ap-object: "Načítám data z Fediversu..."
delete-confirm: "Opravdu chcete smazat tento příspěvek?"
signin-required: "Přihlašte se, prosím"
notification-type: "Typy oznámení"
@@ -101,6 +102,9 @@ common:
"read:reactions": "Prohlížet reakce"
"write:reactions": "Narabět s reakcemi"
"write:votes": "Hlasovat"
"read:pages": "Zhlédnutí stránky"
"write:pages": "Upravit stránky"
"write:user-groups": "Upravit uživatelskou skupinu"
empty-timeline-info:
follow-users-to-make-your-timeline: "Poznámky sledujících se zobrazí ve vaší časové ose"
explore: "Najít uživatele"
@@ -115,10 +119,12 @@ common:
renote: "Renotovat"
posting: "Posílání"
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"
create-poll: "Vytvořit anketu"
text-remain: "zbývá ještě {} znaků"
recent-tags: "Nejnovější"
local-only-message: "Publikovat zprávu pouze lokálně"
click-to-tagging: "Klikni pro otágování"
visibility: "Viditelnost"
geolocation-alert: "Vaše zařízení nedalo k dispozici lokaci"
@@ -187,6 +193,7 @@ common:
other: "Ostatní"
appearance: "Vzhled"
behavior: "Chování"
reactions: "Reakce"
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."
note-visibility: "Viditelnost příspěvku"
@@ -195,6 +202,8 @@ common:
web-search-engine: "Webové vyhledávače"
web-search-engine-desc: "Například: https://www.google.com/?#q={{query}}"
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-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)"
@@ -383,6 +392,7 @@ common/views/components/games/reversi/reversi.game.vue:
surrender: "Vzdát se"
surrendered: "Vzdaním se"
looped-map: "Zacyklená mapa"
can-put-everywhere: "Lze položit kamkoliv"
common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi"
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"
success: "Úspěšně se podařilo spojit s Misskey serverem"
flush: "Vyčistit mezipaměť"
set-version: "Vyberte verzi"
common/views/components/media-banner.vue:
sensitive: "Choulostivý obsah"
click-to-show: "Klikněte pro zobrazení"
@@ -485,6 +496,7 @@ common/views/components/messaging.vue:
group: "Skupina"
start-with-user: "Zahájit konverzaci s uživatelem"
start-with-group: "Zahájit skupinovou konverzaci"
select-group: "Vybrat skupinu"
common/views/components/messaging-room.vue:
new-message: "Máte novou zprávu"
common/views/components/messaging-room.form.vue:
@@ -556,6 +568,7 @@ common/views/components/poll-editor.vue:
deadline-date: "Termín ukončení"
deadline-time: "Doba trvání"
interval: "Trvání"
unit: "Jednotka"
second: "Sekunda"
minute: "Minuta"
hour: "Hodina"
@@ -574,6 +587,12 @@ common/views/components/emoji-picker.vue:
symbols: "Symboly"
flags: "Vlajky"
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."
common/views/components/signin.vue:
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-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."
enter-2fa-code: "Vložte Váš verifikační kód"
common/views/components/signup.vue:
invitation-code: "Kód pozvánky"
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-not-matched: "Neshodují se"
recaptcha: "Potvrzení"
agree-to: "Souhlasím s {0}."
tos: "Podmínky užívání"
create: "Vytvořit účet"
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-talk-messages: "Označit všechny zprávy za přečtené"
common/views/components/integration-settings.vue:
title: "Integrace"
connect: "Připojit"
disconnect: "Odpojit"
connected-to: "Jste připojen k tomuto GitHub účtu"
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."
connected-to: "Je připojen k tomuto GitHub účtu"
@@ -643,6 +666,8 @@ common/views/components/uploader.vue:
common/views/components/visibility-chooser.vue:
public: "Veřejné"
home: "Domů"
home-desc: "Poslat pouze na domovskou časovou osu"
specified: "Přímá"
specified-desc: "Poslat pouze zmíněným uživatelům"
local-public: "Veřejná (pouze místní)"
local-public-desc: "Nepublikovat na vzdálených serverech"
@@ -727,12 +752,14 @@ common/views/components/user-groups.vue:
group-name: "Název skupiny"
owned-groups: "Moje skupiny"
invites: "Pozvat"
accept-invite: "Přidat se"
reject-invite: "Odmítnout"
common/views/widgets/broadcast.vue:
fetching: "Načítám"
no-broadcasts: "Žádná nová oznámení"
have-a-nice-day: "Přejeme Vám příjemný den!"
next: "Další"
prev: "Předchozí"
common/views/widgets/calendar.vue:
year: "Rok {}"
month: "{},"
@@ -741,6 +768,7 @@ common/views/widgets/calendar.vue:
this-month: "Měsíc:"
this-year: "Rok:"
common/views/widgets/photo-stream.vue:
title: "Foto stream"
no-photos: "Žádné obrázky"
common/views/widgets/posts-monitor.vue:
title: "Grafy příspěvků"
@@ -756,6 +784,8 @@ common/views/widgets/memo.vue:
save: "Uložit"
common/views/widgets/slideshow.vue:
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:
page-not-found: "Stránka nenalezena"
common/views/pages/follow.vue:
@@ -827,6 +857,7 @@ desktop/views/components/drive.folder.vue:
input-new-folder-name: "Zadejte nové jméno"
else-folders: "Ostatní"
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-folder: "Tato složka je prázdná"
unable-to-process: "Operace nemohla být dokončena."
@@ -842,10 +873,13 @@ desktop/views/components/drive.vue:
url-upload: "Nahrát z URL"
desktop/views/components/media-video.vue:
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:
game: "Reversi"
desktop/views/components/home.vue:
done: "Hotovo"
add-widget: "Přidat widget:"
add: "Přidat"
desktop/views/input-dialog.vue:
cancel: "Zrušit"
@@ -853,6 +887,7 @@ desktop/views/input-dialog.vue:
desktop/views/components/note-detail.vue:
private: "Tento příspěvek je soukromý"
deleted: "Tento příspěvek byl odstraněn"
location: "Lokace"
renote: "Renotovat"
add-reaction: "Přidat reakci"
undo-reaction: "Odebrat reakci"
@@ -867,6 +902,8 @@ desktop/views/components/note.vue:
desktop/views/components/notes.vue:
error: "Načítání selhalo."
retry: "Opakovat"
desktop/views/components/notifications.vue:
empty: "Žádné nové notifikace!"
desktop/views/components/post-form.vue:
posted: "Odesláno!"
replied: "Odpověděno!"
@@ -891,10 +928,29 @@ desktop/views/components/renote-form-window.vue:
desktop/views/components/settings.2fa.vue:
detail: "Více…"
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"
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:
sensitive: "NSFW"
click-to-show: "Klikněte pro zobrazení"
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:"
enter-password: "Prosím zadejte heslo"
console:
@@ -976,20 +1032,48 @@ admin/views/index.vue:
announcements: "Oznámení"
queue: "Fronta úloh"
logs: "Logy"
db: "Databáze"
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:
dashboard: "Kontrolní panel"
accounts: "Účty"
notes: "Poznámky"
drive: "Disk"
instances: "Instance"
this-instance: "Tato instance"
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:
logs: "Logy"
domain: "Doména"
level: "Úroveň"
levels:
all: "Vše"
info: "Informace"
success: "Podařilo se"
warning: "Varování"
error: "Chyba"
debug: "Debug"
delete-all: "Smazat vše"
admin/views/abuse.vue:
target: "Cíl"
details: "Popis"
remove-report: "Odstranit"
admin/views/instance.vue:
@@ -997,17 +1081,33 @@ admin/views/instance.vue:
instance-name: "Název instance"
instance-description: "Popis instance"
host: "Hostitel"
icon-url: "URL ikonky"
logo-url: "URL loga"
banner-url: "URL pro baner"
error-image-url: "URL pro chybový obrázek"
languages: "Jazyk této instance"
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-name: "Jméno 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-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"
recaptcha-config: "nastavení služby reCAPTCHA"
recaptcha-info: "reCAPTCHA token je povinný. Můžete jej získat na https://www.google.com/recaptcha/intro/"
enable-recaptcha: "povolit reCAPTCHA"
recaptcha-secret-key: "Tajný Klíč (Secret Key)"
recaptcha-preview: "Náhled"
twitter-integration-config: "Nastavení spojení s Twitterem"
twitter-integration-info: "The callback URL is set on {url}."
@@ -1206,6 +1306,9 @@ desktop/views/widgets/notifications.vue:
title: "Oznámení"
desktop/views/widgets/polls.vue:
title: "Ankety"
nothing: "Žádné nové notifikace!"
desktop/views/widgets/trends.vue:
nothing: "Žádné nové notifikace!"
desktop/views/widgets/users.vue:
title: "Doporučení uživatelé"
mobile/views/components/drive.vue:
@@ -1248,6 +1351,8 @@ mobile/views/components/note-sub.vue:
admin: "admin"
bot: "bot"
cat: "kočka"
mobile/views/components/notifications.vue:
empty: "Žádné nové notifikace!"
mobile/views/components/sub-note-content.vue:
private: "Tento příspěvek je soukromý"
deleted: "Tento příspěvek byl odstraněn"
@@ -1291,6 +1396,7 @@ mobile/views/pages/note.vue:
mobile/views/pages/games/reversi.vue:
reversi: "Reversi"
mobile/views/pages/search.vue:
search: "Vyhledávání"
not-found: "Pro '{q}' nebyly nalezeny žádné příspěvky."
mobile/views/pages/selectdrive.vue:
select-file: "Vybrat soubory"

View File

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

View File

@@ -91,7 +91,7 @@ common:
"write:drive": "Work with the Drive"
"read:favorites": "View Favorites"
"write:favorites": "Work with Favorites"
"read:following": "View Follower info"
"read:following": "View your Follow info"
"write:following": "Work with Follow info"
"read:messaging": "View Messaging"
"write:messaging": "Work with Messaging"
@@ -103,6 +103,12 @@ common:
"read:reactions": "View reactions"
"write:reactions": "Work with reactions"
"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:
follow-users-to-make-your-timeline: "Following users will show their posts in your timeline."
explore: "Find users"
@@ -194,6 +200,8 @@ common:
other: "Other"
appearance: "Appearance"
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-desc: "When you scroll down the page, it automatically fetches additional content."
note-visibility: "Post visibility"
@@ -384,9 +392,11 @@ common/views/pages/explore.vue:
popular-users: "Popular users"
recently-updated-users: "Recently active users"
recently-registered-users: "Users who joined recently"
recently-discovered-users: "Recently Discovered Users"
popular-tags: "Popular Tags"
federated: "From the fediverse"
explore: "Explore {host}"
explore-fediverse: "Explore Fediverse"
users-info: "Currently, {users} users are registered here"
common/views/components/reactions-viewer.details.vue:
few-users: "{users} reacted with {reaction}"
@@ -619,7 +629,9 @@ common/views/components/reaction-picker.vue:
choose-reaction: "Send a reaction"
input-reaction-placeholder: "or input Emoji"
common/views/components/emoji-picker.vue:
recent-emoji: "Recently used"
custom-emoji: "Custom Emoji"
no-category: "Uncategorized"
people: "People"
animals-and-nature: "Animals & Nature"
food-and-drink: "Food & drink"
@@ -1294,7 +1306,7 @@ admin/views/instance.vue:
discord-integration-client-id: "Client ID"
discord-integration-client-secret: "Client Secret"
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-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."
@@ -1445,6 +1457,7 @@ admin/views/emoji.vue:
title: "Add emoji"
name: "Emoji name"
name-desc: "You can use the characters a~z 0~9 _"
category: "Categories"
aliases: "Aliases"
aliases-desc: "You can add more than one, separated by spaces."
url: "Image URL"

View File

@@ -99,6 +99,7 @@ common:
"read:reactions": "Lire les réactions"
"write:reactions": "Gérer vos réactions"
"write:votes": "Vote"
"read:pages": "Afficher la page"
empty-timeline-info:
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"
@@ -188,6 +189,7 @@ common:
other: "Avancé"
appearance: "Apparence"
behavior: "Comportement"
reactions: "Réaction"
fetch-on-scroll: "Chargement automatique lors du défilement"
fetch-on-scroll-desc: "Chargement automatique du contenu lors du défilement de la page."
note-visibility: "Visibilité de la publication"

View File

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

View File

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

View File

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

View File

@@ -3,16 +3,58 @@ meta:
lang: "Nederlands"
common:
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:
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-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-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"
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"
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:
all: "Alle"
pollVote: "Stemmen"
follow: "Volgend"
receiveFollowRequest: "Volgverzoeken"
reply: "Beantwoorden"
quote: "Bron"
mention: "Vermeldingen"
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:
unknown: "onbekend"
future: "toekomstig"
@@ -24,7 +66,13 @@ common:
weeks_ago: "{}week/weken geleden"
months_ago: "{}maand(en) geleden"
years_ago: "{}jaar geleden"
month-and-day: "{day} {month}"
trash: "Prullenbak"
drive: "Drive"
pages: "Pagina's"
messaging: "Gesprekken"
home: "Startpagina"
deck: "Deck"
timeline: "Tijdlijn"
followers: "Volgers"
favorites: "Deze notitie toevoegen aan favorieten"
@@ -59,7 +107,11 @@ common:
profile: "Je profiel"
notification: "Meldingen"
password: "Wachtwoord"
reactions: "Reactie"
deck-column-align-center: "Centreren"
deck-column-align-left: "Links"
timeline: "Tijdlijn"
navbar-position-left: "Links"
search: "Zoeken"
delete: "Verwijderen"
loading: "Bezig met laden"
@@ -423,11 +475,13 @@ admin/views/index.vue:
users: "Gebruiker"
admin/views/dashboard.vue:
notes: "Bericht"
drive: "Drive"
admin/views/abuse.vue:
remove-report: "Verwijderen"
admin/views/charts.vue:
notes: "Bericht"
users: "Gebruiker"
drive: "Drive"
admin/views/drive.vue:
origin:
local: "Lokaal"
@@ -563,6 +617,7 @@ mobile/views/pages/home.vue:
global: "Algemeen"
mobile/views/pages/widgets.vue:
add-widget: "Toevoegen"
customization-tips: "Aanpassingstips"
mobile/views/pages/widgets/activity.vue:
activity: "Activiteit"
mobile/views/pages/note.vue:
@@ -643,3 +698,4 @@ room:
translate: "Verplaatsen"
furnitures:
moon: "Maan"
bin: "Prullenbak"

View File

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

View File

@@ -2,12 +2,12 @@
meta:
lang: "中文(简体)"
common:
misskey: "Fediverse中的一颗⭐"
about-title: "Fediverse中的一颗⭐"
about: "非常感谢您找到了Misskey。 Misskey是诞生于地球的<b>分布式微博SNS</b>。因为她处于Fediverse(由各种SNS组成的宇宙)中所以她与其他SNS相互连接。想要远离喧嚣的城市,不如深入这新的互联网来探索一下吧。"
misskey: "联邦宇宙中的一颗⭐"
about-title: "联邦宇宙中的一颗⭐"
about: "非常感谢您找到了Misskey。 Misskey是诞生于地球的<b>分布式微博SNS</b>。因为她处于联邦宇宙(由各种SNS组成的宇宙)中所以她与其他SNS相互连接。为什么不试试远离喧嚣的城市,入这新的网络海洋之中呢?"
intro:
title: "什么是 Misskey 呢?"
about: "Misskey是开源的<b>分散式微博SNS</b>。复杂的完全可定制的Ui各种各样的帖子反应提供集成管理系统和其他先进功能的免费文件存储。此外称为“Fediverse”的网络系统使我们能够与其他SNS的用户进行通信。比如如果你张贴一些东西那么你的帖子不仅会发送给Misskey还会发送到其他SNS平台。想象一下正如一颗行星和另一颗行星通过电磁波来进行通信一样。"
about: "Misskey是开源的<b>分散式微博SNS</b>。丰富且可以高度定制的Ui对别人的帖子进行各种回应集成管理系统的网盘等先进功能。此外称为“联邦世界”的网络系统使我们能够与其他SNS的用户进行通信。比如如果你发布一个帖子那么你的帖子不仅会发送给Misskey还会发送到其他SNS平台。想象一下正如一颗行星和另一颗行星通过电磁波来进行通信一样。"
features: "功能"
rich-contents: "发布"
rich-contents-desc: "请分享您的想法,热门话题,以及任何您想与大家分享的内容。如果有需要的话,您可以使用各种语法来修饰文章,发布问卷调查,或者添加各种您喜欢的图像和视频等文件。"
@@ -16,7 +16,7 @@ common:
ui: "交互界面"
ui-desc: "世界上没有一个UI可以适合每一个人. 所以, Misskey 提供一个可以高度定制的UI交互界面. 您可以通过编辑, 调整布局, 放置可选择的小部件来轻松定制您的专属UI界面。"
drive: "网盘"
drive-desc: "想要发布一张您已经上传过的照片吗想要管理文件或为上传的文件创建文件夹吗Misskey的网盘是一个最好的解决方案。"
drive-desc: "想要发布一张您已经上传过的照片吗想要管理文件或为上传的文件创建文件夹吗Misskey的内置网盘将为您完美解决这些问题。简单地分享您的文件。"
outro: "Misskey还有其他更多功能请亲身体验一下吧。因为 Misskey 是一个分布式的 SNS如果您感觉某个功能不适合自己试试其他的吧。祝您玩得开心"
application-authorization: "应用程序授权"
close: "关闭"
@@ -103,6 +103,12 @@ common:
"read:reactions": "查看回应"
"write:reactions": "回应操作"
"write:votes": "投票"
"read:pages": "查看页面"
"write:pages": "操作页面"
"read:page-likes": "查看喜欢的页面"
"write:page-likes": "操作喜欢的页面"
"read:user-groups": "查看用户组"
"write:user-groups": "操作用户组"
empty-timeline-info:
follow-users-to-make-your-timeline: "关注其他用户时,帖子将显示在时间线中。"
explore: "查找用户"
@@ -185,7 +191,7 @@ common:
profile: "个人资料"
notification: "通知"
apps: "应用程序"
tags: "哈希标签"
tags: "话题标签"
mute-and-block: "屏蔽/拉黑"
blocking: "拉黑"
security: "安全性"
@@ -194,6 +200,8 @@ common:
other: "其他"
appearance: "设计"
behavior: "行为"
reactions: "回应"
reactions-description: "快速选择回应中的自定义表情符号,以换行符分隔。"
fetch-on-scroll: "向下滚动时自动加载"
fetch-on-scroll-desc: "向下滚动页面时,它会自动提取其他内容。"
note-visibility: "帖子可见性"
@@ -384,9 +392,11 @@ common/views/pages/explore.vue:
popular-users: "热门用户"
recently-updated-users: "活跃用户"
recently-registered-users: "新用户"
recently-discovered-users: "最近发现的用户"
popular-tags: "热门标签"
federated: "联"
federated: "联"
explore: "查找{host}"
explore-fediverse: "探索Fediverse"
users-info: "当前有{users}个注册用户"
common/views/components/reactions-viewer.details.vue:
few-users: "{users}作出了{reaction}的回应"
@@ -619,7 +629,9 @@ common/views/components/reaction-picker.vue:
choose-reaction: "选择回应"
input-reaction-placeholder: "表情符号输入"
common/views/components/emoji-picker.vue:
recent-emoji: "最近使用的表情符号"
custom-emoji: "自定义表情符号"
no-category: "未分类"
people: "人"
animals-and-nature: "动物与自然"
food-and-drink: "食物与饮品"
@@ -640,7 +652,7 @@ common/views/components/signin.vue:
username: "用户名"
password: "密码"
token: "Token (令牌)"
signing-in: "正在登录..."
signing-in: "在弄了在弄了..."
or: "或者"
signin-with-twitter: "用 Twitter 登录"
signin-with-github: "用 GitHub 登录"
@@ -1170,16 +1182,16 @@ desktop/views/components/window.vue:
popout: "弹出"
close: "关闭"
admin/views/index.vue:
dashboard: "Dashboard"
instance: "例"
emoji: "Emoji"
dashboard: "仪表盘"
instance: "例"
emoji: "自定义Emoji"
moderators: "版主"
users: "用户"
federation: "联"
federation: "联"
announcements: "公告"
abuse: "举报垃圾信息"
queue: "作业队列"
logs: "登录"
logs: "日志"
db: "数据库"
back-to-misskey: "返回 Misskey"
admin/views/db.vue:
@@ -1192,7 +1204,7 @@ admin/views/dashboard.vue:
accounts: "账户"
notes: "帖子"
drive: "网盘"
instances: "例"
instances: "例"
this-instance: "此实例"
federated: "联合"
admin/views/queue.vue:
@@ -1213,7 +1225,7 @@ admin/views/queue.vue:
result-is-truncated: "结果已省略"
other-queues: "其他队列"
admin/views/logs.vue:
logs: "登录"
logs: "日志"
domain: "域"
level: "级别"
levels:
@@ -1231,7 +1243,7 @@ admin/views/abuse.vue:
details: "详情"
remove-report: "删除"
admin/views/instance.vue:
instance: "例"
instance: "例"
instance-name: "实例名称"
instance-description: "实例介绍"
host: "主机名"
@@ -1435,7 +1447,7 @@ admin/views/moderators.vue:
remove: "取消"
removed: "取消注册版主"
logs:
title: "登录"
title: "日志"
moderator: "版主"
type: "操作"
at: "日期和时间"
@@ -1445,6 +1457,7 @@ admin/views/emoji.vue:
title: "添加emoji"
name: "Emoji 名称"
name-desc: "你可以使用字符a~z 0~9 _"
category: "类别"
aliases: "别名"
aliases-desc: "您可以添加多个,以空格分隔。"
url: "emoji 地址"
@@ -1489,7 +1502,7 @@ admin/views/federation.vue:
block: "拉黑"
marked-as-closed: "标记为已关闭"
lookup: "查询"
instances: "联"
instances: "联"
instance-not-registered: "实例未注册"
sort: "排序"
sorts:
@@ -1609,7 +1622,7 @@ desktop/views/widgets/trends.vue:
desktop/views/widgets/users.vue:
title: "推荐用户"
refresh: "更多"
no-one: "不是"
no-one: "没有任何推荐用户"
mobile/views/components/drive.vue:
used: "已使用"
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",
"author": "syuilo <i@syuilo.com>",
"version": "11.33.0",
"version": "11.35.0",
"codename": "daybreak",
"repository": {
"type": "git",
@@ -27,10 +27,11 @@
},
"resolutions": {
"gulp-cssnano/cssnano/postcss-svgo/svgo/js-yaml": "^3.13.1",
"https-proxy-agent": "^3.0.0",
"lodash": "^4.17.13"
},
"dependencies": {
"@elastic/elasticsearch": "7.3.0",
"@elastic/elasticsearch": "7.4.0",
"@fortawesome/fontawesome-svg-core": "1.2.25",
"@fortawesome/free-brands-svg-icons": "5.11.2",
"@fortawesome/free-regular-svg-icons": "5.11.2",
@@ -38,7 +39,7 @@
"@fortawesome/vue-fontawesome": "0.1.7",
"@koa/cors": "3.0.0",
"@koa/multer": "2.0.0",
"@koa/router": "8.0.1",
"@koa/router": "8.0.2",
"@types/bcryptjs": "2.4.2",
"@types/bull": "3.10.3",
"@types/cbor": "2.0.0",
@@ -69,7 +70,7 @@
"@types/koa__router": "8.0.0",
"@types/lolex": "3.1.1",
"@types/mocha": "5.2.7",
"@types/node": "12.7.8",
"@types/node": "12.7.12",
"@types/nodemailer": "6.2.1",
"@types/nprogress": "0.2.0",
"@types/oauth": "0.9.1",
@@ -95,21 +96,21 @@
"@types/tmp": "0.1.0",
"@types/uuid": "3.4.5",
"@types/web-push": "3.3.0",
"@types/webpack": "4.39.1",
"@types/webpack": "4.39.3",
"@types/webpack-stream": "3.2.10",
"@types/websocket": "0.0.40",
"@types/ws": "6.0.3",
"@typescript-eslint/parser": "2.3.1",
"agentkeepalive": "4.0.2",
"@typescript-eslint/parser": "2.3.3",
"agentkeepalive": "4.1.0",
"animejs": "3.1.0",
"apexcharts": "3.8.6",
"apexcharts": "3.10.0",
"autobind-decorator": "2.4.0",
"autosize": "4.0.2",
"autwh": "0.1.0",
"aws-sdk": "2.537.0",
"aws-sdk": "2.548.0",
"bcryptjs": "2.4.3",
"bootstrap": "4.3.1",
"bootstrap-vue": "2.0.2",
"bootstrap-vue": "2.0.4",
"bull": "3.11.0",
"cafy": "15.1.1",
"cbor": "5.0.1",
@@ -125,7 +126,7 @@
"deep-equal": "1.1.0",
"diskusage": "1.1.3",
"double-ended-queue": "2.1.0-0",
"eslint": "6.4.0",
"eslint": "6.5.1",
"eslint-plugin-vue": "5.2.3",
"eventemitter3": "4.0.0",
"feed": "4.0.0",
@@ -133,8 +134,7 @@
"fluent-ffmpeg": "2.1.2",
"gulp": "4.0.2",
"gulp-cssnano": "2.1.3",
"gulp-imagemin": "6.1.0",
"gulp-mocha": "7.0.1",
"gulp-mocha": "7.0.2",
"gulp-rename": "1.4.0",
"gulp-replace": "1.0.0",
"gulp-sourcemaps": "2.6.5",
@@ -146,17 +146,17 @@
"hard-source-webpack-plugin": "0.13.1",
"html-minifier": "4.0.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",
"is-root": "2.1.0",
"is-svg": "4.2.0",
"js-yaml": "3.13.1",
"jsdom": "15.1.1",
"json5": "2.1.0",
"json5": "2.1.1",
"json5-loader": "3.0.0",
"jsrsasign": "8.0.12",
"katex": "0.11.1",
"koa": "2.8.1",
"koa": "2.10.0",
"koa-bodyparser": "4.2.1",
"koa-compress": "3.0.0",
"koa-favicon": "2.0.1",
@@ -170,13 +170,13 @@
"loader-utils": "1.2.3",
"lolex": "4.2.0",
"lookup-dns-cache": "2.1.0",
"mocha": "6.2.0",
"mocha": "6.2.1",
"moji": "0.5.1",
"ms": "2.1.2",
"multer": "1.4.2",
"nested-property": "1.0.1",
"node-fetch": "2.6.0",
"nodemailer": "6.3.0",
"nodemailer": "6.3.1",
"nprogress": "0.2.0",
"object-assign-deep": "0.4.0",
"os-utils": "0.0.14",
@@ -220,22 +220,22 @@
"stylus-loader": "3.0.2",
"summaly": "2.3.1",
"syslog-pro": "1.0.0",
"systeminformation": "4.14.8",
"systeminformation": "4.14.11",
"syuilo-password-strength": "0.0.1",
"terser-webpack-plugin": "2.1.0",
"terser-webpack-plugin": "2.1.3",
"textarea-caret": "3.1.0",
"three": "0.108.0",
"three": "0.109.0",
"tinycolor2": "1.4.1",
"tmp": "0.1.0",
"ts-loader": "6.1.2",
"ts-loader": "6.2.0",
"ts-node": "8.4.1",
"tslint": "5.20.0",
"tslint-sonarts": "1.9.0",
"typeorm": "0.2.19",
"typescript": "3.6.3",
"typescript": "3.6.4",
"uglify-es": "3.3.9",
"ulid": "2.3.0",
"url-loader": "2.1.0",
"url-loader": "2.2.0",
"uuid": "3.3.3",
"v-animate-css": "0.0.3",
"v-debounce": "0.1.2",
@@ -252,14 +252,14 @@
"vue-router": "3.1.3",
"vue-sequential-entrance": "1.1.3",
"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",
"vuedraggable": "2.23.1",
"vuedraggable": "2.23.2",
"vuewordcloud": "18.7.11",
"vuex": "3.1.1",
"vuex-persistedstate": "2.5.4",
"web-push": "3.3.5",
"webpack": "4.41.0",
"web-push": "3.4.0",
"webpack": "4.41.1",
"webpack-cli": "3.3.9",
"websocket": "1.0.30",
"ws": "7.1.2",

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
<template>
<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>
<p class="text">
<span>{{ this.$t('description').substr(0, this.$t('description').indexOf('{')) }}</span>

View File

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

View File

@@ -11,25 +11,46 @@
</button>
</header>
<div class="emojis">
<header><fa :icon="categories.find(x => x.isActive).icon" fixed-width/> {{ categories.find(x => x.isActive).text }}</header>
<div v-if="categories.find(x => x.isActive).name">
<button v-for="emoji in emojilist.filter(e => e.category === categories.find(x => x.isActive).name)"
:title="emoji.name"
@click="chosen(emoji.char)"
:key="emoji.name"
>
<mk-emoji :emoji="emoji.char"/>
</button>
</div>
<div v-else>
<button v-for="emoji in customEmojis"
:title="emoji.name"
@click="chosen(`:${emoji.name}:`)"
:key="emoji.name"
>
<img :src="emoji.url" :alt="emoji.name"/>
</button>
</div>
<template v-if="categories[0].isActive">
<header class="category"><fa :icon="faHistory" fixed-width/> {{ $t('recent-emoji') }}</header>
<div class="list">
<button v-for="(emoji, i) in ($store.state.device.recentEmojis || [])"
:title="emoji.name"
@click="chosen(emoji)"
:key="i"
>
<mk-emoji v-if="emoji.char != null" :emoji="emoji.char"/>
<img v-else :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
</button>
</div>
</template>
<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">
<div class="list">
<button v-for="emoji in emojilist.filter(e => e.category === categories.find(x => x.isActive).name)"
: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>
</template>
@@ -38,8 +59,10 @@
import Vue from 'vue';
import i18n from '../../../i18n';
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 { groupByX } from '../../../../../prelude/array';
export default Vue.extend({
i18n: i18n('common/views/components/emoji-picker.vue'),
@@ -47,7 +70,9 @@ export default Vue.extend({
data() {
return {
emojilist,
customEmojis: [],
getStaticImageUrl,
customEmojis: {},
faGlobe, faHistory,
categories: [{
text: this.$t('custom-emoji'),
icon: faAsterisk,
@@ -97,18 +122,43 @@ export default Vue.extend({
},
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: {
go(category) {
go(category: any) {
this.goCategory(category.name);
},
goCategory(name: string) {
let matched = false;
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) {
this.$emit('chosen', emoji);
chosen(emoji: any) {
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-x hidden
> header
> header.category
position sticky
top 0
left 0
@@ -152,7 +202,12 @@ export default Vue.extend({
color var(--text)
font-size 12px
> div
>>> header.sub
padding 4px 8px
color var(--text)
font-size 12px
>>> div.list
display grid
grid-template-columns 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr
gap 4px
@@ -180,6 +235,7 @@ export default Vue.extend({
left 0
width 100%
height 100%
object-fit contain
font-size 28px
transition transform 0.2s ease
pointer-events none

View File

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

View File

@@ -47,6 +47,7 @@ import uiInfo from './ui/info.vue';
import uiMargin from './ui/margin.vue';
import uiHr from './ui/hr.vue';
import uiPagination from './ui/pagination.vue';
import uiModal from './ui/modal.vue';
import formButton from './ui/form/button.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-hr', uiHr);
Vue.component('ui-pagination', uiPagination);
Vue.component('ui-modal', uiModal);
Vue.component('form-button', formButton);
Vue.component('form-radio', formRadio);

View File

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

View File

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

View File

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

View File

@@ -4,16 +4,7 @@
<div class="popover" :class="{ isMobile: $root.isMobile }" ref="popover">
<p v-if="!$root.isMobile">{{ title }}</p>
<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 @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>
<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>
</div>
<div v-if="enableEmojiReaction" class="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>
</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>
<header>{{ $t('@._settings.timeline') }}</header>
<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 }); }
},
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: {
get() { return this.$store.state.device.useShadow; },
set(value) { this.$store.commit('device/set', { key: 'useShadow', value }); }

View File

@@ -6,7 +6,10 @@
<div class="value" ref="passwordMetar"></div>
</div>
<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>
<template v-if="type != 'file'">
<input v-if="debounce" ref="input"
@@ -158,6 +161,7 @@ export default Vue.extend({
return {
v: this.value,
focused: false,
invalid: false,
passwordStrength: '',
id: Math.random().toString()
};
@@ -200,6 +204,8 @@ export default Vue.extend({
this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
(this.$refs.passwordMetar as any).style.width = `${strength * 100}%`;
}
this.invalid = this.$refs.input.validity.badInput;
}
},
mounted() {
@@ -365,6 +371,17 @@ root(fill)
//will-change transform
transform-origin top left
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
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>
<ui-container :body-togglable="true">
<ui-container :body-togglable="true" :expanded="expanded">
<template #header><slot></slot></template>
<mk-error v-if="error" @retry="init()"/>
@@ -50,7 +50,11 @@ export default Vue.extend({
iconOnly: {
type: Boolean,
default: false
}
},
expanded: {
type: Boolean,
default: true
},
},
computed: {

View File

@@ -27,7 +27,7 @@
<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"/>
</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">
<dt class="name">
<mfm :text="field.name" :plain="true" :custom-emojis="user.emojis"/>
@@ -112,10 +112,13 @@ export default Vue.extend({
},
menu() {
this.$root.new(XUserMenu, {
const w = this.$root.new(XUserMenu, {
source: this.$refs.menu,
user: this.user
});
this.$once('hook:beforeDestroy', () => {
w.destroyDom();
});
}
}
});

View File

@@ -1,15 +1,30 @@
<template>
<div>
<ui-container :show-header="false" v-if="meta && stats">
<div class="kpdsmpnk" :style="{ backgroundImage: meta.bannerUrl ? `url(${meta.bannerUrl})` : null }">
<div>
<router-link to="/explore" class="title">{{ $t('explore', { host: meta.name || 'Misskey' }) }}</router-link>
<span>{{ $t('users-info', { users: num(stats.originalUsersCount) }) }}</span>
</div>
</div>
</ui-container>
<div class="localfedi7" v-if="meta && stats && tag == null" :style="{ backgroundImage: meta.bannerUrl ? `url(${meta.bannerUrl})` : null }">
<header>{{ $t('explore', { host: meta.name || 'Misskey' }) }}</header>
<div>{{ $t('users-info', { users: num(stats.originalUsersCount) }) }}</div>
</div>
<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>
<div class="vxjfqztj">
@@ -18,25 +33,18 @@
</div>
</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 }}
</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">
<mk-user-list :pagination="pinnedUsers">
<fa :icon="faBookmark" fixed-width/>{{ $t('pinned-users') }}
</mk-user-list>
<mk-user-list :pagination="popularUsers">
<mk-user-list :pagination="popularUsersF" :expanded="false">
<fa :icon="faChartLine" fixed-width/>{{ $t('popular-users') }}
</mk-user-list>
<mk-user-list :pagination="recentlyUpdatedUsers">
<mk-user-list :pagination="recentlyUpdatedUsersF" :expanded="false">
<fa :icon="faCommentAlt" fixed-width/>{{ $t('recently-updated-users') }}
</mk-user-list>
<mk-user-list :pagination="recentlyRegisteredUsers">
<fa :icon="faPlus" fixed-width/>{{ $t('recently-registered-users') }}
<mk-user-list :pagination="recentlyRegisteredUsersF" :expanded="false">
<fa :icon="faRocket" fixed-width/>{{ $t('recently-discovered-users') }}
</mk-user-list>
</template>
</div>
@@ -45,7 +53,7 @@
<script lang="ts">
import Vue from 'vue';
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';
export default Vue.extend({
@@ -58,6 +66,12 @@ export default Vue.extend({
}
},
inject: {
inNakedDeckColumn: {
default: false
}
},
data() {
return {
pinnedUsers: { endpoint: 'pinned-users' },
@@ -75,12 +89,25 @@ export default Vue.extend({
state: 'alive',
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: [],
tagsRemote: [],
stats: null,
meta: null,
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,
params: {
tag: this.tag,
state: 'alive',
origin: 'local',
sort: '+follower',
}
};
},
tagRemoteUsers(): any {
return {
endpoint: 'hashtags/users',
limit: 30,
params: {
tag: this.tag,
state: 'alive',
origin: 'remote',
origin: 'combined',
sort: '+follower',
}
};
@@ -152,6 +165,28 @@ export default Vue.extend({
</script>
<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
padding 16px
@@ -160,35 +195,4 @@ export default Vue.extend({
&.local
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>

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
<template>
<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>
<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-else><fa icon="angle-down"/></template>
</button>
@@ -47,6 +47,7 @@ export default Vue.extend({
},
methods: {
toggleContent(show: boolean) {
if (!this.bodyTogglable) return;
this.showBody = show;
this.$emit('toggle', show);
}
@@ -77,6 +78,9 @@ export default Vue.extend({
> header
background var(--faceHeader)
&.bodyTogglable
cursor pointer
> .title
z-index 1
margin 0
@@ -120,6 +124,9 @@ export default Vue.extend({
color var(--text)
background var(--deckColumnBg)
&.bodyTogglable
cursor pointer
> button
position absolute
top 0

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
<template>
<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>
<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-else><fa icon="angle-down"/></template>
</button>
@@ -47,6 +47,7 @@ export default Vue.extend({
},
methods: {
toggleContent(show: boolean) {
if (!this.bodyTogglable) return;
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"/>
<x-integrations :user="user" style="margin:20px 0;"/>
</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">
<dt class="name">
<mfm :text="field.name" :plain="true" :custom-emojis="user.emojis"/>

View File

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

View File

@@ -41,6 +41,7 @@ const defaultSettings = {
uploadFolder: null,
pastedFileName: 'yyyy-MM-dd HH-mm-ss [{{number}}]',
pasteDialog: false,
reactions: ['like', 'love', 'laugh', 'hmm', 'surprise', 'congrats', 'angry', 'confused', 'rip', 'pudding']
};
const defaultDeviceSettings = {
@@ -78,6 +79,8 @@ const defaultDeviceSettings = {
enableMobileQuickNotificationView: false,
roomGraphicsQuality: 'medium',
roomUseOrthographicCamera: true,
activeEmojiCategoryName: undefined,
recentEmojis: [],
};
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;
pass: string;
index?: string;
ssl?: boolean;
};
autoAdmin?: boolean;

View File

@@ -32,7 +32,7 @@ const index = {
// Init ElasticSearch connection
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
}) : null;

View File

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

View File

@@ -135,8 +135,8 @@ export function toHtml(tokens: MfmForest | null, mentionedRemoteUsers: IMentione
break;
default:
const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
a.href = remoteUserInfo ? remoteUserInfo.uri : `${config.url}/${acct}`;
a.className = 'mention';
a.href = remoteUserInfo ? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri) : `${config.url}/${acct}`;
a.className = 'u-url mention';
break;
}
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;
@Column('varchar', {
length: 128, nullable: true
})
public category: string | null;
@Column('varchar', {
length: 512,
})

View File

@@ -228,6 +228,7 @@ export class Note {
export type IMentionedRemoteUsers = {
uri: string;
url?: string;
username: 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);
}
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
*/

View File

@@ -23,6 +23,7 @@ import { genId } from '../../../misc/gen-id';
import { fetchMeta } from '../../../misc/fetch-meta';
import { ensure } from '../../../prelude/ensure';
import { getApLock } from '../../../misc/app-lock';
import { createMessage } from '../../../services/messages/create';
const logger = apLogger;
@@ -223,6 +224,13 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
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, {
createdAt: note.published ? new Date(note.published) : null,
files,

View File

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

View File

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

View File

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

View File

@@ -40,18 +40,21 @@ function inbox(ctx: Router.RouterContext) {
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) {
ctx.response.vary('Accept');
const accepted = ctx.accepts('html', 'application/activity+json', 'application/ld+json');
return ['application/activity+json', 'application/ld+json'].includes(accepted as string);
const accepted = ctx.accepts('html', ACTIVITY_JSON, LD_JSON);
return typeof accepted === 'string' && !accepted.match(/html/);
}
export function setResponseType(ctx: Router.RouterContext) {
const accpet = ctx.accepts('application/activity+json', 'application/ld+json');
if (accpet === 'application/ld+json') {
ctx.response.type = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8';
const accept = ctx.accepts(ACTIVITY_JSON, LD_JSON);
if (accept === LD_JSON) {
ctx.response.type = LD_JSON;
} 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 { 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')
.select('muting.muteeId')
.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 { getConnection } from 'typeorm';
import { insertModerationLog } from '../../../../../services/insert-moderation-log';
import { ApiError } from '../../../error';
export const meta = {
desc: {
@@ -25,20 +26,40 @@ export const meta = {
validator: $.str.min(1)
},
category: {
validator: $.optional.str
},
aliases: {
validator: $.optional.arr($.str.min(1)),
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) => {
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({
id: genId(),
updatedAt: new Date(),
name: ps.name,
category: ps.category,
host: null,
aliases: ps.aliases,
url: ps.url,

View File

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

View File

@@ -4,6 +4,7 @@ import { ID } from '../../../../../misc/cafy-id';
import { Emojis } from '../../../../../models';
import { getConnection } from 'typeorm';
import { insertModerationLog } from '../../../../../services/insert-moderation-log';
import { ApiError } from '../../../error';
export const meta = {
desc: {
@@ -19,13 +20,21 @@ export const meta = {
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) => {
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);

View File

@@ -4,6 +4,7 @@ import { detectUrlMine } from '../../../../../misc/detect-url-mine';
import { ID } from '../../../../../misc/cafy-id';
import { Emojis } from '../../../../../models';
import { getConnection } from 'typeorm';
import { ApiError } from '../../../error';
export const meta = {
desc: {
@@ -24,6 +25,10 @@ export const meta = {
validator: $.str
},
category: {
validator: $.optional.str
},
url: {
validator: $.str
},
@@ -31,19 +36,28 @@ export const meta = {
aliases: {
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) => {
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);
await Emojis.update(emoji.id, {
updatedAt: new Date(),
name: ps.name,
category: ps.category,
aliases: ps.aliases,
url: ps.url,
type,

View File

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

View File

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

View File

@@ -1,17 +1,12 @@
import $ from 'cafy';
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 { ApiError } from '../../../error';
import { getUser } from '../../../common/getters';
import { MessagingMessages, DriveFiles, Mutings, UserGroups, UserGroupJoinings } from '../../../../../models';
import { MessagingMessage } from '../../../../../models/entities/messaging-message';
import { genId } from '../../../../../misc/gen-id';
import { MessagingMessages, DriveFiles, UserGroups, UserGroupJoinings } from '../../../../../models';
import { User } from '../../../../../models/entities/user';
import { UserGroup } from '../../../../../models/entities/user-group';
import { Not } from 'typeorm';
import { createMessage } from '../../../../../services/messages/create';
export const meta = {
desc: {
@@ -147,68 +142,5 @@ export default define(meta, async (ps, user) => {
throw new ApiError(meta.errors.contentRequired);
}
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: 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;
return await createMessage(user, recipientUser, recipientGroup, ps.text, file);
});

View File

@@ -7,6 +7,7 @@ import * as pkg from '../../../../package.json';
import { Emojis } from '../../../models';
import { getConnection } from 'typeorm';
import redis from '../../../db/redis';
import { DB_MAX_NOTE_TEXT_LENGTH } from '../../../misc/hard-limits';
export const meta = {
stability: 'stable',
@@ -95,7 +96,19 @@ export const meta = {
export default define(meta, async (ps, me) => {
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 = {
maintainerName: instance.maintainerName,
@@ -138,11 +151,12 @@ export default define(meta, async (ps, me) => {
bannerUrl: instance.bannerUrl,
errorImageUrl: instance.errorImageUrl,
iconUrl: instance.iconUrl,
maxNoteTextLength: instance.maxNoteTextLength,
maxNoteTextLength: Math.min(instance.maxNoteTextLength, DB_MAX_NOTE_TEXT_LENGTH),
emojis: emojis.map(e => ({
id: e.id,
aliases: e.aliases,
name: e.name,
category: e.category,
url: e.url,
})),
enableEmail: instance.enableEmail,

View File

@@ -10,8 +10,9 @@ import { User } from '../../../../models/entities/user';
import { Users, DriveFiles, Notes } from '../../../../models';
import { DriveFile } from '../../../../models/entities/drive-file';
import { Note } from '../../../../models/entities/note';
import { DB_MAX_NOTE_TEXT_LENGTH } from '../../../../misc/hard-limits';
let maxNoteTextLength = 1000;
let maxNoteTextLength = 500;
setInterval(() => {
fetchMeta().then(m => {
@@ -55,7 +56,9 @@ export const meta = {
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,
desc: {

View File

@@ -134,7 +134,7 @@ export default define(meta, async (ps, me) => {
.leftJoinAndSelect('note.user', 'user');
if (me) generateVisibilityQuery(query, me);
if (me) generateMuteQuery(query, me);
if (me) generateMuteQuery(query, me, user);
if (ps.withFiles) {
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.body = InternalStorage.read(key);
} else {
if ('download' in ctx.query) {
ctx.set('Content-Disposition', contentDisposition('attachment', `${file.name}`));
}
ctx.set('Content-Disposition', contentDisposition('inline', `${file.name}`));
const readable = InternalStorage.read(file.accessKey!);
readable.on('error', commonReadableHandlerGenerator(ctx));

View File

@@ -42,4 +42,17 @@ export default class TestChart extends Chart<TestLog> {
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)) {
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;

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 extractEmojis from '../../misc/extract-emojis';
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 { DriveFile } from '../../models/entities/drive-file';
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 { genId } from '../../misc/gen-id';
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
if (mentionedUsers.length > 0) {
insert.mentions = mentionedUsers.map(u => u.id);
insert.mentionedRemoteUsers = JSON.stringify(mentionedUsers.filter(u => Users.isRemoteUser(u)).map(u => ({
uri: (u as IRemoteUser).uri,
username: u.username,
host: u.host
})));
const profiles = await UserProfiles.find({ userId: In(insert.mentions) });
insert.mentionedRemoteUsers = JSON.stringify(mentionedUsers.filter(u => Users.isRemoteUser(u)).map(u => {
const profile = profiles.find(p => p.userId == u.id);
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({
type, body
})).catch((err: any) => {
}), {
proxy: config.proxy
}).catch((err: any) => {
//swLogger.info(err.statusCode);
//swLogger.info(err.headers);
//swLogger.info(err.body);

View File

@@ -105,6 +105,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 () => {
const chartHours = await testChart.getChart('hour', 3);

View File

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

1846
yarn.lock

File diff suppressed because it is too large Load Diff