Compare commits
27 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
770e7378be | ||
![]() |
b9a8620d2f | ||
![]() |
d1c8b2993e | ||
![]() |
01e9b3c2f6 | ||
![]() |
57203de4cb | ||
![]() |
74d0e83a8a | ||
![]() |
9eee5644b9 | ||
![]() |
9fe6f9417e | ||
![]() |
e7de5f6051 | ||
![]() |
60d81d74e3 | ||
![]() |
2701a7e85f | ||
![]() |
31a0afdaab | ||
![]() |
9f87545901 | ||
![]() |
9f94f60ede | ||
![]() |
0ca3c0bca1 | ||
![]() |
27611cef77 | ||
![]() |
30df8ea121 | ||
![]() |
595ad04ddb | ||
![]() |
6b8354ccbf | ||
![]() |
1b9d316e7c | ||
![]() |
a8adc46f3b | ||
![]() |
0efa969a15 | ||
![]() |
14b7f05af4 | ||
![]() |
cf43dd6ec5 | ||
![]() |
b5a1fdd4c7 | ||
![]() |
b32737cdff | ||
![]() |
b505874613 |
38
CHANGELOG.md
38
CHANGELOG.md
@@ -1,5 +1,43 @@
|
||||
ChangeLog
|
||||
=========
|
||||
12.44.1 (2020/7/29)
|
||||
-------------------
|
||||
### 🐛Fixes
|
||||
- 通知が流れない問題を修正 [9f94f60](https://github.com/syuilo/misskey/commit/9f94f60ededccfb3ff109aef1241be633d27eaa7)
|
||||
|
||||
12.44.0 (2020/7/29)
|
||||
-------------------
|
||||
### ✨Improvements
|
||||
- ワードミュートの実装 [#6594](https://github.com/syuilo/misskey/pull/6594)
|
||||
- ページのリストをタブUIに [6b8354c](https://github.com/syuilo/misskey/commit/6b8354ccbfa1d96b4445013d2e93af8e06550516)
|
||||
- プラグインを無効にできるように [595ad04](https://github.com/syuilo/misskey/commit/595ad04ddbbf9ff9fc6842f345d4738a9f1cc150)
|
||||
- AiScript: ノート書き換えAPI [30df8ea](https://github.com/syuilo/misskey/commit/30df8ea1213013072f139aa26a635330457cf2bc)
|
||||
- クライアントのソースコードのリファクタ [b5a1fdd](https://github.com/syuilo/misskey/commit/b5a1fdd4c7597ebdd4ab6022e189da9ca3451dbb), [14b7f05](https://github.com/syuilo/misskey/commit/14b7f05af40ede154a767334dbbefc3458584290), [0efa969](https://github.com/syuilo/misskey/commit/0efa969a153a060d232a0e31b10577ece87faeae), [a8adc46](https://github.com/syuilo/misskey/commit/a8adc46f3ba42e86c64a64f2633f5796aeca01f4), [1b9d316](https://github.com/syuilo/misskey/commit/1b9d316e7c2446211f4b5b6ec27dce0d9b4f0968)
|
||||
|
||||
12.43.0 (2020/7/26)
|
||||
-------------------
|
||||
*このアップデートでは、データベースのマイグレーション(`npm run migrate`/`yarn migrate`)が必要です。*
|
||||
|
||||
### ✨Improvements
|
||||
- 連合ウィジェットを追加 [186b26e](https://github.com/syuilo/misskey/commit/186b26e103d5dc893a741ab9c5805b5dc81f14c0), [e1f2e36](https://github.com/syuilo/misskey/commit/e1f2e364a4347a8da78a32ed741c789a288d3957), [bd54e44](https://github.com/syuilo/misskey/commit/bd54e44b35f7aeae8766054322e2908881323041), [58211fc](https://github.com/syuilo/misskey/commit/58211fc6a72536b066bd8a78fb4bb083cfc1051a), [e5863c2](https://github.com/syuilo/misskey/commit/e5863c2867c1ee8d0d6f2257de7f7fc7791cf8a6), [55be9cc](https://github.com/syuilo/misskey/commit/55be9cc9d130cca541cfe0569885db4d79a58128)
|
||||
* 連合ウィジェットは、最近着信のあったリモートのインスタンスを表示します。
|
||||
- リモートのインスタンスのアイコンを取得して表示するように [#6591](https://github.com/syuilo/misskey/pull/6591), [b07d037](https://github.com/syuilo/misskey/commit/b07d037cb5b1531c38cb2d56ff612bdba5c58a3f), [3f2ffce](https://github.com/syuilo/misskey/commit/3f2ffcea97b6496053fd4027192976bfad2626b0)
|
||||
- インスタンス設定の不足分を追加 [#6576](https://github.com/syuilo/misskey/pull/6576)
|
||||
- クライアントでのソースコードのリファクタ・パフォーマンス改善
|
||||
* lintでのエラーを修正 [#6568](https://github.com/syuilo/misskey/pull/6568)
|
||||
* ~~vue-i18nのv-tを使うように [9c30b23](https://github.com/syuilo/misskey/commit/9c30b23358699a530f2bcb0f5ae6efe17146bcb3)~~ [166bc19](https://github.com/syuilo/misskey/commit/166bc19131ae4b40bdd5e85269729f6eb5e3d931)
|
||||
* 静的な内容にv-onceを付加 [da874f3](https://github.com/syuilo/misskey/commit/da874f3383088dddbf7ce441b0c9d8f6512dfc9b)
|
||||
|
||||
### 🐛Fixes
|
||||
- 投票の残り時間表示の修正 [#6565](https://github.com/syuilo/misskey/pull/6565)
|
||||
- blurhashにした影響で猫耳の色をアイコンに合わせられなくなっているのを修正 [#6585](https://github.com/syuilo/misskey/pull/6585), [7e2b6b6](https://github.com/syuilo/misskey/commit/7e2b6b6369a5eecad2374b84527dca1a712053c9)
|
||||
- 脆弱性のある依存関係をアップデート [#6572](https://github.com/syuilo/misskey/pull/6572)
|
||||
- blurhashのテストを修正 [#6573](https://github.com/syuilo/misskey/pull/6573)
|
||||
- Deckであなた宛て・ダイレクトカラムを追加するとメインカラムに文字が重なる問題を修正 [#6577](https://github.com/syuilo/misskey/pull/6577)
|
||||
- Deckの翻訳を追加 [#6567](https://github.com/syuilo/misskey/pull/6567)
|
||||
- アンテナカラムの挙動を正常化 [#6567](https://github.com/syuilo/misskey/pull/6567)
|
||||
- ウィジェットカラムの挙動を正常化して編集モードの見栄えを良くした [#6567](https://github.com/syuilo/misskey/pull/6567)
|
||||
|
||||
12.42.0 (2020/7/19)
|
||||
-------------------
|
||||
*このアップデートでは、データベースのマイグレーション(`npm run migrate`/`yarn migrate`)が必要です。*
|
||||
|
28
README.md
28
README.md
@@ -112,7 +112,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><img src="https://c8.patreon.com/2/200/20832595" alt="Roujo " width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/27956229" alt="Oliver Maximilian Seidel" 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/605366/c9dc408fdcbf412fb183ca5b06235f8d/1.jpeg?token-time=2145916800&token-hash=oaqsjLqOFjWN5I9hm2epOaTXaEtKwQUy5OW-EpAz6-g%3D" alt="Jon Leibowitz" 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/27648259" alt="みなしま " width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24430516/b1964ac5b9f746d2a12ff53dbc9aa40a/1.jpg?token-time=2145916800&token-hash=bmEiMGYpp3bS7hCCbymjGGsHBZM3AXuBOFO3Kro37PU%3D" alt="Eduardo Quiros" width="100"></td>
|
||||
@@ -120,7 +119,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><a href="https://www.patreon.com/user?u=20832595">Roujo </a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=27956229">Oliver Maximilian Seidel</a></td>
|
||||
<td><a href="https://www.patreon.com/weepjp">weepjp </a></td>
|
||||
<td><a href="https://www.patreon.com/jonleibowitz">Jon Leibowitz</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=19045173">kiritan </a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=27648259">みなしま </a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=24430516">Eduardo Quiros</a></td>
|
||||
@@ -135,7 +133,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><img src="https://c8.patreon.com/2/200/21285325" alt="Nie(sha) " width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon " width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ " width="100"></td>
|
||||
<td><img src="https://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/36813045/29876ea679d443bcbba3c3f16edab8c2/2.jpeg?token-time=2145916800&token-hash=YCKWnIhrV9rjUCV9KqtJnEqjy_uGYF3WMXftjUdpi7o%3D" alt="Wataru Manji (manji0)" width="100"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/Nesakko">Nesakko</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=776209">Demogrognard</a></td>
|
||||
@@ -146,9 +144,10 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><a href="https://www.patreon.com/user?u=21285325">Nie(sha) </a></td>
|
||||
<td><a href="https://www.patreon.com/osapon">osapon </a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ </a></td>
|
||||
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61 </a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=36813045">Wataru Manji (manji0)</a></td>
|
||||
</tr></table>
|
||||
<table><tr>
|
||||
<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/28779508/3cd4cb7f017f4ee0864341e3464d42f9/1.png?token-time=2145916800&token-hash=eGQtR15be44kgvh8fw2Jx8Db4Bv15YBp2ldxh0EKRxA%3D" alt="S Y" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td>
|
||||
@@ -156,8 +155,8 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/26340354/08834cf767b3449e93098ef73a434e2f/2.png?token-time=2145916800&token-hash=nyM8DnKRL8hR47HQ619mUzsqVRpkWZjgtgBU9RY15Uc%3D" alt="totokoro " width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s " width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5827393/59893c191dda408f9cabd0f20a3a5627/1.jpeg?token-time=2145916800&token-hash=i9N05vOph-eP1LTLb9_npATjYOpntL0ZsHNaZFSsPmE%3D" alt="motcha " width="100"></td>
|
||||
</tr><tr>
|
||||
<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/user?u=28779508">S Y</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=16542964">Takumi Sugita</a></td>
|
||||
@@ -165,50 +164,51 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=26340354">totokoro </a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s </a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=5827393">motcha </a></td>
|
||||
</tr></table>
|
||||
<table><tr>
|
||||
<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/20494440/540beaf2445f408ea6597bc61e077bb3/1.png?token-time=2145916800&token-hash=UJ0JQge64Bx9XmN_qYA1inMQhrWf4U91fqz7VAKJeSg%3D" alt="axtuki1 " 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.jpg?token-time=2145916800&token-hash=nVAntpybQrznE0rg05keLrSE6ogPKJXB13rmrJng42c%3D" alt="takimura " width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13100201/fc5be4fa90444f09a9c8a06f72385272/1.png?token-time=2145916800&token-hash=i8PjlgfOB2LPEdbtWyx8ZPsBKhGcNZqcw_FQmH71UGU%3D" alt="aqz tamaina" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/28295158/cd2451bfb94a449dbf705ef4718cd355/2.jpeg?token-time=2145916800&token-hash=MRv3BxufHPuCyiBSxU5UYmLGvD6YZlhtSFRfMWg2k4U%3D" alt="012 " width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/9109588/e3cffc48d20a4e43afe04123e696781d/3.png?token-time=2145916800&token-hash=T_VIUA0IFIbleZv4pIjiszZGnQonwn34sLCYFIhakBo%3D" alt="nafuchoco " width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%3D" alt="Atsuko Tominaga" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie " width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/26144593/9514b10a5c1b42a3af58621aee213d1d/1.png?token-time=2145916800&token-hash=v1PYRsjzu4c_mndN4Hvi_dlispZJsuGRCQeNS82pUSM%3D" alt="EBISUME" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo " width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG " width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpg?token-time=2145916800&token-hash=7bkMqTwHPRsJPGAq42PYdDXDZBVGLqdgr1ZmBxX8GFQ%3D" alt="Hekovic " width="100"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/user?u=5827393">motcha </a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=20494440">axtuki1 </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/aqz">aqz tamaina</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=28295158">012 </a></td>
|
||||
<td><a href="https://www.patreon.com/nijimiss">nafuchoco </a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=9109588">nafuchoco </a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=4389829">natalie </a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=26144593">EBISUME</a></td>
|
||||
<td><a href="https://www.patreon.com/noellabo">noellabo </a></td>
|
||||
<td><a href="https://www.patreon.com/Corset">CG </a></td>
|
||||
<td><a href="https://www.patreon.com/hekovic">Hekovic </a></td>
|
||||
</tr></table>
|
||||
<table><tr>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpg?token-time=2145916800&token-hash=7bkMqTwHPRsJPGAq42PYdDXDZBVGLqdgr1ZmBxX8GFQ%3D" alt="Hekovic " width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24641572/b4fd175424814f15b0ca9178d2d2d2e4/1.png?token-time=2145916800&token-hash=e2fyqdbuJbpCckHcwux7rbuW6OPkKdERcus0u2wIEWU%3D" alt="uroco @99" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/14661394" alt="Chandler " 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/9481273/7fa89168e72943859c3d3c96e424ed31/4.jpeg?token-time=2145916800&token-hash=5w1QV1qXe-NdWbdFmp1H7O_-QBsSiV0haumk3XTHIEg%3D" alt="Efertone " 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/hekovic">Hekovic </a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=24641572">uroco @99</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=14661394">Chandler </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/efertone">Efertone </a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
|
||||
</tr></table>
|
||||
|
||||
**Last updated:** Tue, 02 Jun 2020 00:00:08 UTC
|
||||
**Last updated:** Tue, 14 Jul 2020 09:00:09 UTC
|
||||
<!-- PATREON_END -->
|
||||
|
||||
[backer-url]: #backers
|
||||
|
@@ -500,6 +500,9 @@ _notification:
|
||||
youGotMessagingMessageFromUser: "لقد تلقيت رسالة مِن {name}"
|
||||
youGotMessagingMessageFromGroup: "لقد أرسِلَت رسالة إلى الفريق {name}"
|
||||
youWereFollowed: "يتابعك"
|
||||
_types:
|
||||
follow: "المتابَعون"
|
||||
quote: "اقتبس"
|
||||
_deck:
|
||||
_columns:
|
||||
notifications: "الإشعارات"
|
||||
|
@@ -104,6 +104,8 @@ unblockConfirm: "Möchtest du diese Blockierung wirklich aufheben?"
|
||||
suspendConfirm: "Möchtest du diesen Benutzer wirklich sperren?"
|
||||
unsuspendConfirm: "Möchtest du die Sperrung dieses Benutzers wirklich aufheben?"
|
||||
selectList: "Wähle eine Liste aus"
|
||||
selectAntenna: "Antenne auswählen"
|
||||
selectWidget: "Widget auswählen"
|
||||
customEmojis: "Benutzerdefinierte Emojis"
|
||||
emoji: "Emoji"
|
||||
emojiName: "Emojiname"
|
||||
@@ -535,9 +537,33 @@ enableAll: "Alle aktivieren"
|
||||
disableAll: "Alle deaktivieren"
|
||||
tokenRequested: "Benutzerkontozugriff gewähren"
|
||||
pluginTokenRequestedDescription: "Dieses Plugin wird die hier konfigurierten Berechtigungen verwenden können."
|
||||
notificationType: "Benachrichtigungstyp"
|
||||
edit: "Bearbeiten"
|
||||
useStarForReactionFallback: "Verwende ★ falls das Reaktions-Emoji unbekannt ist"
|
||||
emailConfig: "Email-Server Konfiguration"
|
||||
enableEmail: "Email-Versand aktivieren"
|
||||
emailConfigInfo: "Zur Email-Bestätigung bei Registrierung und zum Zurücksetzen des Passworts verwendet"
|
||||
email: "Email-Adresse"
|
||||
smtpConfig: "SMTP-Server Konfiguration"
|
||||
smtpHost: "Host"
|
||||
smtpPort: "Port"
|
||||
smtpUser: "Benutzername"
|
||||
smtpPass: "Passwort"
|
||||
emptyToDisableSmtpAuth: "Benutzername und Passwort leer lassen um SMTP-Verifizierung zu deaktivieren"
|
||||
smtpSecure: "Für SMTP-Verbindungen implizit SSL/TLS verwenden"
|
||||
smtpSecureInfo: "Schalte dies aus, falls du STARTTLS verwendest"
|
||||
testEmail: "Email-Versand testen"
|
||||
wordMute: "Wort-Stummschaltung"
|
||||
userSaysSomething: "{name} hat etwas gesagt."
|
||||
makeActive: "Aktivieren"
|
||||
_wordMute:
|
||||
muteWords: "Wort stummschalten"
|
||||
muteWordsDescription: "Mit Leerzeichen für eine \"UND\"-Verknüpfung trennen, durch Zeilenumbrüche für eine \"ODER\"-Verknüpfung trennen."
|
||||
muteWordsDescription2: "Umgib Schlüsselworter mit Schrägstrichen, um Reguläre Ausdrücke zu verwenden."
|
||||
softDescription: "Notizen, die die eingestellten Konditionen erfüllen, in der Chronik ausblenden"
|
||||
hardDescription: "Verhindern, dass Notizen, die die eingestellten Konditionen erfüllen, der Chronik hinzugefügt werden. Zudem werden diese Notizen auch nicht der Chronik hinzugefügt, falls die Konditionen geändert werden."
|
||||
soft: "Leicht"
|
||||
hard: "Schwer"
|
||||
_theme:
|
||||
explore: "Themen erforschen"
|
||||
install: "Thema installieren"
|
||||
@@ -1177,10 +1203,26 @@ _notification:
|
||||
youReceivedFollowRequest: "Du hast eine Follow-Anfrage erhalten"
|
||||
yourFollowRequestAccepted: "Deine Follow-Anfrage wurde akzeptiert"
|
||||
youWereInvitedToGroup: "Du wurdest in eine Gruppe eingeladen"
|
||||
_types:
|
||||
all: "Alle"
|
||||
follow: "Folgt"
|
||||
mention: "Erwähnung"
|
||||
reply: "Antworten"
|
||||
renote: "Renote"
|
||||
quote: "Zitieren"
|
||||
reaction: "Reaktionen"
|
||||
pollVote: "Umfragen"
|
||||
receiveFollowRequest: "Follow-Anfragen"
|
||||
_deck:
|
||||
alwaysShowMainColumn: "Hauptspalte immer zeigen"
|
||||
columnAlign: "Spalten ausrichten"
|
||||
addColumn: "Spalte hinzufügen"
|
||||
swapLeft: "Nach links verschieben"
|
||||
swapRight: "Nach rechts verschieben"
|
||||
swapUp: "Nach oben verschieben"
|
||||
swapDown: "Nach unten verschieben"
|
||||
stackLeft: "Nach links stapeln"
|
||||
popRight: "Nach rechts vom Stapel nehmen"
|
||||
_columns:
|
||||
widgets: "Widgets"
|
||||
notifications: "Benachrichtigungen"
|
||||
|
@@ -104,6 +104,8 @@ unblockConfirm: "Are you sure that you want to unblock this account?"
|
||||
suspendConfirm: "Are you sure that you want to suspend this account?"
|
||||
unsuspendConfirm: "Are you sure you that want to unsuspend this account?"
|
||||
selectList: "Select a list"
|
||||
selectAntenna: "Select an Antenna"
|
||||
selectWidget: "Select a widget"
|
||||
customEmojis: "Custom Emoji"
|
||||
emoji: "Emoji"
|
||||
emojiName: "Emoji name"
|
||||
@@ -535,9 +537,33 @@ enableAll: "Enable all"
|
||||
disableAll: "Disable all"
|
||||
tokenRequested: "Grant access to account"
|
||||
pluginTokenRequestedDescription: "This plugin will be able to use the permissions set here."
|
||||
notificationType: "Notification type"
|
||||
edit: "Edit"
|
||||
useStarForReactionFallback: "Use ★ as fallback if the reaction emoji is unknown"
|
||||
emailConfig: "Email server configuration"
|
||||
enableEmail: "Enable email distribution"
|
||||
emailConfigInfo: "Used to confirm your email during sign-up and if you forget your password"
|
||||
email: "Email Address"
|
||||
smtpConfig: "SMTP Server configuration"
|
||||
smtpHost: "Host"
|
||||
smtpPort: "Port"
|
||||
smtpUser: "Username"
|
||||
smtpPass: "Password"
|
||||
emptyToDisableSmtpAuth: "Leave username and password empty to disable SMTP verification"
|
||||
smtpSecure: "Use implicit SSL/TLS for SMTP connections"
|
||||
smtpSecureInfo: "Turn this off when using STARTTLS"
|
||||
testEmail: "Test email delivery"
|
||||
wordMute: "Word mute"
|
||||
userSaysSomething: "{name} said something"
|
||||
makeActive: "Activate"
|
||||
_wordMute:
|
||||
muteWords: "Word to mute"
|
||||
muteWordsDescription: "Separate with spaces for AND condition. Separate with line breaks for OR."
|
||||
muteWordsDescription2: "Surround keywords by slashes to use regular expressions."
|
||||
softDescription: "Hide notes fulfilling the set conditions from the timeline."
|
||||
hardDescription: "Prevent notes fulfilling the set conditions from being added to the timeline. In addition, these notes will not be added to the timeline even if the conditions are changed."
|
||||
soft: "Soft"
|
||||
hard: "Hard"
|
||||
_theme:
|
||||
explore: "Explore Themes"
|
||||
install: "Install theme"
|
||||
@@ -1177,10 +1203,26 @@ _notification:
|
||||
youReceivedFollowRequest: "You've received a follow request"
|
||||
yourFollowRequestAccepted: "Your follow request was accepted"
|
||||
youWereInvitedToGroup: "Invited to group"
|
||||
_types:
|
||||
all: "All"
|
||||
follow: "Following"
|
||||
mention: "Mention"
|
||||
reply: "Replies"
|
||||
renote: "Renote"
|
||||
quote: "Quote"
|
||||
reaction: "Reaction"
|
||||
pollVote: "Polls"
|
||||
receiveFollowRequest: "Follow requests"
|
||||
_deck:
|
||||
alwaysShowMainColumn: "Always show main column"
|
||||
columnAlign: "Align columns"
|
||||
addColumn: "Add column"
|
||||
swapLeft: "Swap to left"
|
||||
swapRight: "Swap to right"
|
||||
swapUp: "Swap with above"
|
||||
swapDown: "Swap with below"
|
||||
stackLeft: "Stack on the left"
|
||||
popRight: "Pop to the right"
|
||||
_columns:
|
||||
widgets: "Widgets"
|
||||
notifications: "Notifications"
|
||||
|
@@ -1188,6 +1188,12 @@ _notification:
|
||||
youReceivedFollowRequest: "Has mandado una solicitud de seguimiento"
|
||||
yourFollowRequestAccepted: "Tu solicitud de seguimiento fue aceptada"
|
||||
youWereInvitedToGroup: "Invitado al grupo"
|
||||
_types:
|
||||
follow: "Siguiendo"
|
||||
mention: "Menciones"
|
||||
renote: "Renotar"
|
||||
quote: "Citar"
|
||||
reaction: "Reacción"
|
||||
_deck:
|
||||
alwaysShowMainColumn: "Siempre mostrar la columna principal"
|
||||
columnAlign: "Alinear columnas"
|
||||
|
@@ -104,6 +104,8 @@ unblockConfirm: "Êtes-vous sûr·e de vouloir débloquer ce compte ?"
|
||||
suspendConfirm: "Êtes-vous sûr·e de vouloir suspendre ce compte ?"
|
||||
unsuspendConfirm: "Êtes-vous sûr·e de vouloir annuler la suspension de ce compte ?"
|
||||
selectList: "Sélectionner une liste"
|
||||
selectAntenna: "Sélectionner une antenne"
|
||||
selectWidget: "Sélectionner un widget"
|
||||
customEmojis: "Émojis personnalisés"
|
||||
emoji: "Émoji"
|
||||
emojiName: "Nom de l’émoji"
|
||||
@@ -526,6 +528,18 @@ leaveConfirm: "Vous avez des modifications non-sauvegardées. Voulez-vous les ig
|
||||
manage: "Gestion"
|
||||
plugins: "Extensions"
|
||||
pluginInstallWarn: "N’installez que des extensions provenant de sources de confiance."
|
||||
deck: "Deck"
|
||||
undeck: "Quitter le deck"
|
||||
useBlurEffectForModal: "Utiliser un effet de flou pour les modals"
|
||||
generateAccessToken: "Générer un jeton d'accès"
|
||||
permission: "Autorisations "
|
||||
enableAll: "Tout activer"
|
||||
disableAll: "Tout désactiver"
|
||||
tokenRequested: "Autoriser l'accès au compte"
|
||||
pluginTokenRequestedDescription: "Ce plugin pourra utiliser les autorisations définies ici."
|
||||
notificationType: "Type de notifications"
|
||||
edit: "Editer"
|
||||
emailConfig: "Configuration du serveur email"
|
||||
smtpHost: "Hôte"
|
||||
smtpUser: "Nom d’utilisateur·rice"
|
||||
smtpPass: "Mot de passe"
|
||||
@@ -1134,6 +1148,12 @@ _notification:
|
||||
youReceivedFollowRequest: "Vous avez reçu une demande d’abonnement"
|
||||
yourFollowRequestAccepted: "Votre demande d’abonnement a été accepté"
|
||||
youWereInvitedToGroup: "Invité au groupe"
|
||||
_types:
|
||||
follow: "Abonnements"
|
||||
mention: "Mentionner"
|
||||
renote: "Renote"
|
||||
quote: "Citer"
|
||||
reaction: "Réactions"
|
||||
_deck:
|
||||
alwaysShowMainColumn: "Toujours afficher la colonne principale"
|
||||
columnAlign: "Aligner les colonnes"
|
||||
|
@@ -553,6 +553,18 @@ emptyToDisableSmtpAuth: "ユーザー名とパスワードを空欄にするこ
|
||||
smtpSecure: "SMTP 接続に暗黙的なSSL/TLSを使用する"
|
||||
smtpSecureInfo: "STARTTLS使用時はオフにします。"
|
||||
testEmail: "配信テスト"
|
||||
wordMute: "ワードミュート"
|
||||
userSaysSomething: "{name}が何かを言いました"
|
||||
makeActive: "アクティブにする"
|
||||
|
||||
_wordMute:
|
||||
muteWords: "ミュートするワード"
|
||||
muteWordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。"
|
||||
muteWordsDescription2: "キーワードをスラッシュで囲むと正規表現になります。"
|
||||
softDescription: "指定した条件のノートをタイムラインから隠します。"
|
||||
hardDescription: "指定した条件のノートをタイムラインに追加しないようにします。追加されなかったノートは、条件を変更しても除外されたままになります。"
|
||||
soft: "ソフト"
|
||||
hard: "ハード"
|
||||
|
||||
_theme:
|
||||
explore: "テーマを探す"
|
||||
|
@@ -439,6 +439,11 @@ _pages:
|
||||
array: "リスト"
|
||||
_notification:
|
||||
youWereFollowed: "フォローされたで"
|
||||
_types:
|
||||
follow: "フォロー"
|
||||
renote: "Renote"
|
||||
quote: "引用"
|
||||
reaction: "リアクション"
|
||||
_deck:
|
||||
_columns:
|
||||
notifications: "通知"
|
||||
|
@@ -82,6 +82,9 @@ _pages:
|
||||
array: "Tibdarin"
|
||||
_notification:
|
||||
youWereFollowed: "Yeṭṭafaṛ-ik·em-id"
|
||||
_types:
|
||||
follow: "Ig ṭṭafaṛ"
|
||||
mention: "Bder"
|
||||
_deck:
|
||||
_columns:
|
||||
notifications: "Ilɣuyen"
|
||||
|
@@ -1120,6 +1120,12 @@ _notification:
|
||||
youReceivedFollowRequest: "새로운 팔로우 요청이 있습니다"
|
||||
yourFollowRequestAccepted: "팔로우 요청이 수락되었습니다"
|
||||
youWereInvitedToGroup: "그룹에 초대되었습니다"
|
||||
_types:
|
||||
follow: "팔로잉"
|
||||
mention: "멘션"
|
||||
renote: "Renote"
|
||||
quote: "인용"
|
||||
reaction: "리액션"
|
||||
_deck:
|
||||
_columns:
|
||||
widgets: "위젯"
|
||||
|
@@ -86,8 +86,8 @@ you: "您"
|
||||
clickToShow: "点击以显示"
|
||||
sensitive: "阅读注意"
|
||||
add: "添加"
|
||||
reaction: "反应"
|
||||
reactionSettingDescription: "选择您想要固定在反应选择器中的反应。"
|
||||
reaction: "回应"
|
||||
reactionSettingDescription: "选择您想要置顶的回应。"
|
||||
rememberNoteVisibility: "记录公开范围"
|
||||
attachCancel: "删除附件"
|
||||
markAsSensitive: "阅读注意"
|
||||
@@ -104,6 +104,7 @@ unblockConfirm: "确定要解除屏蔽吗?"
|
||||
suspendConfirm: "要冻结吗?"
|
||||
unsuspendConfirm: "要解除冻结吗?"
|
||||
selectList: "选择列表"
|
||||
selectWidget: "选择小工具"
|
||||
customEmojis: "自定义Emoji"
|
||||
emoji: "表情符号"
|
||||
emojiName: "Emoji 名称"
|
||||
@@ -364,7 +365,7 @@ resetPassword: "重置密码"
|
||||
newPasswordIs: "新的密码是「{password}」"
|
||||
autoReloadWhenDisconnected: "断开连接时自动重新加载"
|
||||
autoNoteWatch: "自动关注帖子"
|
||||
autoNoteWatchDescription: "让您能够收到关于「反应」和回复其他用户的帖子的通知。"
|
||||
autoNoteWatchDescription: "让您能够收到关于「回应」和回复其他用户的帖子的通知。"
|
||||
reduceUiAnimation: "减少UI动画"
|
||||
share: "分享"
|
||||
notFound: "未找到"
|
||||
@@ -535,10 +536,22 @@ enableAll: "启用全部"
|
||||
disableAll: "禁用全部"
|
||||
tokenRequested: "允许访问账户"
|
||||
pluginTokenRequestedDescription: "此插件将能够拥有此处设置的权限"
|
||||
notificationType: "通知类型"
|
||||
edit: "编辑"
|
||||
useStarForReactionFallback: "如果回应的颜文字未知,则使用★作为代替"
|
||||
emailConfig: "邮件服务器设置"
|
||||
enableEmail: "启用发送邮件功能"
|
||||
emailConfigInfo: "用于确认电子邮件和密码重置"
|
||||
email: "邮件地址"
|
||||
smtpConfig: "SMTP服务器设置"
|
||||
smtpHost: "主机名"
|
||||
smtpPort: "端口"
|
||||
smtpUser: "用户名"
|
||||
smtpPass: "密码"
|
||||
emptyToDisableSmtpAuth: "用户名和密码留空可以禁用SMTP验证"
|
||||
smtpSecure: "在 SMTP 连接中使用隐式 SSL / TLS"
|
||||
smtpSecureInfo: "使用STARTTLS时关闭。"
|
||||
testEmail: "邮件发送测试"
|
||||
_theme:
|
||||
explore: "寻找主题"
|
||||
install: "安装主题"
|
||||
@@ -651,8 +664,8 @@ _tutorial:
|
||||
step5_3: "要关注其他用户,请单击他的头像,然后在他的个人资料上按下“关注”按钮。"
|
||||
step5_4: "如果用户的名称旁边有锁定图标,则该用户需要手动批准您的关注请求。"
|
||||
step6_1: "现在,您将可以在时间线上看到其他用户的帖子。"
|
||||
step6_2: "您还可以在其他人的帖子上进行「反应」,以快速做出简单回复。"
|
||||
step6_3: "在他人的贴子上按下「+」图标,即可选择想要的表情来进行「反应」。"
|
||||
step6_2: "您还可以在其他人的帖子上进行「回应」,以快速做出简单回复。"
|
||||
step6_3: "在他人的贴子上按下「+」图标,即可选择想要的表情来进行「回应」。"
|
||||
step7_1: "对Misskey基本操作的简单介绍,到此结束了。 辛苦了!"
|
||||
step7_2: "如果你想了解更多有关Misskey的信息,请参见{help}。"
|
||||
step7_3: "接下来,享受Misskey带来的乐趣吧🚀"
|
||||
@@ -1178,12 +1191,18 @@ _notification:
|
||||
youReceivedFollowRequest: "您有新的关注请求"
|
||||
yourFollowRequestAccepted: "您的关注请求已通过"
|
||||
youWereInvitedToGroup: "您有新的群组邀请"
|
||||
_types:
|
||||
follow: "关注中"
|
||||
mention: "提及"
|
||||
renote: "转发"
|
||||
quote: "引用"
|
||||
reaction: "回应"
|
||||
_deck:
|
||||
alwaysShowMainColumn: "总是显示主列"
|
||||
columnAlign: "列对齐"
|
||||
addColumn: "添加列"
|
||||
_columns:
|
||||
widgets: "小部件"
|
||||
widgets: "小工具"
|
||||
notifications: "通知"
|
||||
tl: "时间线"
|
||||
antenna: "天线"
|
||||
|
@@ -673,6 +673,12 @@ _notification:
|
||||
youGotPoll: "{name}已投票"
|
||||
youWereFollowed: "您有新的追隨者"
|
||||
yourFollowRequestAccepted: "您的追隨請求已通過"
|
||||
_types:
|
||||
follow: "追隨中"
|
||||
mention: "提及"
|
||||
renote: "轉發貼文"
|
||||
quote: "引用"
|
||||
reaction: "反應"
|
||||
_deck:
|
||||
_columns:
|
||||
notifications: "通知"
|
||||
|
30
migration/1595771249699-word-mute.ts
Normal file
30
migration/1595771249699-word-mute.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class wordMute1595771249699 implements MigrationInterface {
|
||||
name = 'wordMute1595771249699'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "muted_note" ("id" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, CONSTRAINT "PK_897e2eff1c0b9b64e55ca1418a4" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_70ab9786313d78e4201d81cdb8" ON "muted_note" ("noteId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_d8e07aa18c2d64e86201601aec" ON "muted_note" ("userId") `);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a8c6bfd637d3f1d67a27c48e27" ON "muted_note" ("noteId", "userId") `);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ADD "enableWordMute" boolean NOT NULL DEFAULT false`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ADD "mutedWords" jsonb NOT NULL DEFAULT '[]'`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_3befe6f999c86aff06eb0257b4" ON "user_profile" ("enableWordMute") `);
|
||||
await queryRunner.query(`ALTER TABLE "muted_note" ADD CONSTRAINT "FK_70ab9786313d78e4201d81cdb89" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "muted_note" ADD CONSTRAINT "FK_d8e07aa18c2d64e86201601aec1" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "muted_note" DROP CONSTRAINT "FK_d8e07aa18c2d64e86201601aec1"`);
|
||||
await queryRunner.query(`ALTER TABLE "muted_note" DROP CONSTRAINT "FK_70ab9786313d78e4201d81cdb89"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_3befe6f999c86aff06eb0257b4"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "mutedWords"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "enableWordMute"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_a8c6bfd637d3f1d67a27c48e27"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_d8e07aa18c2d64e86201601aec"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_70ab9786313d78e4201d81cdb8"`);
|
||||
await queryRunner.query(`DROP TABLE "muted_note"`);
|
||||
}
|
||||
|
||||
}
|
18
migration/1595782306083-word-mute2.ts
Normal file
18
migration/1595782306083-word-mute2.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class wordMute21595782306083 implements MigrationInterface {
|
||||
name = 'wordMute21595782306083'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TYPE "muted_note_reason_enum" AS ENUM('word', 'manual', 'spam', 'other')`);
|
||||
await queryRunner.query(`ALTER TABLE "muted_note" ADD "reason" "muted_note_reason_enum" NOT NULL`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_636e977ff90b23676fb5624b25" ON "muted_note" ("reason") `);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "IDX_636e977ff90b23676fb5624b25"`);
|
||||
await queryRunner.query(`ALTER TABLE "muted_note" DROP COLUMN "reason"`);
|
||||
await queryRunner.query(`DROP TYPE "muted_note_reason_enum"`);
|
||||
}
|
||||
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <syuilotan@yahoo.co.jp>",
|
||||
"version": "12.43.0",
|
||||
"version": "12.45.0",
|
||||
"codename": "indigo",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -47,7 +47,7 @@
|
||||
"@koa/multer": "3.0.0",
|
||||
"@koa/router": "9.0.1",
|
||||
"@sinonjs/fake-timers": "6.0.1",
|
||||
"@syuilo/aiscript": "0.8.0",
|
||||
"@syuilo/aiscript": "0.10.0",
|
||||
"@types/bcryptjs": "2.4.2",
|
||||
"@types/bull": "3.14.0",
|
||||
"@types/cbor": "5.0.0",
|
||||
@@ -204,6 +204,7 @@
|
||||
"random-seed": "0.3.0",
|
||||
"randomcolor": "0.5.4",
|
||||
"ratelimiter": "3.4.1",
|
||||
"re2": "1.15.4",
|
||||
"recaptcha-promise": "0.1.3",
|
||||
"reconnecting-websocket": "4.4.0",
|
||||
"redis": "3.0.2",
|
||||
|
@@ -5,10 +5,22 @@
|
||||
</template>
|
||||
<div class="xkpnjxcv">
|
||||
<label v-for="item in Object.keys(form).filter(item => !form[item].hidden)" :key="item">
|
||||
<mk-input v-if="form[item].type === 'number'" v-model="values[item]" type="number" :step="form[item].step || 1"><span v-text="form[item].label || item"></span></mk-input>
|
||||
<mk-input v-else-if="form[item].type === 'string' && !item.multiline" v-model="values[item]" type="text"><span v-text="form[item].label || item"></span></mk-input>
|
||||
<mk-textarea v-else-if="form[item].type === 'string' && item.multiline" v-model="values[item]"><span v-text="form[item].label || item"></span></mk-textarea>
|
||||
<mk-switch v-else-if="form[item].type === 'boolean'" v-model="values[item]"><span v-text="form[item].label || item"></span></mk-switch>
|
||||
<mk-input v-if="form[item].type === 'number'" v-model="values[item]" type="number" :step="form[item].step || 1">
|
||||
<span v-text="form[item].label || item"></span>
|
||||
<template v-if="form[item].description" #desc>{{ form[item].description }}</template>
|
||||
</mk-input>
|
||||
<mk-input v-else-if="form[item].type === 'string' && !item.multiline" v-model="values[item]" type="text">
|
||||
<span v-text="form[item].label || item"></span>
|
||||
<template v-if="form[item].description" #desc>{{ form[item].description }}</template>
|
||||
</mk-input>
|
||||
<mk-textarea v-else-if="form[item].type === 'string' && item.multiline" v-model="values[item]">
|
||||
<span v-text="form[item].label || item"></span>
|
||||
<template v-if="form[item].description" #desc>{{ form[item].description }}</template>
|
||||
</mk-textarea>
|
||||
<mk-switch v-else-if="form[item].type === 'boolean'" v-model="values[item]">
|
||||
<span v-text="form[item].label || item"></span>
|
||||
<template v-if="form[item].description" #desc>{{ form[item].description }}</template>
|
||||
</mk-switch>
|
||||
</label>
|
||||
</div>
|
||||
</x-window>
|
||||
@@ -48,7 +60,7 @@ export default Vue.extend({
|
||||
|
||||
created() {
|
||||
for (const item in this.form) {
|
||||
Vue.set(this.values, item, this.form[item].default || null);
|
||||
Vue.set(this.values, item, this.form[item].hasOwnProperty('default') ? this.form[item].default : null);
|
||||
}
|
||||
},
|
||||
|
||||
|
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<div
|
||||
class="note _panel"
|
||||
v-show="!isDeleted && !hideThisNote"
|
||||
v-if="!muted"
|
||||
v-show="!isDeleted"
|
||||
:tabindex="!isDeleted ? '-1' : null"
|
||||
:class="{ renote: isRenote }"
|
||||
v-hotkey="keymap"
|
||||
@@ -34,19 +35,19 @@
|
||||
</div>
|
||||
</div>
|
||||
<article class="article">
|
||||
<mk-avatar class="avatar" :user="appearNote.user" v-once/>
|
||||
<mk-avatar class="avatar" :user="appearNote.user"/>
|
||||
<div class="main">
|
||||
<x-note-header class="header" :note="appearNote" :mini="true"/>
|
||||
<div class="body" v-if="appearNote.deletedAt == null" ref="noteBody">
|
||||
<div class="body" ref="noteBody">
|
||||
<p v-if="appearNote.cw != null" class="cw">
|
||||
<mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis" v-once/>
|
||||
<mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis"/>
|
||||
<x-cw-button v-model="showContent" :note="appearNote"/>
|
||||
</p>
|
||||
<div class="content" v-show="appearNote.cw == null || showContent">
|
||||
<div class="text">
|
||||
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ $t('private') }})</span>
|
||||
<router-link class="reply" v-if="appearNote.replyId" :to="`/notes/${appearNote.replyId}`"><fa :icon="faReply"/></router-link>
|
||||
<mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis" v-once/>
|
||||
<mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis"/>
|
||||
<a class="rp" v-if="appearNote.renote != null">RN:</a>
|
||||
</div>
|
||||
<div class="files" v-if="appearNote.files.length > 0">
|
||||
@@ -57,7 +58,7 @@
|
||||
<div class="renote" v-if="appearNote.renote"><x-note-preview :note="appearNote.renote"/></div>
|
||||
</div>
|
||||
</div>
|
||||
<footer v-if="appearNote.deletedAt == null" class="footer">
|
||||
<footer class="footer">
|
||||
<x-reactions-viewer :note="appearNote" ref="reactionsViewer"/>
|
||||
<button @click="reply()" class="button _button">
|
||||
<template v-if="appearNote.reply"><fa :icon="faReplyAll"/></template>
|
||||
@@ -80,11 +81,17 @@
|
||||
<fa :icon="faEllipsisH"/>
|
||||
</button>
|
||||
</footer>
|
||||
<div class="deleted" v-if="appearNote.deletedAt != null">{{ $t('deleted') }}</div>
|
||||
</div>
|
||||
</article>
|
||||
<x-sub v-for="note in replies" :key="note.id" :note="note" class="reply" :detail="true"/>
|
||||
</div>
|
||||
<div v-else class="_panel muted" @click="muted = false">
|
||||
<i18n path="userSaysSomething" tag="small">
|
||||
<router-link class="name" :to="appearNote.user | userPage" v-user-preview="appearNote.userId" place="name">
|
||||
<mk-user-name :user="appearNote.user"/>
|
||||
</router-link>
|
||||
</i18n>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -106,8 +113,15 @@ import pleaseLogin from '../scripts/please-login';
|
||||
import { focusPrev, focusNext } from '../scripts/focus';
|
||||
import { url } from '../config';
|
||||
import copyToClipboard from '../scripts/copy-to-clipboard';
|
||||
import { checkWordMute } from '../scripts/check-word-mute';
|
||||
import { utils } from '@syuilo/aiscript';
|
||||
|
||||
export default Vue.extend({
|
||||
model: {
|
||||
prop: 'note',
|
||||
event: 'updated'
|
||||
},
|
||||
|
||||
components: {
|
||||
XSub,
|
||||
XNoteHeader,
|
||||
@@ -142,7 +156,8 @@ export default Vue.extend({
|
||||
conversation: [],
|
||||
replies: [],
|
||||
showContent: false,
|
||||
hideThisNote: false,
|
||||
isDeleted: false,
|
||||
muted: false,
|
||||
noteBody: this.$refs.noteBody,
|
||||
faEdit, faBolt, faTimes, faBullhorn, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faBiohazard, faPlug
|
||||
};
|
||||
@@ -186,10 +201,6 @@ export default Vue.extend({
|
||||
return this.isRenote ? this.note.renote : this.note;
|
||||
},
|
||||
|
||||
isDeleted(): boolean {
|
||||
return this.appearNote.deletedAt != null || this.note.deletedAt != null;
|
||||
},
|
||||
|
||||
isMyNote(): boolean {
|
||||
return this.$store.getters.isSignedIn && (this.$store.state.i.id === this.appearNote.userId);
|
||||
},
|
||||
@@ -231,11 +242,22 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
async created() {
|
||||
if (this.$store.getters.isSignedIn) {
|
||||
this.connection = this.$root.stream;
|
||||
}
|
||||
|
||||
// plugin
|
||||
if (this.$store.state.noteViewInterruptors.length > 0) {
|
||||
let result = this.note;
|
||||
for (const interruptor of this.$store.state.noteViewInterruptors) {
|
||||
result = utils.valToJs(await interruptor.handler(JSON.parse(JSON.stringify(result))));
|
||||
}
|
||||
this.$emit('updated', Object.freeze(result));
|
||||
}
|
||||
|
||||
this.muted = await checkWordMute(this.appearNote, this.$store.state.i, this.$store.state.settings.mutedWords);
|
||||
|
||||
if (this.detail) {
|
||||
this.$root.api('notes/children', {
|
||||
noteId: this.appearNote.id,
|
||||
@@ -261,7 +283,7 @@ export default Vue.extend({
|
||||
this.connection.on('_connected_', this.onStreamConnected);
|
||||
}
|
||||
|
||||
this.noteBody = this.$refs.noteBody
|
||||
this.noteBody = this.$refs.noteBody;
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
@@ -273,11 +295,24 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateAppearNote(v) {
|
||||
this.$emit('updated', Object.freeze(this.isRenote ? {
|
||||
...this.note,
|
||||
renote: {
|
||||
...this.note.renote,
|
||||
...v
|
||||
}
|
||||
} : {
|
||||
...this.note,
|
||||
...v
|
||||
}));
|
||||
},
|
||||
|
||||
readPromo() {
|
||||
(this as any).$root.api('promo/read', {
|
||||
noteId: this.appearNote.id
|
||||
});
|
||||
this.hideThisNote = true;
|
||||
this.isDeleted = true;
|
||||
},
|
||||
|
||||
capture(withHandler = false) {
|
||||
@@ -309,67 +344,88 @@ export default Vue.extend({
|
||||
case 'reacted': {
|
||||
const reaction = body.reaction;
|
||||
|
||||
// DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので)
|
||||
let n = {
|
||||
...this.appearNote,
|
||||
};
|
||||
|
||||
if (body.emoji) {
|
||||
const emojis = this.appearNote.emojis || [];
|
||||
if (!emojis.includes(body.emoji)) {
|
||||
emojis.push(body.emoji);
|
||||
Vue.set(this.appearNote, 'emojis', emojis);
|
||||
n.emojis = [...emojis, body.emoji];
|
||||
}
|
||||
}
|
||||
|
||||
if (this.appearNote.reactions == null) {
|
||||
Vue.set(this.appearNote, 'reactions', {});
|
||||
}
|
||||
|
||||
if (this.appearNote.reactions[reaction] == null) {
|
||||
Vue.set(this.appearNote.reactions, reaction, 0);
|
||||
}
|
||||
// TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる
|
||||
const currentCount = (this.appearNote.reactions || {})[reaction] || 0;
|
||||
|
||||
// Increment the count
|
||||
this.appearNote.reactions[reaction]++;
|
||||
n.reactions = {
|
||||
...this.appearNote.reactions,
|
||||
[reaction]: currentCount + 1
|
||||
};
|
||||
|
||||
if (body.userId == this.$store.state.i.id) {
|
||||
Vue.set(this.appearNote, 'myReaction', reaction);
|
||||
if (body.userId === this.$store.state.i.id) {
|
||||
n.myReaction = reaction;
|
||||
}
|
||||
|
||||
this.updateAppearNote(n);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'unreacted': {
|
||||
const reaction = body.reaction;
|
||||
|
||||
if (this.appearNote.reactions == null) {
|
||||
return;
|
||||
}
|
||||
// DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので)
|
||||
let n = {
|
||||
...this.appearNote,
|
||||
};
|
||||
|
||||
if (this.appearNote.reactions[reaction] == null) {
|
||||
return;
|
||||
}
|
||||
// TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる
|
||||
const currentCount = (this.appearNote.reactions || {})[reaction] || 0;
|
||||
|
||||
// Decrement the count
|
||||
if (this.appearNote.reactions[reaction] > 0) this.appearNote.reactions[reaction]--;
|
||||
n.reactions = {
|
||||
...this.appearNote.reactions,
|
||||
[reaction]: Math.max(0, currentCount - 1)
|
||||
};
|
||||
|
||||
if (body.userId == this.$store.state.i.id) {
|
||||
Vue.set(this.appearNote, 'myReaction', null);
|
||||
if (body.userId === this.$store.state.i.id) {
|
||||
n.myReaction = null;
|
||||
}
|
||||
|
||||
this.updateAppearNote(n);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'pollVoted': {
|
||||
const choice = body.choice;
|
||||
this.appearNote.poll.choices[choice].votes++;
|
||||
if (body.userId == this.$store.state.i.id) {
|
||||
Vue.set(this.appearNote.poll.choices[choice], 'isVoted', true);
|
||||
}
|
||||
|
||||
// DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので)
|
||||
let n = {
|
||||
...this.appearNote,
|
||||
};
|
||||
|
||||
n.poll = {
|
||||
...this.appearNote.poll,
|
||||
choices: {
|
||||
...this.appearNote.poll.choices,
|
||||
[choice]: {
|
||||
...this.appearNote.poll.choices[choice],
|
||||
votes: this.appearNote.poll.choices[choice].votes + 1,
|
||||
...(body.userId === this.$store.state.i.id ? {
|
||||
isVoted: true
|
||||
} : {})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.updateAppearNote(n);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'deleted': {
|
||||
Vue.set(this.appearNote, 'deletedAt', body.deletedAt);
|
||||
Vue.set(this.appearNote, 'renote', null);
|
||||
this.appearNote.text = null;
|
||||
this.appearNote.fileIds = [];
|
||||
this.appearNote.poll = null;
|
||||
this.appearNote.cw = null;
|
||||
this.isDeleted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -638,7 +694,7 @@ export default Vue.extend({
|
||||
this.$root.api('notes/delete', {
|
||||
noteId: this.note.id
|
||||
});
|
||||
Vue.set(this.note, 'deletedAt', new Date());
|
||||
this.isDeleted = true;
|
||||
}
|
||||
}],
|
||||
source: this.$refs.renoteTime,
|
||||
@@ -925,10 +981,6 @@ export default Vue.extend({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .deleted {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -995,4 +1047,10 @@ export default Vue.extend({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.muted {
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
|
@@ -15,7 +15,7 @@
|
||||
</div>
|
||||
|
||||
<x-list ref="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed">
|
||||
<x-note :note="note" :detail="detail" :key="note._featuredId_ || note._prId_ || note.id"/>
|
||||
<x-note :note="note" @updated="updated(note, $event)" :detail="detail" :key="note._featuredId_ || note._prId_ || note.id"/>
|
||||
</x-list>
|
||||
|
||||
<div v-show="more && !reversed" style="margin-top: var(--margin);">
|
||||
@@ -62,14 +62,15 @@ export default Vue.extend({
|
||||
default: false
|
||||
},
|
||||
|
||||
extract: {
|
||||
prop: {
|
||||
type: String,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
notes(): any[] {
|
||||
return this.extract ? this.extract(this.items) : this.items;
|
||||
return this.prop ? this.items.map(item => item[this.prop]) : this.items;
|
||||
},
|
||||
|
||||
reversed(): boolean {
|
||||
@@ -78,6 +79,15 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
methods: {
|
||||
updated(oldValue, newValue) {
|
||||
const i = this.notes.findIndex(n => n === oldValue);
|
||||
if (this.prop) {
|
||||
Vue.set(this.items[i], this.prop, newValue);
|
||||
} else {
|
||||
Vue.set(this.items, i, newValue);
|
||||
}
|
||||
},
|
||||
|
||||
focus() {
|
||||
this.$refs.notes.focus();
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="mfcuwfyp">
|
||||
<x-list class="notifications" :items="items" v-slot="{ item: notification }">
|
||||
<x-note v-if="['reply', 'quote', 'mention'].includes(notification.type)" :note="notification.note" :key="notification.id"/>
|
||||
<x-note v-if="['reply', 'quote', 'mention'].includes(notification.type)" :note="notification.note" @updated="noteUpdated(notification.note, $event)" :key="notification.id"/>
|
||||
<x-notification v-else :notification="notification" :with-time="true" :full="true" class="_panel notification" :key="notification.id"/>
|
||||
</x-list>
|
||||
|
||||
@@ -75,11 +75,20 @@ export default Vue.extend({
|
||||
this.$root.stream.send('readNotification', {
|
||||
id: notification.id
|
||||
});
|
||||
|
||||
notification.isRead = true;
|
||||
}
|
||||
|
||||
this.prepend(notification);
|
||||
this.prepend({
|
||||
...notification,
|
||||
isRead: document.visibilityState === 'visible'
|
||||
});
|
||||
},
|
||||
|
||||
noteUpdated(oldValue, newValue) {
|
||||
const i = this.items.findIndex(n => n.note === oldValue);
|
||||
Vue.set(this.items, i, {
|
||||
...this.items[i],
|
||||
note: newValue
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
|
@@ -69,6 +69,7 @@ import getAcct from '../../misc/acct/render';
|
||||
import { formatTimeString } from '../../misc/format-time-string';
|
||||
import { selectDriveFile } from '../scripts/select-drive-file';
|
||||
import { noteVisibilities } from '../../types';
|
||||
import { utils } from '@syuilo/aiscript';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@@ -533,9 +534,8 @@ export default Vue.extend({
|
||||
localStorage.setItem('drafts', JSON.stringify(data));
|
||||
},
|
||||
|
||||
post() {
|
||||
this.posting = true;
|
||||
this.$root.api('notes/create', {
|
||||
async post() {
|
||||
let data = {
|
||||
text: this.text == '' ? undefined : this.text,
|
||||
fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
|
||||
replyId: this.reply ? this.reply.id : undefined,
|
||||
@@ -546,7 +546,17 @@ export default Vue.extend({
|
||||
visibility: this.visibility,
|
||||
visibleUserIds: this.visibility == 'specified' ? this.visibleUsers.map(u => u.id) : undefined,
|
||||
viaMobile: this.$root.isMobile
|
||||
}).then(data => {
|
||||
};
|
||||
|
||||
// plugin
|
||||
if (this.$store.state.notePostInterruptors.length > 0) {
|
||||
for (const interruptor of this.$store.state.notePostInterruptors) {
|
||||
data = utils.valToJs(await interruptor.handler(JSON.parse(JSON.stringify(data))));
|
||||
}
|
||||
}
|
||||
|
||||
this.posting = true;
|
||||
this.$root.api('notes/create', data).then(() => {
|
||||
this.clear();
|
||||
this.deleteDraft();
|
||||
this.$emit('posted');
|
||||
|
46
src/client/components/tab.vue
Normal file
46
src/client/components/tab.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div class="pxhvhrfw" v-size="[{ max: 500 }]">
|
||||
<button v-for="item in items" class="_button" @click="$emit('input', item.value)" :class="{ active: value === item.value }" :key="item.value"><fa v-if="item.icon" :icon="item.icon" class="icon"/>{{ item.label }}</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pxhvhrfw {
|
||||
display: flex;
|
||||
|
||||
> button {
|
||||
flex: 1;
|
||||
padding: 11px 8px 8px 8px;
|
||||
border-bottom: solid 3px transparent;
|
||||
|
||||
&.active {
|
||||
color: var(--accent);
|
||||
border-bottom-color: var(--accent);
|
||||
}
|
||||
|
||||
> .icon {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&.max-width_500px {
|
||||
font-size: 80%;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -52,8 +52,7 @@ export default Vue.extend({
|
||||
});
|
||||
|
||||
const prepend = note => {
|
||||
const _note = JSON.parse(JSON.stringify(note)); // deepcopy
|
||||
(this.$refs.tl as any).prepend(_note);
|
||||
(this.$refs.tl as any).prepend(note);
|
||||
|
||||
this.$emit('note');
|
||||
|
||||
|
@@ -242,7 +242,7 @@ os.init(async () => {
|
||||
//store.commit('instance/set', );
|
||||
});
|
||||
|
||||
for (const plugin of store.state.deviceUser.plugins) {
|
||||
for (const plugin of store.state.deviceUser.plugins.filter(p => p.active)) {
|
||||
console.info('Plugin installed:', plugin.name, 'v' + plugin.version);
|
||||
|
||||
const aiscript = new AiScript(createPluginEnv(app, {
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<portal to="icon"><fa :icon="faStar"/></portal>
|
||||
<portal to="title">{{ $t('favorites') }}</portal>
|
||||
<x-notes :pagination="pagination" :detail="true" :extract="items => items.map(item => item.note)" @before="before()" @after="after()"/>
|
||||
<x-notes :pagination="pagination" :detail="true" :prop="'note'" @before="before()" @after="after()"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@@ -436,7 +436,7 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
onStatsLog(statsLog) {
|
||||
for (const stats of statsLog.reverse()) {
|
||||
for (const stats of [...statsLog].reverse()) {
|
||||
this.onStats(stats);
|
||||
}
|
||||
}
|
||||
|
@@ -169,7 +169,7 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
onStatsLog(statsLog) {
|
||||
for (const stats of statsLog.reverse()) {
|
||||
for (const stats of [...statsLog].reverse()) {
|
||||
this.onStats(stats);
|
||||
}
|
||||
},
|
||||
|
@@ -27,6 +27,7 @@
|
||||
<x-import-export/>
|
||||
<x-drive/>
|
||||
<x-mute-block/>
|
||||
<x-word-mute/>
|
||||
<x-security/>
|
||||
<x-2fa/>
|
||||
<x-integration/>
|
||||
@@ -47,6 +48,7 @@ import XImportExport from './import-export.vue';
|
||||
import XDrive from './drive.vue';
|
||||
import XReactionSetting from './reaction.vue';
|
||||
import XMuteBlock from './mute-block.vue';
|
||||
import XWordMute from './word-mute.vue';
|
||||
import XSecurity from './security.vue';
|
||||
import X2fa from './2fa.vue';
|
||||
import XIntegration from './integration.vue';
|
||||
@@ -68,6 +70,7 @@ export default Vue.extend({
|
||||
XDrive,
|
||||
XReactionSetting,
|
||||
XMuteBlock,
|
||||
XWordMute,
|
||||
XSecurity,
|
||||
X2fa,
|
||||
XIntegration,
|
||||
|
77
src/client/pages/my-settings/word-mute.vue
Normal file
77
src/client/pages/my-settings/word-mute.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<section class="_card">
|
||||
<div class="_title"><fa :icon="faCommentSlash"/> {{ $t('wordMute') }}</div>
|
||||
<div class="_content _noPad">
|
||||
<mk-tab v-model="tab" :items="[{ label: $t('_wordMute.soft'), value: 'soft' }, { label: $t('_wordMute.hard'), value: 'hard' }]"/>
|
||||
</div>
|
||||
<div class="_content" v-show="tab === 'soft'">
|
||||
<mk-info>{{ $t('_wordMute.softDescription') }}</mk-info>
|
||||
<mk-textarea v-model="softMutedWords">
|
||||
<span>{{ $t('_wordMute.muteWords') }}</span>
|
||||
<template #desc>{{ $t('_wordMute.muteWordsDescription') }}<br>{{ $t('_wordMute.muteWordsDescription2') }}</template>
|
||||
</mk-textarea>
|
||||
</div>
|
||||
<div class="_content" v-show="tab === 'hard'">
|
||||
<mk-info>{{ $t('_wordMute.hardDescription') }}</mk-info>
|
||||
<mk-textarea v-model="hardMutedWords">
|
||||
<span>{{ $t('_wordMute.muteWords') }}</span>
|
||||
<template #desc>{{ $t('_wordMute.muteWordsDescription') }}<br>{{ $t('_wordMute.muteWordsDescription2') }}</template>
|
||||
</mk-textarea>
|
||||
</div>
|
||||
<div class="_footer">
|
||||
<mk-button @click="save()" primary inline :disabled="!changed"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faCommentSlash, faSave } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkButton from '../../components/ui/button.vue';
|
||||
import MkTextarea from '../../components/ui/textarea.vue';
|
||||
import MkTab from '../../components/tab.vue';
|
||||
import MkInfo from '../../components/ui/info.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
MkButton,
|
||||
MkTextarea,
|
||||
MkTab,
|
||||
MkInfo,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
tab: 'soft',
|
||||
softMutedWords: '',
|
||||
hardMutedWords: '',
|
||||
changed: false,
|
||||
faCommentSlash, faSave,
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
softMutedWords() {
|
||||
this.changed = true;
|
||||
},
|
||||
hardMutedWords() {
|
||||
this.changed = true;
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.softMutedWords = this.$store.state.settings.mutedWords.map(x => x.join(' ')).join('\n');
|
||||
this.hardMutedWords = this.$store.state.i.mutedWords.map(x => x.join(' ')).join('\n');
|
||||
},
|
||||
|
||||
methods: {
|
||||
async save() {
|
||||
this.$store.dispatch('settings/set', { key: 'mutedWords', value: this.softMutedWords.trim().split('\n').map(x => x.trim().split(' ')) });
|
||||
await this.$root.api('i/update', {
|
||||
mutedWords: this.hardMutedWords.trim().split('\n').map(x => x.trim().split(' ')),
|
||||
});
|
||||
this.changed = false;
|
||||
},
|
||||
}
|
||||
});
|
||||
</script>
|
@@ -14,7 +14,7 @@
|
||||
<hr v-if="showNext"/>
|
||||
|
||||
<mk-remote-caution v-if="note.user.host != null" :href="note.url || note.uri" style="margin-bottom: var(--margin)"/>
|
||||
<x-note :note="note" :key="note.id" :detail="true"/>
|
||||
<x-note v-model="note" :key="note.id" :detail="true"/>
|
||||
|
||||
<button class="_panel _button" v-if="hasPrev && !showPrev" @click="showPrev = true" style="margin: var(--margin) auto 0 auto;"><fa :icon="faChevronDown"/></button>
|
||||
<hr v-if="showPrev"/>
|
||||
|
@@ -3,24 +3,20 @@
|
||||
<portal to="icon"><fa :icon="faStickyNote"/></portal>
|
||||
<portal to="title">{{ $t('pages') }}</portal>
|
||||
|
||||
<mk-container :body-togglable="true">
|
||||
<template #header><fa :icon="faEdit" fixed-width/>{{ $t('_pages.my') }}</template>
|
||||
<div class="rknalgpo my">
|
||||
<mk-button class="new" @click="create()"><fa :icon="faPlus"/></mk-button>
|
||||
<mk-pagination :pagination="myPagesPagination" #default="{items}">
|
||||
<mk-page-preview v-for="page in items" class="ckltabjg" :page="page" :key="page.id"/>
|
||||
</mk-pagination>
|
||||
</div>
|
||||
</mk-container>
|
||||
<mk-tab v-model="tab" :items="[{ label: $t('_pages.my'), value: 'my', icon: faEdit }, { label: $t('_pages.liked'), value: 'liked', icon: faHeart }]"/>
|
||||
|
||||
<mk-container :body-togglable="true">
|
||||
<template #header><fa :icon="faHeart" fixed-width/>{{ $t('_pages.liked') }}</template>
|
||||
<div class="rknalgpo">
|
||||
<mk-pagination :pagination="likedPagesPagination" #default="{items}">
|
||||
<mk-page-preview v-for="like in items" class="ckltabjg" :page="like.page" :key="like.page.id"/>
|
||||
</mk-pagination>
|
||||
</div>
|
||||
</mk-container>
|
||||
<div class="rknalgpo my" v-if="tab === 'my'">
|
||||
<mk-button class="new" @click="create()"><fa :icon="faPlus"/></mk-button>
|
||||
<mk-pagination :pagination="myPagesPagination" #default="{items}">
|
||||
<mk-page-preview v-for="page in items" class="ckltabjg" :page="page" :key="page.id"/>
|
||||
</mk-pagination>
|
||||
</div>
|
||||
|
||||
<div class="rknalgpo" v-if="tab === 'liked'">
|
||||
<mk-pagination :pagination="likedPagesPagination" #default="{items}">
|
||||
<mk-page-preview v-for="like in items" class="ckltabjg" :page="like.page" :key="like.page.id"/>
|
||||
</mk-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -31,14 +27,15 @@ import { faStickyNote, faHeart } from '@fortawesome/free-regular-svg-icons';
|
||||
import MkPagePreview from '../components/page-preview.vue';
|
||||
import MkPagination from '../components/ui/pagination.vue';
|
||||
import MkButton from '../components/ui/button.vue';
|
||||
import MkContainer from '../components/ui/container.vue';
|
||||
import MkTab from '../components/tab.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
MkPagePreview, MkPagination, MkButton, MkContainer
|
||||
MkPagePreview, MkPagination, MkButton, MkTab
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tab: 'my',
|
||||
myPagesPagination: {
|
||||
endpoint: 'i/pages',
|
||||
limit: 5,
|
||||
|
@@ -18,6 +18,9 @@
|
||||
<option v-for="x in $store.state.deviceUser.plugins" :value="x.id" :key="x.id">{{ x.name }}</option>
|
||||
</mk-select>
|
||||
<template v-if="selectedPlugin">
|
||||
<div style="margin: -8px 0 8px 0;">
|
||||
<mk-switch :value="selectedPlugin.active" @change="changeActive(selectedPlugin, $event)">{{ $t('makeActive') }}</mk-switch>
|
||||
</div>
|
||||
<div class="_keyValue">
|
||||
<div>{{ $t('version') }}:</div>
|
||||
<div>{{ selectedPlugin.version }}</div>
|
||||
@@ -44,11 +47,13 @@
|
||||
import Vue from 'vue';
|
||||
import { AiScript, parse } from '@syuilo/aiscript';
|
||||
import { serialize } from '@syuilo/aiscript/built/serializer';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { faPlug, faSave, faTrashAlt, faFolderOpen, faDownload, faCog } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkButton from '../../components/ui/button.vue';
|
||||
import MkTextarea from '../../components/ui/textarea.vue';
|
||||
import MkSelect from '../../components/ui/select.vue';
|
||||
import MkInfo from '../../components/ui/info.vue';
|
||||
import MkSwitch from '../../components/ui/switch.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@@ -56,6 +61,7 @@ export default Vue.extend({
|
||||
MkTextarea,
|
||||
MkSelect,
|
||||
MkInfo,
|
||||
MkSwitch,
|
||||
},
|
||||
|
||||
data() {
|
||||
@@ -101,8 +107,8 @@ export default Vue.extend({
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { id, name, version, author, description, permissions, config } = data;
|
||||
if (id == null || name == null || version == null || author == null) {
|
||||
const { name, version, author, description, permissions, config } = data;
|
||||
if (name == null || version == null || author == null) {
|
||||
this.$root.dialog({
|
||||
type: 'error',
|
||||
text: 'Required property not found :('
|
||||
@@ -128,8 +134,9 @@ export default Vue.extend({
|
||||
});
|
||||
|
||||
this.$store.commit('deviceUser/installPlugin', {
|
||||
id: uuid(),
|
||||
meta: {
|
||||
id, name, version, author, description, permissions, config
|
||||
name, version, author, description, permissions, config
|
||||
},
|
||||
token,
|
||||
ast: serialize(ast)
|
||||
@@ -171,6 +178,17 @@ export default Vue.extend({
|
||||
config: result
|
||||
});
|
||||
|
||||
this.$nextTick(() => {
|
||||
location.reload();
|
||||
});
|
||||
},
|
||||
|
||||
changeActive(plugin, active) {
|
||||
this.$store.commit('deviceUser/changePluginActive', {
|
||||
id: plugin.id,
|
||||
active: active
|
||||
});
|
||||
|
||||
this.$nextTick(() => {
|
||||
location.reload();
|
||||
});
|
||||
|
@@ -83,7 +83,7 @@
|
||||
<router-view :user="user"></router-view>
|
||||
<template v-if="$route.name == 'user'">
|
||||
<div class="pins">
|
||||
<x-note v-for="note in user.pinnedNotes" class="note" :note="note" :key="note.id" :detail="true" :pinned="true"/>
|
||||
<x-note v-for="note in user.pinnedNotes" class="note" :note="note" @updated="pinnedNoteUpdated(note, $event)" :key="note.id" :detail="true" :pinned="true"/>
|
||||
</div>
|
||||
<mk-container :body-togglable="true" class="content">
|
||||
<template #header><fa :icon="faImage"/>{{ $t('images') }}</template>
|
||||
@@ -210,6 +210,11 @@ export default Vue.extend({
|
||||
const pos = -(top / z);
|
||||
banner.style.backgroundPosition = `center calc(50% - ${pos}px)`;
|
||||
},
|
||||
|
||||
pinnedNoteUpdated(oldValue, newValue) {
|
||||
const i = this.user.pinnedNotes.findIndex(n => n === oldValue);
|
||||
Vue.set(this.user.pinnedNotes, i, newValue);
|
||||
},
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -14,9 +14,9 @@ export function createAiScriptEnv(vm, opts) {
|
||||
text: text.value,
|
||||
});
|
||||
}),
|
||||
'Mk:confirm': values.FN_NATIVE(async ([title, text]) => {
|
||||
'Mk:confirm': values.FN_NATIVE(async ([title, text, type]) => {
|
||||
const confirm = await vm.$root.dialog({
|
||||
type: 'warning',
|
||||
type: type ? type.value : 'question',
|
||||
showCancelButton: true,
|
||||
title: title.value,
|
||||
text: text.value,
|
||||
@@ -44,12 +44,13 @@ export function createAiScriptEnv(vm, opts) {
|
||||
|
||||
export function createPluginEnv(vm, opts) {
|
||||
const config = new Map();
|
||||
for (const [k, v] of Object.entries(opts.plugin.config)) {
|
||||
for (const [k, v] of Object.entries(opts.plugin.config || {})) {
|
||||
config.set(k, jsToVal(opts.plugin.configData[k] || v.default));
|
||||
}
|
||||
|
||||
return {
|
||||
...createAiScriptEnv(vm, { ...opts, token: opts.plugin.token }),
|
||||
//#region Deprecated
|
||||
'Mk:register_post_form_action': values.FN_NATIVE(([title, handler]) => {
|
||||
vm.$store.commit('registerPostFormAction', { pluginId: opts.plugin.id, title: title.value, handler });
|
||||
}),
|
||||
@@ -59,6 +60,25 @@ export function createPluginEnv(vm, opts) {
|
||||
'Mk:register_note_action': values.FN_NATIVE(([title, handler]) => {
|
||||
vm.$store.commit('registerNoteAction', { pluginId: opts.plugin.id, title: title.value, handler });
|
||||
}),
|
||||
//#endregion
|
||||
'Plugin:register_post_form_action': values.FN_NATIVE(([title, handler]) => {
|
||||
vm.$store.commit('registerPostFormAction', { pluginId: opts.plugin.id, title: title.value, handler });
|
||||
}),
|
||||
'Plugin:register_user_action': values.FN_NATIVE(([title, handler]) => {
|
||||
vm.$store.commit('registerUserAction', { pluginId: opts.plugin.id, title: title.value, handler });
|
||||
}),
|
||||
'Plugin:register_note_action': values.FN_NATIVE(([title, handler]) => {
|
||||
vm.$store.commit('registerNoteAction', { pluginId: opts.plugin.id, title: title.value, handler });
|
||||
}),
|
||||
'Plugin:register_note_view_interruptor': values.FN_NATIVE(([handler]) => {
|
||||
vm.$store.commit('registerNoteViewInterruptor', { pluginId: opts.plugin.id, handler });
|
||||
}),
|
||||
'Plugin:register_note_post_interruptor': values.FN_NATIVE(([handler]) => {
|
||||
vm.$store.commit('registerNotePostInterruptor', { pluginId: opts.plugin.id, handler });
|
||||
}),
|
||||
'Plugin:open_url': values.FN_NATIVE(([url]) => {
|
||||
window.open(url.value, '_blank');
|
||||
}),
|
||||
'Plugin:config': values.OBJ(config),
|
||||
};
|
||||
}
|
||||
|
26
src/client/scripts/check-word-mute.ts
Normal file
26
src/client/scripts/check-word-mute.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export async function checkWordMute(note: Record<string, any>, me: Record<string, any> | null | undefined, mutedWords: string[][]): Promise<boolean> {
|
||||
// 自分自身
|
||||
if (me && (note.userId === me.id)) return false;
|
||||
|
||||
const words = mutedWords
|
||||
// Clean up
|
||||
.map(xs => xs.filter(x => x !== ''))
|
||||
.filter(xs => xs.length > 0);
|
||||
|
||||
if (words.length > 0) {
|
||||
if (note.text == null) return false;
|
||||
|
||||
const matched = words.some(and =>
|
||||
and.every(keyword => {
|
||||
const regexp = keyword.match(/^\/(.+)\/(.*)$/);
|
||||
if (regexp) {
|
||||
return new RegExp(regexp[1], regexp[2]).test(note.text!);
|
||||
}
|
||||
return note.text!.includes(keyword);
|
||||
}));
|
||||
|
||||
if (matched) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
@@ -74,10 +74,6 @@ export default (opts) => ({
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateItem(i, item) {
|
||||
Vue.set((this as any).items, i, item);
|
||||
},
|
||||
|
||||
reload() {
|
||||
this.items = [];
|
||||
this.init();
|
||||
@@ -94,6 +90,9 @@ export default (opts) => ({
|
||||
...params,
|
||||
limit: this.pagination.noPaging ? (this.pagination.limit || 10) : (this.pagination.limit || 10) + 1,
|
||||
}).then(items => {
|
||||
for (const item of items) {
|
||||
Object.freeze(item);
|
||||
}
|
||||
if (!this.pagination.noPaging && (items.length > (this.pagination.limit || 10))) {
|
||||
items.pop();
|
||||
this.items = this.pagination.reversed ? [...items].reverse() : items;
|
||||
@@ -130,6 +129,9 @@ export default (opts) => ({
|
||||
untilId: this.items[this.items.length - 1].id,
|
||||
}),
|
||||
}).then(items => {
|
||||
for (const item of items) {
|
||||
Object.freeze(item);
|
||||
}
|
||||
if (items.length > SECOND_FETCH_LIMIT) {
|
||||
items.pop();
|
||||
this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items);
|
||||
|
@@ -112,10 +112,10 @@ export default class Stream extends EventEmitter {
|
||||
}
|
||||
|
||||
for (const c of connections.filter(c => c != null)) {
|
||||
c.emit(body.type, body.body);
|
||||
c.emit(body.type, Object.freeze(body.body));
|
||||
}
|
||||
} else {
|
||||
this.emit(type, body);
|
||||
this.emit(type, Object.freeze(body));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -18,6 +18,7 @@ export const defaultSettings = {
|
||||
pastedFileName: 'yyyy-MM-dd HH-mm-ss [{{number}}]',
|
||||
memo: null,
|
||||
reactions: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
|
||||
mutedWords: [],
|
||||
};
|
||||
|
||||
export const defaultDeviceUserSettings = {
|
||||
@@ -44,7 +45,14 @@ export const defaultDeviceUserSettings = {
|
||||
columns: [],
|
||||
layout: [],
|
||||
},
|
||||
plugins: [],
|
||||
plugins: [] as {
|
||||
id: string;
|
||||
name: string;
|
||||
active: boolean;
|
||||
configData: Record<string, any>;
|
||||
token: string;
|
||||
ast: any[];
|
||||
}[],
|
||||
};
|
||||
|
||||
export const defaultDeviceSettings = {
|
||||
@@ -103,6 +111,8 @@ export default () => new Vuex.Store({
|
||||
postFormActions: [],
|
||||
userActions: [],
|
||||
noteActions: [],
|
||||
noteViewInterruptors: [],
|
||||
notePostInterruptors: [],
|
||||
},
|
||||
|
||||
getters: {
|
||||
@@ -266,6 +276,22 @@ export default () => new Vuex.Store({
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
registerNoteViewInterruptor(state, { pluginId, handler }) {
|
||||
state.noteViewInterruptors.push({
|
||||
handler: (note) => {
|
||||
return state.pluginContexts.get(pluginId).execFn(handler, [utils.jsToVal(note)]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
registerNotePostInterruptor(state, { pluginId, handler }) {
|
||||
state.notePostInterruptors.push({
|
||||
handler: (note) => {
|
||||
return state.pluginContexts.get(pluginId).execFn(handler, [utils.jsToVal(note)]);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
@@ -587,9 +613,11 @@ export default () => new Vuex.Store({
|
||||
},
|
||||
//#endregion
|
||||
|
||||
installPlugin(state, { meta, ast, token }) {
|
||||
installPlugin(state, { id, meta, ast, token }) {
|
||||
state.plugins.push({
|
||||
...meta,
|
||||
id,
|
||||
active: true,
|
||||
configData: {},
|
||||
token: token,
|
||||
ast: ast
|
||||
@@ -603,6 +631,10 @@ export default () => new Vuex.Store({
|
||||
configPlugin(state, { id, config }) {
|
||||
state.plugins.find(p => p.id === id).configData = config;
|
||||
},
|
||||
|
||||
changePluginActive(state, { id, active }) {
|
||||
state.plugins.find(p => p.id === id).active = active;
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
|
@@ -355,6 +355,10 @@ hr {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
&._noPad {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
& + ._content {
|
||||
border-top: solid 1px var(--divider);
|
||||
}
|
||||
|
@@ -59,6 +59,7 @@ import { PromoNote } from '../models/entities/promo-note';
|
||||
import { PromoRead } from '../models/entities/promo-read';
|
||||
import { program } from '../argv';
|
||||
import { Relay } from '../models/entities/relay';
|
||||
import { MutedNote } from '../models/entities/muted-note';
|
||||
|
||||
const sqlLogger = dbLogger.createSubLogger('sql', 'white', false);
|
||||
|
||||
@@ -151,6 +152,7 @@ export const entities = [
|
||||
ReversiGame,
|
||||
ReversiMatching,
|
||||
Relay,
|
||||
MutedNote,
|
||||
...charts as any
|
||||
];
|
||||
|
||||
|
90
src/docs/create-plugin.ja-JP.md
Normal file
90
src/docs/create-plugin.ja-JP.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# プラグインの作成
|
||||
Misskey Webクライアントのプラグイン機能を使うと、クライアントを拡張し、様々な機能を追加できます。
|
||||
ここではプラグインの作成にあたってのメタデータ定義や、AiScript APIリファレンスを掲載します。
|
||||
|
||||
## メタデータ
|
||||
プラグインは、AiScriptのメタデータ埋め込み機能を使って、デフォルトとしてプラグインのメタデータを定義する必要があります。
|
||||
メタデータは次のプロパティを含むオブジェクトです。
|
||||
|
||||
### mame
|
||||
プラグイン名
|
||||
|
||||
### author
|
||||
プラグイン作者
|
||||
|
||||
### version
|
||||
プラグインバージョン。数値を指定してください。
|
||||
|
||||
### description
|
||||
プラグインの説明
|
||||
|
||||
### permissions
|
||||
プラグインが要求する権限。MisskeyAPIにリクエストする際に用いられます。
|
||||
|
||||
### config
|
||||
プラグインの設定情報を表すオブジェクト。
|
||||
キーに設定名、値に以下のプロパティを含めます。
|
||||
|
||||
#### type
|
||||
設定値の種類を表す文字列。以下から選択します。
|
||||
string number boolean
|
||||
|
||||
#### label
|
||||
ユーザーに表示する設定名
|
||||
|
||||
#### description
|
||||
設定の説明
|
||||
|
||||
#### default
|
||||
設定のデフォルト値
|
||||
|
||||
## APIリファレンス
|
||||
AiScript標準で組み込まれているAPIは掲載しません。
|
||||
|
||||
### Mk:dialog(title text type)
|
||||
ダイアログを表示します。typeには以下の値が設定できます。
|
||||
info success warn error question
|
||||
省略すると info になります。
|
||||
|
||||
### Mk:confirm(title text type)
|
||||
確認ダイアログを表示します。typeには以下の値が設定できます。
|
||||
info success warn error question
|
||||
省略すると question になります。
|
||||
ユーザーが"OK"を選択した場合は true を、"キャンセル"を選択した場合は false が返ります。
|
||||
|
||||
### Mk:api(endpoint params)
|
||||
Misskey APIにリクエストします。第一引数にエンドポイント名、第二引数にパラメータオブジェクトを渡します。
|
||||
|
||||
### Mk:save(key value)
|
||||
任意の値に任意の名前を付けて永続化します。永続化した値は、AiScriptコンテキストが終了しても残り、Mk:loadで読み取ることができます。
|
||||
|
||||
### Mk:load(key)
|
||||
Mk:saveで永続化した指定の名前の値を読み取ります。
|
||||
|
||||
### Plugin:register_post_form_action(title fn)
|
||||
投稿フォームにアクションを追加します。第一引数にアクション名、第二引数にアクションが選択された際のコールバック関数を渡します。
|
||||
コールバック関数には、第一引数に投稿フォームオブジェクトが渡されます。
|
||||
|
||||
### Plugin:register_note_action(title fn)
|
||||
ノートメニューに項目を追加します。第一引数に項目名、第二引数に項目が選択された際のコールバック関数を渡します。
|
||||
コールバック関数には、第一引数に対象のノートオブジェクトが渡されます。
|
||||
|
||||
### Plugin:register_user_action(title fn)
|
||||
ユーザーメニューに項目を追加します。第一引数に項目名、第二引数に項目が選択された際のコールバック関数を渡します。
|
||||
コールバック関数には、第一引数に対象のユーザーオブジェクトが渡されます。
|
||||
|
||||
### Plugin:register_note_view_interruptor(fn)
|
||||
UIに表示されるノート情報を書き換えます。
|
||||
コールバック関数には、第一引数に対象のノートオブジェクトが渡されます。
|
||||
コールバック関数の返り値でノートが書き換えられます。
|
||||
|
||||
### Plugin:register_note_post_interruptor(fn)
|
||||
ノート投稿時にノート情報を書き換えます。
|
||||
コールバック関数には、第一引数に対象のノートオブジェクトが渡されます。
|
||||
コールバック関数の返り値でノートが書き換えられます。
|
||||
|
||||
### Plugin:open_url(url)
|
||||
第一引数に渡されたURLをブラウザの新しいタブで開きます。
|
||||
|
||||
### Plugin:config
|
||||
プラグインの設定が格納されるオブジェクト。プラグイン定義のconfigで設定したキーで値が入ります。
|
39
src/misc/check-word-mute.ts
Normal file
39
src/misc/check-word-mute.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
const RE2 = require('re2');
|
||||
import { Note } from '../models/entities/note';
|
||||
import { User } from '../models/entities/user';
|
||||
|
||||
type NoteLike = {
|
||||
userId: Note['userId'];
|
||||
text: Note['text'];
|
||||
};
|
||||
|
||||
type UserLike = {
|
||||
id: User['id'];
|
||||
};
|
||||
|
||||
export async function checkWordMute(note: NoteLike, me: UserLike | null | undefined, mutedWords: string[][]): Promise<boolean> {
|
||||
// 自分自身
|
||||
if (me && (note.userId === me.id)) return false;
|
||||
|
||||
const words = mutedWords
|
||||
// Clean up
|
||||
.map(xs => xs.filter(x => x !== ''))
|
||||
.filter(xs => xs.length > 0);
|
||||
|
||||
if (words.length > 0) {
|
||||
if (note.text == null) return false;
|
||||
|
||||
const matched = words.some(and =>
|
||||
and.every(keyword => {
|
||||
const regexp = keyword.match(/^\/(.+)\/(.*)$/);
|
||||
if (regexp) {
|
||||
return new RE2(regexp[1], regexp[2]).test(note.text!);
|
||||
}
|
||||
return note.text!.includes(keyword);
|
||||
}));
|
||||
|
||||
if (matched) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
export default function(note: any, mutedUserIds: string[]): boolean {
|
||||
export function isMutedUserRelated(note: any, mutedUserIds: string[]): boolean {
|
||||
if (mutedUserIds.includes(note.userId)) {
|
||||
return true;
|
||||
}
|
48
src/models/entities/muted-note.ts
Normal file
48
src/models/entities/muted-note.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Entity, Index, JoinColumn, Column, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||
import { Note } from './note';
|
||||
import { User } from './user';
|
||||
import { id } from '../id';
|
||||
import { mutedNoteReasons } from '../../types';
|
||||
|
||||
@Entity()
|
||||
@Index(['noteId', 'userId'], { unique: true })
|
||||
export class MutedNote {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The note ID.'
|
||||
})
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public note: Note | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The user ID.'
|
||||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
/**
|
||||
* ミュートされた理由。
|
||||
*/
|
||||
@Index()
|
||||
@Column('enum', {
|
||||
enum: mutedNoteReasons,
|
||||
comment: 'The reason of the MutedNote.'
|
||||
})
|
||||
public reason: typeof mutedNoteReasons[number];
|
||||
}
|
@@ -147,6 +147,17 @@ export class UserProfile {
|
||||
})
|
||||
public integrations: Record<string, any>;
|
||||
|
||||
@Index()
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public enableWordMute: boolean;
|
||||
|
||||
@Column('jsonb', {
|
||||
default: []
|
||||
})
|
||||
public mutedWords: string[][];
|
||||
|
||||
//#region Denormalized fields
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
|
@@ -53,6 +53,7 @@ import { PromoNote } from './entities/promo-note';
|
||||
import { PromoRead } from './entities/promo-read';
|
||||
import { EmojiRepository } from './repositories/emoji';
|
||||
import { RelayRepository } from './repositories/relay';
|
||||
import { MutedNote } from './entities/muted-note';
|
||||
|
||||
export const Announcements = getRepository(Announcement);
|
||||
export const AnnouncementReads = getRepository(AnnouncementRead);
|
||||
@@ -108,3 +109,4 @@ export const AntennaNotes = getRepository(AntennaNote);
|
||||
export const PromoNotes = getRepository(PromoNote);
|
||||
export const PromoReads = getRepository(PromoRead);
|
||||
export const Relays = getCustomRepository(RelayRepository);
|
||||
export const MutedNotes = getRepository(MutedNote);
|
||||
|
@@ -239,6 +239,7 @@ export class UserRepository extends Repository<User> {
|
||||
hasUnreadNotification: this.getHasUnreadNotification(user.id),
|
||||
hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
|
||||
integrations: profile!.integrations,
|
||||
mutedWords: profile!.mutedWords,
|
||||
} : {}),
|
||||
|
||||
...(opts.includeSecrets ? {
|
||||
|
13
src/server/api/common/generate-muted-note-query.ts
Normal file
13
src/server/api/common/generate-muted-note-query.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { User } from '../../../models/entities/user';
|
||||
import { MutedNotes } from '../../../models';
|
||||
import { SelectQueryBuilder } from 'typeorm';
|
||||
|
||||
export function generateMutedNoteQuery(q: SelectQueryBuilder<any>, me: User) {
|
||||
const mutedQuery = MutedNotes.createQueryBuilder('muted')
|
||||
.select('muted.noteId')
|
||||
.where('muted.userId = :userId', { userId: me.id });
|
||||
|
||||
q.andWhere(`note.id NOT IN (${ mutedQuery.getQuery() })`);
|
||||
|
||||
q.setParameters(mutedQuery.getParameters());
|
||||
}
|
@@ -2,7 +2,7 @@ import { User } from '../../../models/entities/user';
|
||||
import { Mutings } from '../../../models';
|
||||
import { SelectQueryBuilder, Brackets } from 'typeorm';
|
||||
|
||||
export function generateMuteQuery(q: SelectQueryBuilder<any>, me: User, exclude?: User) {
|
||||
export function generateMutedUserQuery(q: SelectQueryBuilder<any>, me: User, exclude?: User) {
|
||||
const mutingQuery = Mutings.createQueryBuilder('muting')
|
||||
.select('muting.muteeId')
|
||||
.where('muting.muterId = :muterId', { muterId: me.id });
|
||||
@@ -28,7 +28,7 @@ export function generateMuteQuery(q: SelectQueryBuilder<any>, me: User, exclude?
|
||||
q.setParameters(mutingQuery.getParameters());
|
||||
}
|
||||
|
||||
export function generateMuteQueryForUsers(q: SelectQueryBuilder<any>, me: User) {
|
||||
export function generateMutedUserQueryForUsers(q: SelectQueryBuilder<any>, me: User) {
|
||||
const mutingQuery = Mutings.createQueryBuilder('muting')
|
||||
.select('muting.muteeId')
|
||||
.where('muting.muterId = :muterId', { muterId: me.id });
|
@@ -2,7 +2,7 @@ import rndstr from 'rndstr';
|
||||
import { Note } from '../../../models/entities/note';
|
||||
import { User } from '../../../models/entities/user';
|
||||
import { Notes, UserProfiles, NoteReactions } from '../../../models';
|
||||
import { generateMuteQuery } from './generate-mute-query';
|
||||
import { generateMutedUserQuery } from './generate-muted-user-query';
|
||||
import { ensure } from '../../../prelude/ensure';
|
||||
|
||||
// TODO: リアクション、Renote、返信などをしたノートは除外する
|
||||
@@ -29,7 +29,7 @@ export async function injectFeatured(timeline: Note[], user?: User | null) {
|
||||
if (user) {
|
||||
query.andWhere('note.userId != :userId', { userId: user.id });
|
||||
|
||||
generateMuteQuery(query, user);
|
||||
generateMutedUserQuery(query, user);
|
||||
|
||||
const reactionQuery = NoteReactions.createQueryBuilder('reaction')
|
||||
.select('reaction.noteId')
|
||||
|
@@ -4,7 +4,7 @@ import define from '../../define';
|
||||
import { Antennas, Notes, AntennaNotes } from '../../../../models';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
||||
import { ApiError } from '../../error';
|
||||
|
||||
export const meta = {
|
||||
@@ -62,7 +62,7 @@ export default define(meta, async (ps, user) => {
|
||||
.setParameters(antennaQuery.getParameters());
|
||||
|
||||
generateVisibilityQuery(query, user);
|
||||
generateMuteQuery(query, user);
|
||||
generateMutedUserQuery(query, user);
|
||||
|
||||
const notes = await query
|
||||
.take(ps.limit!)
|
||||
|
@@ -4,7 +4,7 @@ import define from '../../define';
|
||||
import { Clips, Notes } from '../../../../models';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
||||
|
||||
export const meta = {
|
||||
tags: ['account', 'notes', 'clips'],
|
||||
@@ -57,7 +57,7 @@ export default define(meta, async (ps, user) => {
|
||||
.setParameters(clipQuery.getParameters());
|
||||
|
||||
generateVisibilityQuery(query, user);
|
||||
generateMuteQuery(query, user);
|
||||
generateMutedUserQuery(query, user);
|
||||
|
||||
const notes = await query
|
||||
.take(ps.limit!)
|
||||
|
@@ -142,7 +142,11 @@ export const meta = {
|
||||
desc: {
|
||||
'ja-JP': 'ピン留めするページID'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mutedWords: {
|
||||
validator: $.optional.arr($.arr($.str))
|
||||
},
|
||||
},
|
||||
|
||||
errors: {
|
||||
@@ -193,6 +197,10 @@ export default define(meta, async (ps, user, token) => {
|
||||
if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
|
||||
if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId;
|
||||
if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId;
|
||||
if (ps.mutedWords !== undefined) {
|
||||
profileUpdates.mutedWords = ps.mutedWords;
|
||||
profileUpdates.enableWordMute = ps.mutedWords.length > 0;
|
||||
}
|
||||
if (typeof ps.isLocked === 'boolean') updates.isLocked = ps.isLocked;
|
||||
if (typeof ps.isBot === 'boolean') updates.isBot = ps.isBot;
|
||||
if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot;
|
||||
|
@@ -3,7 +3,7 @@ import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
||||
import { Brackets } from 'typeorm';
|
||||
import { Notes } from '../../../../models';
|
||||
|
||||
@@ -67,7 +67,7 @@ export default define(meta, async (ps, user) => {
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
|
||||
generateVisibilityQuery(query, user);
|
||||
if (user) generateMuteQuery(query, user);
|
||||
if (user) generateMutedUserQuery(query, user);
|
||||
|
||||
const notes = await query.take(ps.limit!).getMany();
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import $ from 'cafy';
|
||||
import define from '../../define';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
||||
import { Notes } from '../../../../models';
|
||||
|
||||
export const meta = {
|
||||
@@ -51,7 +51,7 @@ export default define(meta, async (ps, user) => {
|
||||
.andWhere(`note.visibility = 'public'`)
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
|
||||
if (user) generateMuteQuery(query, user);
|
||||
if (user) generateMutedUserQuery(query, user);
|
||||
|
||||
let notes = await query
|
||||
.orderBy('note.score', 'DESC')
|
||||
|
@@ -5,11 +5,12 @@ import { fetchMeta } from '../../../../misc/fetch-meta';
|
||||
import { ApiError } from '../../error';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { Notes } from '../../../../models';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
||||
import { activeUsersChart } from '../../../../services/chart';
|
||||
import { generateRepliesQuery } from '../../common/generate-replies-query';
|
||||
import { injectPromo } from '../../common/inject-promo';
|
||||
import { injectFeatured } from '../../common/inject-featured';
|
||||
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -82,7 +83,8 @@ export default define(meta, async (ps, user) => {
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
|
||||
generateRepliesQuery(query, user);
|
||||
if (user) generateMuteQuery(query, user);
|
||||
if (user) generateMutedUserQuery(query, user);
|
||||
if (user) generateMutedNoteQuery(query, user);
|
||||
|
||||
if (ps.withFiles) {
|
||||
query.andWhere('note.fileIds != \'{}\'');
|
||||
|
@@ -7,11 +7,12 @@ import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { Followings, Notes } from '../../../../models';
|
||||
import { Brackets } from 'typeorm';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
||||
import { activeUsersChart } from '../../../../services/chart';
|
||||
import { generateRepliesQuery } from '../../common/generate-replies-query';
|
||||
import { injectPromo } from '../../common/inject-promo';
|
||||
import { injectFeatured } from '../../common/inject-featured';
|
||||
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -132,7 +133,8 @@ export default define(meta, async (ps, user) => {
|
||||
|
||||
generateRepliesQuery(query, user);
|
||||
generateVisibilityQuery(query, user);
|
||||
generateMuteQuery(query, user);
|
||||
generateMutedUserQuery(query, user);
|
||||
generateMutedNoteQuery(query, user);
|
||||
|
||||
if (ps.includeMyRenotes === false) {
|
||||
query.andWhere(new Brackets(qb => {
|
||||
|
@@ -4,7 +4,7 @@ import define from '../../define';
|
||||
import { fetchMeta } from '../../../../misc/fetch-meta';
|
||||
import { ApiError } from '../../error';
|
||||
import { Notes } from '../../../../models';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { activeUsersChart } from '../../../../services/chart';
|
||||
@@ -12,6 +12,7 @@ import { Brackets } from 'typeorm';
|
||||
import { generateRepliesQuery } from '../../common/generate-replies-query';
|
||||
import { injectPromo } from '../../common/inject-promo';
|
||||
import { injectFeatured } from '../../common/inject-featured';
|
||||
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -100,7 +101,8 @@ export default define(meta, async (ps, user) => {
|
||||
|
||||
generateRepliesQuery(query, user);
|
||||
generateVisibilityQuery(query, user);
|
||||
if (user) generateMuteQuery(query, user);
|
||||
if (user) generateMutedUserQuery(query, user);
|
||||
if (user) generateMutedNoteQuery(query, user);
|
||||
|
||||
if (ps.withFiles) {
|
||||
query.andWhere('note.fileIds != \'{}\'');
|
||||
|
@@ -4,7 +4,7 @@ import define from '../../define';
|
||||
import read from '../../../../services/note/read';
|
||||
import { Notes, Followings } from '../../../../models';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { Brackets } from 'typeorm';
|
||||
|
||||
@@ -66,7 +66,7 @@ export default define(meta, async (ps, user) => {
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
|
||||
generateVisibilityQuery(query, user);
|
||||
generateMuteQuery(query, user);
|
||||
generateMutedUserQuery(query, user);
|
||||
|
||||
if (ps.visibility) {
|
||||
query.andWhere('note.visibility = :visibility', { visibility: ps.visibility });
|
||||
|
@@ -4,7 +4,7 @@ import define from '../../define';
|
||||
import { getNote } from '../../common/getters';
|
||||
import { ApiError } from '../../error';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { Notes } from '../../../../models';
|
||||
|
||||
@@ -71,7 +71,7 @@ export default define(meta, async (ps, user) => {
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
|
||||
generateVisibilityQuery(query, user);
|
||||
if (user) generateMuteQuery(query, user);
|
||||
if (user) generateMutedUserQuery(query, user);
|
||||
|
||||
const renotes = await query.take(ps.limit!).getMany();
|
||||
|
||||
|
@@ -4,7 +4,7 @@ import define from '../../define';
|
||||
import { Notes } from '../../../../models';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -62,7 +62,7 @@ export default define(meta, async (ps, user) => {
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
|
||||
generateVisibilityQuery(query, user);
|
||||
if (user) generateMuteQuery(query, user);
|
||||
if (user) generateMutedUserQuery(query, user);
|
||||
|
||||
const timeline = await query.take(ps.limit!).getMany();
|
||||
|
||||
|
@@ -3,7 +3,7 @@ import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { Notes } from '../../../../models';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { Brackets } from 'typeorm';
|
||||
import { safeForSql } from '../../../../misc/safe-for-sql';
|
||||
@@ -97,7 +97,7 @@ export default define(meta, async (ps, me) => {
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
|
||||
generateVisibilityQuery(query, me);
|
||||
if (me) generateMuteQuery(query, me);
|
||||
if (me) generateMutedUserQuery(query, me);
|
||||
|
||||
if (ps.tag) {
|
||||
if (!safeForSql(ps.tag)) return;
|
||||
|
@@ -7,7 +7,7 @@ import { ID } from '../../../../misc/cafy-id';
|
||||
import config from '../../../../config';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -69,7 +69,7 @@ export default define(meta, async (ps, me) => {
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
|
||||
generateVisibilityQuery(query, me);
|
||||
if (me) generateMuteQuery(query, me);
|
||||
if (me) generateMutedUserQuery(query, me);
|
||||
|
||||
const notes = await query.take(ps.limit!).getMany();
|
||||
|
||||
|
@@ -4,12 +4,13 @@ import define from '../../define';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { Notes, Followings } from '../../../../models';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
||||
import { activeUsersChart } from '../../../../services/chart';
|
||||
import { Brackets } from 'typeorm';
|
||||
import { generateRepliesQuery } from '../../common/generate-replies-query';
|
||||
import { injectPromo } from '../../common/inject-promo';
|
||||
import { injectFeatured } from '../../common/inject-featured';
|
||||
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -125,7 +126,8 @@ export default define(meta, async (ps, user) => {
|
||||
|
||||
generateRepliesQuery(query, user);
|
||||
generateVisibilityQuery(query, user);
|
||||
generateMuteQuery(query, user);
|
||||
generateMutedUserQuery(query, user);
|
||||
generateMutedNoteQuery(query, user);
|
||||
|
||||
if (ps.includeMyRenotes === false) {
|
||||
query.andWhere(new Brackets(qb => {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import $ from 'cafy';
|
||||
import define from '../define';
|
||||
import { Users } from '../../../models';
|
||||
import { generateMuteQueryForUsers } from '../common/generate-mute-query';
|
||||
import { generateMutedUserQueryForUsers } from '../common/generate-muted-user-query';
|
||||
|
||||
export const meta = {
|
||||
tags: ['users'],
|
||||
@@ -87,7 +87,7 @@ export default define(meta, async (ps, me) => {
|
||||
default: query.orderBy('user.id', 'ASC'); break;
|
||||
}
|
||||
|
||||
if (me) generateMuteQueryForUsers(query, me);
|
||||
if (me) generateMutedUserQueryForUsers(query, me);
|
||||
|
||||
query.take(ps.limit!);
|
||||
query.skip(ps.offset);
|
||||
|
@@ -6,7 +6,7 @@ import { getUser } from '../../common/getters';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { Notes } from '../../../../models';
|
||||
import { generateMuteQuery } from '../../common/generate-mute-query';
|
||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
|
||||
import { Brackets } from 'typeorm';
|
||||
|
||||
export const meta = {
|
||||
@@ -134,7 +134,7 @@ export default define(meta, async (ps, me) => {
|
||||
.leftJoinAndSelect('note.user', 'user');
|
||||
|
||||
generateVisibilityQuery(query, me);
|
||||
if (me) generateMuteQuery(query, me, user);
|
||||
if (me) generateMutedUserQuery(query, me, user);
|
||||
|
||||
if (ps.withFiles) {
|
||||
query.andWhere('note.fileIds != \'{}\'');
|
||||
|
@@ -2,7 +2,7 @@ import * as ms from 'ms';
|
||||
import $ from 'cafy';
|
||||
import define from '../../define';
|
||||
import { Users, Followings } from '../../../../models';
|
||||
import { generateMuteQueryForUsers } from '../../common/generate-mute-query';
|
||||
import { generateMutedUserQueryForUsers } from '../../common/generate-muted-user-query';
|
||||
import { generateBlockQueryForUsers } from '../../common/generate-block-query';
|
||||
|
||||
export const meta = {
|
||||
@@ -47,7 +47,7 @@ export default define(meta, async (ps, me) => {
|
||||
.andWhere('user.id != :meId', { meId: me.id })
|
||||
.orderBy('user.followersCount', 'DESC');
|
||||
|
||||
generateMuteQueryForUsers(query, me);
|
||||
generateMutedUserQueryForUsers(query, me);
|
||||
generateBlockQueryForUsers(query, me);
|
||||
|
||||
const followingQuery = Followings.createQueryBuilder('following')
|
||||
|
@@ -15,6 +15,10 @@ export default abstract class Channel {
|
||||
return this.connection.user;
|
||||
}
|
||||
|
||||
protected get userProfile() {
|
||||
return this.connection.userProfile;
|
||||
}
|
||||
|
||||
protected get following() {
|
||||
return this.connection.following;
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import Channel from '../channel';
|
||||
import { Notes } from '../../../../models';
|
||||
import shouldMuteThisNote from '../../../../misc/should-mute-this-note';
|
||||
import { isMutedUserRelated } from '../../../../misc/is-muted-user-related';
|
||||
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'antenna';
|
||||
@@ -25,7 +25,7 @@ export default class extends Channel {
|
||||
const note = await Notes.pack(body.id, this.user, { detail: true });
|
||||
|
||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||
if (shouldMuteThisNote(note, this.muting)) return;
|
||||
if (isMutedUserRelated(note, this.muting)) return;
|
||||
|
||||
this.send('note', note);
|
||||
} else {
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import shouldMuteThisNote from '../../../../misc/should-mute-this-note';
|
||||
import { isMutedUserRelated } from '../../../../misc/is-muted-user-related';
|
||||
import Channel from '../channel';
|
||||
import { fetchMeta } from '../../../../misc/fetch-meta';
|
||||
import { Notes } from '../../../../models';
|
||||
import { PackedNote } from '../../../../models/repositories/note';
|
||||
import { checkWordMute } from '../../../../misc/check-word-mute';
|
||||
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'globalTimeline';
|
||||
@@ -45,7 +46,14 @@ export default class extends Channel {
|
||||
}
|
||||
|
||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||
if (shouldMuteThisNote(note, this.muting)) return;
|
||||
if (isMutedUserRelated(note, this.muting)) return;
|
||||
|
||||
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
||||
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
||||
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
||||
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
|
||||
// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
|
||||
if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return;
|
||||
|
||||
this.send('note', note);
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import shouldMuteThisNote from '../../../../misc/should-mute-this-note';
|
||||
import { isMutedUserRelated } from '../../../../misc/is-muted-user-related';
|
||||
import Channel from '../channel';
|
||||
import { Notes } from '../../../../models';
|
||||
import { PackedNote } from '../../../../models/repositories/note';
|
||||
@@ -34,7 +34,7 @@ export default class extends Channel {
|
||||
}
|
||||
|
||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||
if (shouldMuteThisNote(note, this.muting)) return;
|
||||
if (isMutedUserRelated(note, this.muting)) return;
|
||||
|
||||
this.send('note', note);
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import shouldMuteThisNote from '../../../../misc/should-mute-this-note';
|
||||
import { isMutedUserRelated } from '../../../../misc/is-muted-user-related';
|
||||
import Channel from '../channel';
|
||||
import { Notes } from '../../../../models';
|
||||
import { PackedNote } from '../../../../models/repositories/note';
|
||||
import { checkWordMute } from '../../../../misc/check-word-mute';
|
||||
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'homeTimeline';
|
||||
@@ -50,7 +51,14 @@ export default class extends Channel {
|
||||
}
|
||||
|
||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||
if (shouldMuteThisNote(note, this.muting)) return;
|
||||
if (isMutedUserRelated(note, this.muting)) return;
|
||||
|
||||
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
||||
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
||||
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
||||
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
|
||||
// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
|
||||
if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return;
|
||||
|
||||
this.send('note', note);
|
||||
}
|
||||
|
@@ -1,10 +1,11 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import shouldMuteThisNote from '../../../../misc/should-mute-this-note';
|
||||
import { isMutedUserRelated } from '../../../../misc/is-muted-user-related';
|
||||
import Channel from '../channel';
|
||||
import { fetchMeta } from '../../../../misc/fetch-meta';
|
||||
import { Notes } from '../../../../models';
|
||||
import { PackedNote } from '../../../../models/repositories/note';
|
||||
import { PackedUser } from '../../../../models/repositories/user';
|
||||
import { checkWordMute } from '../../../../misc/check-word-mute';
|
||||
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'hybridTimeline';
|
||||
@@ -59,7 +60,14 @@ export default class extends Channel {
|
||||
}
|
||||
|
||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||
if (shouldMuteThisNote(note, this.muting)) return;
|
||||
if (isMutedUserRelated(note, this.muting)) return;
|
||||
|
||||
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
||||
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
||||
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
||||
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
|
||||
// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
|
||||
if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return;
|
||||
|
||||
this.send('note', note);
|
||||
}
|
||||
|
@@ -1,10 +1,11 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import shouldMuteThisNote from '../../../../misc/should-mute-this-note';
|
||||
import { isMutedUserRelated } from '../../../../misc/is-muted-user-related';
|
||||
import Channel from '../channel';
|
||||
import { fetchMeta } from '../../../../misc/fetch-meta';
|
||||
import { Notes } from '../../../../models';
|
||||
import { PackedNote } from '../../../../models/repositories/note';
|
||||
import { PackedUser } from '../../../../models/repositories/user';
|
||||
import { checkWordMute } from '../../../../misc/check-word-mute';
|
||||
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'localTimeline';
|
||||
@@ -47,7 +48,14 @@ export default class extends Channel {
|
||||
}
|
||||
|
||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||
if (shouldMuteThisNote(note, this.muting)) return;
|
||||
if (isMutedUserRelated(note, this.muting)) return;
|
||||
|
||||
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
||||
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
||||
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
||||
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
|
||||
// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
|
||||
if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return;
|
||||
|
||||
this.send('note', note);
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import Channel from '../channel';
|
||||
import { Notes, UserListJoinings, UserLists } from '../../../../models';
|
||||
import shouldMuteThisNote from '../../../../misc/should-mute-this-note';
|
||||
import { isMutedUserRelated } from '../../../../misc/is-muted-user-related';
|
||||
import { User } from '../../../../models/entities/user';
|
||||
import { PackedNote } from '../../../../models/repositories/note';
|
||||
|
||||
@@ -73,7 +73,7 @@ export default class extends Channel {
|
||||
}
|
||||
|
||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||
if (shouldMuteThisNote(note, this.muting)) return;
|
||||
if (isMutedUserRelated(note, this.muting)) return;
|
||||
|
||||
this.send('note', note);
|
||||
}
|
||||
|
@@ -7,15 +7,17 @@ import Channel from './channel';
|
||||
import channels from './channels';
|
||||
import { EventEmitter } from 'events';
|
||||
import { User } from '../../../models/entities/user';
|
||||
import { Users, Followings, Mutings } from '../../../models';
|
||||
import { Users, Followings, Mutings, UserProfiles } from '../../../models';
|
||||
import { ApiError } from '../error';
|
||||
import { AccessToken } from '../../../models/entities/access-token';
|
||||
import { UserProfile } from '../../../models/entities/user-profile';
|
||||
|
||||
/**
|
||||
* Main stream connection
|
||||
*/
|
||||
export default class Connection {
|
||||
public user?: User;
|
||||
public userProfile?: UserProfile;
|
||||
public following: User['id'][] = [];
|
||||
public muting: User['id'][] = [];
|
||||
public token?: AccessToken;
|
||||
@@ -25,6 +27,7 @@ export default class Connection {
|
||||
private subscribingNotes: any = {};
|
||||
private followingClock: NodeJS.Timer;
|
||||
private mutingClock: NodeJS.Timer;
|
||||
private userProfileClock: NodeJS.Timer;
|
||||
|
||||
constructor(
|
||||
wsConnection: websocket.connection,
|
||||
@@ -49,6 +52,9 @@ export default class Connection {
|
||||
|
||||
this.updateMuting();
|
||||
this.mutingClock = setInterval(this.updateMuting, 5000);
|
||||
|
||||
this.updateUserProfile();
|
||||
this.userProfileClock = setInterval(this.updateUserProfile, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,6 +268,13 @@ export default class Connection {
|
||||
this.muting = mutings.map(x => x.muteeId);
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async updateUserProfile() {
|
||||
this.userProfile = await UserProfiles.findOne({
|
||||
userId: this.user!.id
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ストリームが切れたとき
|
||||
*/
|
||||
@@ -273,5 +286,6 @@ export default class Connection {
|
||||
|
||||
if (this.followingClock) clearInterval(this.followingClock);
|
||||
if (this.mutingClock) clearInterval(this.mutingClock);
|
||||
if (this.userProfileClock) clearInterval(this.userProfileClock);
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ import { Antenna } from '../models/entities/antenna';
|
||||
import { Note } from '../models/entities/note';
|
||||
import { AntennaNotes, Mutings, Notes } from '../models';
|
||||
import { genId } from '../misc/gen-id';
|
||||
import shouldMuteThisNote from '../misc/should-mute-this-note';
|
||||
import { isMutedUserRelated } from '../misc/is-muted-user-related';
|
||||
import { ensure } from '../prelude/ensure';
|
||||
import { publishAntennaStream, publishMainStream } from './stream';
|
||||
import { User } from '../models/entities/user';
|
||||
@@ -39,7 +39,7 @@ export async function addNoteToAntenna(antenna: Antenna, note: Note, noteUser: U
|
||||
_note.renote = await Notes.findOne(note.renoteId).then(ensure);
|
||||
}
|
||||
|
||||
if (shouldMuteThisNote(_note, mutings.map(x => x.muteeId))) {
|
||||
if (isMutedUserRelated(_note, mutings.map(x => x.muteeId))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -17,7 +17,7 @@ import extractMentions from '../../misc/extract-mentions';
|
||||
import extractEmojis from '../../misc/extract-emojis';
|
||||
import extractHashtags from '../../misc/extract-hashtags';
|
||||
import { Note, IMentionedRemoteUsers } from '../../models/entities/note';
|
||||
import { Mutings, Users, NoteWatchings, Notes, Instances, UserProfiles, Antennas, Followings } from '../../models';
|
||||
import { Mutings, Users, NoteWatchings, Notes, Instances, UserProfiles, Antennas, Followings, MutedNotes } from '../../models';
|
||||
import { DriveFile } from '../../models/entities/drive-file';
|
||||
import { App } from '../../models/entities/app';
|
||||
import { Not, getConnection, In } from 'typeorm';
|
||||
@@ -29,6 +29,7 @@ import { createNotification } from '../create-notification';
|
||||
import { isDuplicateKeyValueError } from '../../misc/is-duplicate-key-value-error';
|
||||
import { ensure } from '../../prelude/ensure';
|
||||
import { checkHitAntenna } from '../../misc/check-hit-antenna';
|
||||
import { checkWordMute } from '../../misc/check-word-mute';
|
||||
import { addNoteToAntenna } from '../add-note-to-antenna';
|
||||
import { countSameRenotes } from '../../misc/count-same-renotes';
|
||||
import { deliverToRelays } from '../relay';
|
||||
@@ -219,6 +220,24 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
|
||||
// Increment notes count (user)
|
||||
incNotesCountOfUser(user);
|
||||
|
||||
// Word mute
|
||||
UserProfiles.find({
|
||||
enableWordMute: true
|
||||
}).then(us => {
|
||||
for (const u of us) {
|
||||
checkWordMute(note, { id: u.userId }, u.mutedWords).then(shouldMute => {
|
||||
if (shouldMute) {
|
||||
MutedNotes.save({
|
||||
id: genId(),
|
||||
userId: u.userId,
|
||||
noteId: note.id,
|
||||
reason: 'word',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Antenna
|
||||
Antennas.find().then(async antennas => {
|
||||
const followings = await Followings.createQueryBuilder('following')
|
||||
|
@@ -1,3 +1,5 @@
|
||||
export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app'] as const;
|
||||
|
||||
export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;
|
||||
|
||||
export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const;
|
||||
|
56
yarn.lock
56
yarn.lock
@@ -192,10 +192,10 @@
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
|
||||
"@syuilo/aiscript@0.8.0":
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@syuilo/aiscript/-/aiscript-0.8.0.tgz#3a895ddd9f5bd5afa1648acb5fd3e6f94f434cbb"
|
||||
integrity sha512-mrZ3awYf1R81D+OWZctRFiAWUt6xL3A5ovBn2OD8+1hZyX3T7S+awqrhYVLoQPhd/cijz1RT6PE8AEUtuR1J8Q==
|
||||
"@syuilo/aiscript@0.10.0":
|
||||
version "0.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@syuilo/aiscript/-/aiscript-0.10.0.tgz#858c0f84db9dd2c3ffec40c340966c6bcc096e1d"
|
||||
integrity sha512-Nrhzsb0JfplazCl2biLqlC9sUcrwAEu7dF5MaFZc2lbR4gAMX1i1ijFuH4V2NRG0P81l8cikGCpMnjhxfS6qGw==
|
||||
dependencies:
|
||||
autobind-decorator "2.4.0"
|
||||
chalk "4.0.0"
|
||||
@@ -3245,6 +3245,11 @@ entities@^2.0.0, entities@~2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
|
||||
integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
|
||||
|
||||
env-paths@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43"
|
||||
integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==
|
||||
|
||||
errno@^0.1.3:
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
|
||||
@@ -4129,6 +4134,11 @@ graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, g
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
|
||||
integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
|
||||
|
||||
graceful-fs@^4.2.3:
|
||||
version "4.2.4"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
|
||||
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
|
||||
|
||||
growl@1.10.5:
|
||||
version "1.10.5"
|
||||
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
|
||||
@@ -4658,6 +4668,11 @@ insert-text-at-cursor@0.3.0:
|
||||
resolved "https://registry.yarnpkg.com/insert-text-at-cursor/-/insert-text-at-cursor-0.3.0.tgz#1819607680ec1570618347c4cd475e791faa25da"
|
||||
integrity sha512-/nPtyeX9xPUvxZf+r0518B7uqNKlP+LqNJqSiXFEaa2T71rWIwTVXGH7hB9xO/EVdwa5/pWlFCPwShOW81XIxQ==
|
||||
|
||||
install-artifact-from-github@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.0.2.tgz#e1e478dd29880b9112ecd684a84029603e234a9d"
|
||||
integrity sha512-yuMFBSVIP3vD0SDBGUqeIpgOAIlFx8eQFknQObpkYEM5gsl9hy6R9Ms3aV+Vw9MMyYsoPMeex0XDnfgY7uzc+Q==
|
||||
|
||||
interpret@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296"
|
||||
@@ -6187,7 +6202,7 @@ mz@^2.4.0, mz@^2.7.0:
|
||||
object-assign "^4.0.1"
|
||||
thenify-all "^1.0.0"
|
||||
|
||||
nan@^2.14.0:
|
||||
nan@^2.14.0, nan@^2.14.1:
|
||||
version "2.14.1"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
|
||||
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
|
||||
@@ -6283,6 +6298,22 @@ node-forge@^0.9.1:
|
||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.1.tgz#775368e6846558ab6676858a4d8c6e8d16c677b5"
|
||||
integrity sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==
|
||||
|
||||
node-gyp@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-7.0.0.tgz#2e88425ce84e9b1a4433958ed55d74c70fffb6be"
|
||||
integrity sha512-ZW34qA3CJSPKDz2SJBHKRvyNQN0yWO5EGKKksJc+jElu9VA468gwJTyTArC1iOXU7rN3Wtfg/CMt/dBAOFIjvg==
|
||||
dependencies:
|
||||
env-paths "^2.2.0"
|
||||
glob "^7.1.4"
|
||||
graceful-fs "^4.2.3"
|
||||
nopt "^4.0.3"
|
||||
npmlog "^4.1.2"
|
||||
request "^2.88.2"
|
||||
rimraf "^2.6.3"
|
||||
semver "^7.3.2"
|
||||
tar "^6.0.1"
|
||||
which "^2.0.2"
|
||||
|
||||
node-object-hash@^1.2.0:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/node-object-hash/-/node-object-hash-1.4.2.tgz#385833d85b229902b75826224f6077be969a9e94"
|
||||
@@ -7775,6 +7806,15 @@ rdf-canonize@^1.0.2:
|
||||
node-forge "^0.9.1"
|
||||
semver "^6.3.0"
|
||||
|
||||
re2@1.15.4:
|
||||
version "1.15.4"
|
||||
resolved "https://registry.yarnpkg.com/re2/-/re2-1.15.4.tgz#2ffc3e4894fb60430393459978197648be01a0a9"
|
||||
integrity sha512-7w3K+Daq/JjbX/dz5voMt7B9wlprVBQnMiypyCojAZ99kcAL+3LiJ5uBoX/u47l8eFTVq3Wj+V0pmvU+CT8tOg==
|
||||
dependencies:
|
||||
install-artifact-from-github "^1.0.2"
|
||||
nan "^2.14.1"
|
||||
node-gyp "^7.0.0"
|
||||
|
||||
read-pkg-up@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
|
||||
@@ -8183,7 +8223,7 @@ rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2:
|
||||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
rimraf@^2.6.2:
|
||||
rimraf@^2.6.2, rimraf@^2.6.3:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
|
||||
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
|
||||
@@ -9088,7 +9128,7 @@ tar-stream@^2.0.0:
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^3.1.1"
|
||||
|
||||
tar@^6.0.2:
|
||||
tar@^6.0.1, tar@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.2.tgz#5df17813468a6264ff14f766886c622b84ae2f39"
|
||||
integrity sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg==
|
||||
@@ -10138,7 +10178,7 @@ which-pm-runs@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb"
|
||||
integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=
|
||||
|
||||
which@2.0.2, which@^2.0.1:
|
||||
which@2.0.2, which@^2.0.1, which@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
|
||||
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
|
||||
|
Reference in New Issue
Block a user