Compare commits
51 Commits
Author | SHA1 | Date | |
---|---|---|---|
70ee172128 | |||
b9febc00f9 | |||
0112e2f7ec | |||
60736bab2a | |||
fb7c4ee21a | |||
e93c06cd00 | |||
0a99345909 | |||
3d08ff7cb4 | |||
057ce73ba1 | |||
66b07578c5 | |||
de3b365563 | |||
7bb8d8b27e | |||
9008664606 | |||
7374905c28 | |||
718e20de60 | |||
73d166323d | |||
3589fc6f4f | |||
feed6c7acc | |||
2522e7388d | |||
09cfd620bb | |||
362e95263d | |||
c6837b9fdf | |||
71878f93e4 | |||
f5d43b1f25 | |||
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 |
@ -10,3 +10,4 @@ docker-compose.yml
|
||||
elasticsearch/
|
||||
node_modules/
|
||||
redis/
|
||||
files/
|
||||
|
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
|
||||
|
@ -13,6 +13,8 @@ services:
|
||||
networks:
|
||||
- internal_network
|
||||
- external_network
|
||||
volumes:
|
||||
- ./files:/misskey/files
|
||||
|
||||
redis:
|
||||
restart: always
|
||||
|
@ -4,3 +4,30 @@ Docs for users are located in `src/docs`.
|
||||
|
||||
これらのドキュメントはMisskeyの開発者またはMisskeyインスタンス運営者向けです。
|
||||
利用者向けのドキュメントは`src/docs`にあります。
|
||||
|
||||
这些文档是为 Misskey 的贡献者,或是 Misskey 实例的管理者准备的。
|
||||
为用户准备的文档放置在 `src/docs` 文件夹中。
|
||||
|
||||
## 日本語版
|
||||
|
||||
- [Misskey構築の手引き](./setup.ja.md)
|
||||
- [運営ガイド](./manage.ja.md)
|
||||
- [Dockerを使ったMisskey構築方法](./docker.ja.md)
|
||||
|
||||
## English Version
|
||||
|
||||
- [Misskey Setup and Installation Guide](./setup.en.md)
|
||||
- [Management guide](./manage.en.md)
|
||||
- [Docker Guide](./docker.en.md)
|
||||
|
||||
## Française Version
|
||||
|
||||
- [Guide d'installation et de configuration de Misskey](./setup.fr.md)
|
||||
- [Guide d'administration](./manage.fr.md)
|
||||
- [Guide Docker](./docker.fr.md)
|
||||
|
||||
## 简体中文版
|
||||
|
||||
- [Misskey 设置和安装指南](./setup.zh.md)
|
||||
- [运营指南](./manage.zh.md)
|
||||
- [Docker 部署指南](./docker.zh.md)
|
||||
|
@ -3,7 +3,8 @@ Docker Guide
|
||||
|
||||
This guide describes how to install and setup Misskey with Docker.
|
||||
|
||||
[Japanese version also available - 日本語版もあります](./docker.ja.md)
|
||||
- [Japanese version also available - 日本語版もあります](./docker.ja.md)
|
||||
- [Simplified Chinese version also available - 简体中文版同样可用](./docker.zh.md)
|
||||
|
||||
----------------------------------------------------------------
|
||||
|
||||
|
@ -3,8 +3,9 @@ Guide Docker
|
||||
|
||||
Ce guide explique comment installer et configurer Misskey avec Docker.
|
||||
|
||||
[Version japonaise également disponible - Japanese version also available - 日本語版もあります](./docker.ja.md)
|
||||
[Version anglaise également disponible - English version also available - 英語版もあります](./docker.en.md)
|
||||
- [Version japonaise également disponible - Japanese version also available - 日本語版もあります](./docker.ja.md)
|
||||
- [Version anglaise également disponible - English version also available - 英語版もあります](./docker.en.md)
|
||||
- [Version Chinois simplifié également disponible - Simplified Chinese version also available - 简体中文版同样可用](./docker.zh.md)
|
||||
|
||||
----------------------------------------------------------------
|
||||
|
||||
|
@ -3,7 +3,8 @@ Dockerを使ったMisskey構築方法
|
||||
|
||||
このガイドはDockerを使ったMisskeyセットアップ方法について解説します。
|
||||
|
||||
[英語版もあります - English version also available](./docker.en.md)
|
||||
- [英語版もあります - English version also available](./docker.en.md)
|
||||
- [简体中文版同样可用 - Simplified Chinese version also available](./docker.zh.md)
|
||||
|
||||
----------------------------------------------------------------
|
||||
|
||||
|
96
docs/docker.zh.md
Normal file
96
docs/docker.zh.md
Normal file
@ -0,0 +1,96 @@
|
||||
Docker 部署指南
|
||||
================================================================
|
||||
|
||||
这份指南描述了如何使用Docker安装并设置 Misskey 。
|
||||
|
||||
- [日本語版もあります - Japanese version also available](./docker.ja.md)
|
||||
- [英語版もあります - English version also available](./docker.en.md)
|
||||
|
||||
----------------------------------------------------------------
|
||||
|
||||
*1.* 下载 Misskey
|
||||
----------------------------------------------------------------
|
||||
1. 克隆 Misskey 项目的 master 分支。
|
||||
|
||||
`git clone -b master git://github.com/syuilo/misskey.git`
|
||||
|
||||
2. 进入 misskey 文件夹。
|
||||
|
||||
`cd misskey`
|
||||
|
||||
3. 检查 [最新发布版](https://github.com/syuilo/misskey/releases/latest) 标签。
|
||||
|
||||
`git checkout master`
|
||||
|
||||
*2.* 配置 Misskey
|
||||
----------------------------------------------------------------
|
||||
|
||||
可以按照如下方式创建配置文件:
|
||||
|
||||
``` bash
|
||||
cd .config
|
||||
cp example.yml default.yml
|
||||
cp docker_example.env docker.env
|
||||
```
|
||||
|
||||
### `default.yml`
|
||||
|
||||
这个文件的编辑工作基本与非 Docker 环境的版本相同。
|
||||
但请注意, Postgresql、 Redis 和 Elasticsearch 的 **主机名(hostname)** 配置不应该是 `localhost` ,它们被设置在 `docker-compose.yml` 文件中。
|
||||
以下是默认的主机名:
|
||||
|
||||
| 服务 | 主机名 |
|
||||
|---------------|----------|
|
||||
| Postgresql | `db` |
|
||||
| Redis | `redis` |
|
||||
| Elasticsearch | `es` |
|
||||
|
||||
### `docker.env`
|
||||
|
||||
在这个文件中配置 Postgresql 。
|
||||
至少需要如下这些配置:
|
||||
|
||||
| 名称 | 描述 |
|
||||
|---------------------|---------------|
|
||||
| `POSTGRES_PASSWORD` | 数据库密码 |
|
||||
| `POSTGRES_USER` | 数据库用户名 |
|
||||
| `POSTGRES_DB` | 数据库名 |
|
||||
|
||||
*3.* 配置 Docker
|
||||
----------------------------------------------------------------
|
||||
编辑 `docker-compose.yml` 文件。
|
||||
|
||||
*4.* 构建 Misskey
|
||||
----------------------------------------------------------------
|
||||
使用如下的方式构建Misskey:
|
||||
|
||||
`docker-compose build`
|
||||
|
||||
*5.* 初始化数据库
|
||||
----------------------------------------------------------------
|
||||
``` bash
|
||||
docker-compose run --rm web yarn run init
|
||||
```
|
||||
|
||||
*6.* 完成了!
|
||||
----------------------------------------------------------------
|
||||
干得不错!现在您拥有了一个可以运行Misskey的环境啦。
|
||||
|
||||
### 正常启动
|
||||
只需要 `docker-compose up -d` 即可。玩得愉快!
|
||||
|
||||
### 如何将您的 Misskey 服务器升级至最新版本
|
||||
1. `git stash`
|
||||
2. `git checkout master`
|
||||
3. `git pull`
|
||||
4. `git stash pop`
|
||||
5. `docker-compose build`
|
||||
6. 检查 [更新日志](../CHANGELOG.md) 以获取升级迁移信息。
|
||||
7. `docker-compose stop && docker-compose up -d`
|
||||
|
||||
### 如何执行 [控制台指令](manage.zh.md):
|
||||
`docker-compose run --rm web node built/tools/mark-admin @example`
|
||||
|
||||
----------------------------------------------------------------
|
||||
|
||||
如果您有任何疑问或是困惑,欢迎与我们联系!
|
14
docs/manage.zh.md
Normal file
14
docs/manage.zh.md
Normal file
@ -0,0 +1,14 @@
|
||||
# 运营指南
|
||||
|
||||
## 检查任务队列的状态
|
||||
即将到来……
|
||||
|
||||
## 设置用户为管理员
|
||||
``` shell
|
||||
node built/tools/mark-admin (用户名)
|
||||
```
|
||||
|
||||
样例
|
||||
``` shell
|
||||
node built/tools/mark-admin @syuilo
|
||||
```
|
@ -4,7 +4,8 @@ Misskey Setup and Installation Guide
|
||||
We thank you for your interest in setting up your Misskey server!
|
||||
This guide describes how to install and setup Misskey.
|
||||
|
||||
[Japanese version also available - 日本語版もあります](./setup.ja.md)
|
||||
- [Japanese version also available - 日本語版もあります](./setup.ja.md)
|
||||
- [Simplified Chinese version also available - 简体中文版同样可用](./setup.zh.md)
|
||||
|
||||
----------------------------------------------------------------
|
||||
|
||||
|
@ -4,7 +4,9 @@ Guide d'installation et de configuration de Misskey
|
||||
Nous vous remerçions de l'intrêt que vous manifestez pour l'installation de votre propre instance Misskey !
|
||||
Ce guide décrit les étapes à suivre afin d'installer et de configurer une instance Misskey.
|
||||
|
||||
[La version en japonnais est également disponible sur - 日本語版もあります](./setup.ja.md)
|
||||
- [La version en japonnais est également disponible sur - 日本語版もあります](./setup.ja.md)
|
||||
- [Version anglaise également disponible - English version also available - 英語版もあります](./setup.en.md)
|
||||
- [Version Chinois simplifié également disponible - Simplified Chinese version also available - 简体中文版同样可用](./setup.zh.md)
|
||||
|
||||
----------------------------------------------------------------
|
||||
|
||||
|
@ -4,7 +4,8 @@ Misskey構築の手引き
|
||||
Misskeyサーバーの構築にご関心をお寄せいただきありがとうございます!
|
||||
このガイドではMisskeyのインストール・セットアップ方法について解説します。
|
||||
|
||||
[英語版もあります - English version also available](./setup.en.md)
|
||||
- [英語版もあります - English version also available](./setup.en.md)
|
||||
- [简体中文版同样可用 - Simplified Chinese version also available](./setup.zh.md)
|
||||
|
||||
----------------------------------------------------------------
|
||||
|
||||
|
146
docs/setup.zh.md
Normal file
146
docs/setup.zh.md
Normal file
@ -0,0 +1,146 @@
|
||||
Misskey 设置和安装指南
|
||||
================================================================
|
||||
|
||||
非常感谢您对构建 Misskey 服务器的关注!
|
||||
这份指南描述了 Misskey 的安装与设置流程。
|
||||
|
||||
- [日本語版もあります - Japanese version also available](./setup.ja.md)
|
||||
- [英語版もあります - English version also available](./setup.en.md)
|
||||
|
||||
----------------------------------------------------------------
|
||||
|
||||
*1.* 创建 Misskey 用户
|
||||
----------------------------------------------------------------
|
||||
直接使用 root 用户来运行 misskey 也许并不是一个好主意,因此我们有必要创建一个专用的用户。
|
||||
以 Debian 为例:
|
||||
|
||||
``` bash
|
||||
adduser --disabled-password --disabled-login misskey
|
||||
```
|
||||
|
||||
*2.* 安装依赖
|
||||
----------------------------------------------------------------
|
||||
请安装并设置如下这些软件:
|
||||
|
||||
#### Dependencies :package:
|
||||
* **[Node.js](https://nodejs.org/en/)** (12.x, 14.x)
|
||||
* **[PostgreSQL](https://www.postgresql.org/)** (>= 10)
|
||||
* **[Redis](https://redis.io/)**
|
||||
|
||||
##### Optional
|
||||
* [Yarn](https://yarnpkg.com/) *可选,但出于安全因素考虑还是推荐安装。如果您没有安装, 您需要使用 `npx yarn` 来代替 `yarn`.*
|
||||
* [Elasticsearch](https://www.elastic.co/) - 为了启用搜索功能,这个搜索引擎是有必要的。
|
||||
* [FFmpeg](https://www.ffmpeg.org/)
|
||||
|
||||
*3.* 安装 Misskey
|
||||
----------------------------------------------------------------
|
||||
1. 连接至 misskey 用户.
|
||||
|
||||
`su - misskey`
|
||||
|
||||
2. 克隆 Misskey 项目的 master 分支。
|
||||
|
||||
`git clone -b master git://github.com/syuilo/misskey.git`
|
||||
|
||||
3. 进入 misskey 文件夹。
|
||||
|
||||
`cd misskey`
|
||||
|
||||
4. 检查 [最新发布版](https://github.com/syuilo/misskey/releases/latest) 标签。
|
||||
|
||||
`git checkout master`
|
||||
|
||||
5. 安装 Misskey 的依赖。
|
||||
|
||||
`yarn`
|
||||
|
||||
*4.* 配置 Misskey
|
||||
----------------------------------------------------------------
|
||||
1. 复制 `.config/example.yml` 并重命名为 `default.yml`。
|
||||
|
||||
`cp .config/example.yml .config/default.yml`
|
||||
|
||||
2. 编辑 `default.yml`
|
||||
|
||||
*5.* 构建 Misskey
|
||||
----------------------------------------------------------------
|
||||
|
||||
使用如下的指令构建 Misskey :
|
||||
|
||||
`NODE_ENV=production yarn build`
|
||||
|
||||
如果您使用的是 Debian , 您需要安装 `build-essential`, `python` 环境包。
|
||||
|
||||
如果您仍然遇到有关某些模块的错误,您可以使用 node-gyp:
|
||||
|
||||
1. `npx node-gyp configure`
|
||||
2. `npx node-gyp build`
|
||||
3. `NODE_ENV=production yarn build`
|
||||
|
||||
*6.* 初始化数据库
|
||||
----------------------------------------------------------------
|
||||
``` bash
|
||||
yarn run init
|
||||
```
|
||||
|
||||
*7.* 完成了!
|
||||
----------------------------------------------------------------
|
||||
干得不错!现在您拥有了一个可以运行Misskey的环境啦。
|
||||
|
||||
### 正常启动
|
||||
只需要 `NODE_ENV=production npm start` 即可。玩得愉快!
|
||||
|
||||
### 使用 systemd 来启动
|
||||
|
||||
1. 在此处创建一个 systemd 服务:
|
||||
|
||||
`/etc/systemd/system/misskey.service`
|
||||
|
||||
2. 编辑它,粘贴如下内容并保存:
|
||||
|
||||
```
|
||||
[Unit]
|
||||
Description=Misskey daemon
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=misskey
|
||||
ExecStart=/usr/bin/npm start
|
||||
WorkingDirectory=/home/misskey/misskey
|
||||
Environment="NODE_ENV=production"
|
||||
TimeoutSec=60
|
||||
StandardOutput=syslog
|
||||
StandardError=syslog
|
||||
SyslogIdentifier=misskey
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
3. 重启 systemd 并设置 misskey 服务自动启动:
|
||||
|
||||
`systemctl daemon-reload ; systemctl enable misskey`
|
||||
|
||||
4. 启动 misskey 服务:
|
||||
|
||||
`systemctl start misskey`
|
||||
|
||||
您可以使用 `systemctl status misskey` 来检查服务是否正在运行。
|
||||
|
||||
### 如何将您的 Misskey 服务器升级至最新版本
|
||||
1. `git checkout master`
|
||||
2. `git pull`
|
||||
3. `yarn install`
|
||||
4. `NODE_ENV=production yarn build`
|
||||
5. `yarn migrate`
|
||||
6. 重启您的 Misskey 进程来应用改变。
|
||||
7. 尽情享受吧!
|
||||
|
||||
如果您在更新时遇到任何问题,请尝试以下操作:
|
||||
1. `yarn clean` 或是 `yarn cleanall`
|
||||
2. 重试升级 (请不要忘记 `yarn install` )
|
||||
|
||||
----------------------------------------------------------------
|
||||
|
||||
如果您有任何疑问或是困惑,欢迎与我们联系!
|
@ -351,6 +351,9 @@ pluginInstallWarn: "يرجى تنصيب إضافات ذات مصدر موثوق
|
||||
smtpHost: "المضيف"
|
||||
smtpUser: "اسم المستخدم"
|
||||
smtpPass: "الكلمة السرية"
|
||||
_sidebar:
|
||||
icon: "الصورة الرمزية"
|
||||
hide: "إخفاء"
|
||||
_theme:
|
||||
explore: "استكشف قوالب المظهر"
|
||||
install: "تنصيب قالب"
|
||||
@ -500,6 +503,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,38 @@ 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"
|
||||
display: "Anzeige"
|
||||
_sidebar:
|
||||
full: "Voll"
|
||||
icon: "Profilbild"
|
||||
hide: "Ausblenden"
|
||||
_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 +1208,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,38 @@ 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"
|
||||
display: "Display"
|
||||
_sidebar:
|
||||
full: "Full"
|
||||
icon: "Avatar"
|
||||
hide: "Hide"
|
||||
_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 +1208,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"
|
||||
|
@ -104,6 +104,8 @@ unblockConfirm: "¿Quiere dejar de bloquear esta cuenta?"
|
||||
suspendConfirm: "¿Quiere suspender esta cuenta?"
|
||||
unsuspendConfirm: "¿Quiere dejar de suspender esta cuenta?"
|
||||
selectList: "Seleccione una lista"
|
||||
selectAntenna: "Seleccionar antena"
|
||||
selectWidget: "Seleccionar widget"
|
||||
customEmojis: "Emojis personalizados"
|
||||
emoji: "Emoji"
|
||||
emojiName: "Nombre del emoji"
|
||||
@ -535,6 +537,8 @@ enableAll: "Activar todo"
|
||||
disableAll: "Desactivar todo"
|
||||
tokenRequested: "Permiso de acceso a la cuenta"
|
||||
pluginTokenRequestedDescription: "Este plugin podrá usar los permisos descritos aquí"
|
||||
notificationType: "Tipo de notificación"
|
||||
edit: "Editar"
|
||||
useStarForReactionFallback: "En caso de que los emojis de reacciones no sean claros, usar en su lugar una estrella"
|
||||
emailConfig: "Configuración del servidor de correos"
|
||||
enableEmail: "Activar el envío de correos electrónicos"
|
||||
@ -549,6 +553,20 @@ emptyToDisableSmtpAuth: "Deje el nombre del usuario y la contraseña en blanco p
|
||||
smtpSecure: "Usar SSL/TLS implícito en la conexión SMTP"
|
||||
smtpSecureInfo: "Apagar cuando se use STARTTLS"
|
||||
testEmail: "Prueba de envío"
|
||||
wordMute: "Silenciar palabras"
|
||||
userSaysSomething: "{name} dijo algo"
|
||||
makeActive: "Activar"
|
||||
_sidebar:
|
||||
icon: "Avatar"
|
||||
hide: "Ocultar"
|
||||
_wordMute:
|
||||
muteWords: "Palabras que silenciar"
|
||||
muteWordsDescription: "Separar con espacios indica una declaracion And, separar con lineas nuevas indica una declaracion Or。"
|
||||
muteWordsDescription2: "Encerrar las palabras clave entre numerales para usar expresiones regulares"
|
||||
softDescription: "Ocultar en la linea de tiempo las notas que cumplen las condiciones"
|
||||
hardDescription: "Evitar que se agreguen a la linea de tiempo las notas que cumplen las condiciones. Las notas no agregadas seguirán quitadas aunque cambien las condiciones."
|
||||
soft: "Suave"
|
||||
hard: "Duro"
|
||||
_theme:
|
||||
explore: "Explorar temas"
|
||||
install: "Instalar tema"
|
||||
@ -1188,10 +1206,26 @@ _notification:
|
||||
youReceivedFollowRequest: "Has mandado una solicitud de seguimiento"
|
||||
yourFollowRequestAccepted: "Tu solicitud de seguimiento fue aceptada"
|
||||
youWereInvitedToGroup: "Invitado al grupo"
|
||||
_types:
|
||||
all: "Todo"
|
||||
follow: "Siguiendo"
|
||||
mention: "Menciones"
|
||||
reply: "Respuestas"
|
||||
renote: "Renotar"
|
||||
quote: "Citar"
|
||||
reaction: "Reacción"
|
||||
pollVote: "Encuestas"
|
||||
receiveFollowRequest: "Solicitudes de seguimiento"
|
||||
_deck:
|
||||
alwaysShowMainColumn: "Siempre mostrar la columna principal"
|
||||
columnAlign: "Alinear columnas"
|
||||
addColumn: "Agregar columna"
|
||||
swapLeft: "Mover a la izquierda"
|
||||
swapRight: "Mover a la derecha"
|
||||
swapUp: "Mover arriba"
|
||||
swapDown: "Mover abajo"
|
||||
stackLeft: "Apilar a la izquierda"
|
||||
popRight: "Sacar a la derecha"
|
||||
_columns:
|
||||
widgets: "Widgets"
|
||||
notifications: "Notificaciones"
|
||||
|
@ -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,9 +528,24 @@ 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"
|
||||
_sidebar:
|
||||
icon: "Avatar"
|
||||
hide: "Masquer"
|
||||
_theme:
|
||||
explore: "Explorer les thèmes"
|
||||
install: "Installer un thème"
|
||||
@ -1134,6 +1151,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,24 @@ emptyToDisableSmtpAuth: "ユーザー名とパスワードを空欄にするこ
|
||||
smtpSecure: "SMTP 接続に暗黙的なSSL/TLSを使用する"
|
||||
smtpSecureInfo: "STARTTLS使用時はオフにします。"
|
||||
testEmail: "配信テスト"
|
||||
wordMute: "ワードミュート"
|
||||
userSaysSomething: "{name}が何かを言いました"
|
||||
makeActive: "アクティブにする"
|
||||
display: "表示"
|
||||
|
||||
_sidebar:
|
||||
full: "フル"
|
||||
icon: "アイコン"
|
||||
hide: "隠す"
|
||||
|
||||
_wordMute:
|
||||
muteWords: "ミュートするワード"
|
||||
muteWordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。"
|
||||
muteWordsDescription2: "キーワードをスラッシュで囲むと正規表現になります。"
|
||||
softDescription: "指定した条件のノートをタイムラインから隠します。"
|
||||
hardDescription: "指定した条件のノートをタイムラインに追加しないようにします。追加されなかったノートは、条件を変更しても除外されたままになります。"
|
||||
soft: "ソフト"
|
||||
hard: "ハード"
|
||||
|
||||
_theme:
|
||||
explore: "テーマを探す"
|
||||
|
@ -356,6 +356,8 @@ invites: "来てや"
|
||||
smtpHost: "ホスト"
|
||||
smtpUser: "ユーザー名"
|
||||
smtpPass: "パスワード"
|
||||
_sidebar:
|
||||
icon: "アイコン"
|
||||
_theme:
|
||||
keys:
|
||||
renote: "Renote"
|
||||
@ -439,6 +441,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"
|
||||
|
@ -104,6 +104,8 @@ unblockConfirm: "이 계정의 차단을 해제하시겠습니까?"
|
||||
suspendConfirm: "이 계정을 정지하시겠습니까?"
|
||||
unsuspendConfirm: "이 계정의 정지를 해제하시겠습니까?"
|
||||
selectList: "리스트 선택"
|
||||
selectAntenna: "안테나 선택"
|
||||
selectWidget: "위젯 선택"
|
||||
customEmojis: "커스텀 이모지"
|
||||
emoji: "이모지"
|
||||
emojiName: "이모지 이름"
|
||||
@ -432,7 +434,7 @@ tags: "태그"
|
||||
docSource: "이 문서의 소스"
|
||||
createAccount: "계정 만들기"
|
||||
existingAcount: "기존 계정"
|
||||
regenerate: "다시 생성"
|
||||
regenerate: "재생성"
|
||||
fontSize: "글자 크기"
|
||||
noFollowRequests: "처리되지 않은 팔로우 요청이 없습니다"
|
||||
openImageInNewTab: "새 탭에서 이미지 열기"
|
||||
@ -527,9 +529,25 @@ plugins: "플러그인"
|
||||
pluginInstallWarn: "신뢰할 수 없는 플러그인은 설치하지 마십시오."
|
||||
deck: "덱"
|
||||
undeck: "덱 해제"
|
||||
generateAccessToken: "액세스 토큰 생성"
|
||||
permission: "권한"
|
||||
enableAll: "전체 선택"
|
||||
disableAll: "전체 해제"
|
||||
edit: "편집"
|
||||
useStarForReactionFallback: "알 수 없는 리액션 이모지 대신 ★ 사용"
|
||||
email: "메일 주소"
|
||||
smtpConfig: "SMTP 서버 설정"
|
||||
smtpHost: "호스트"
|
||||
smtpUser: "유저명"
|
||||
smtpPass: "비밀번호"
|
||||
emptyToDisableSmtpAuth: "SMTP 인증을 사용하지 않으려면 공란으로 비워둡니다."
|
||||
smtpSecureInfo: "STARTTLS 사용 시에는 해제합니다."
|
||||
wordMute: "단어 뮤트"
|
||||
_sidebar:
|
||||
icon: "아바타"
|
||||
hide: "숨기기"
|
||||
_wordMute:
|
||||
muteWords: "뮤트할 단어"
|
||||
_theme:
|
||||
explore: "테마 찾아보기"
|
||||
install: "테마 설치"
|
||||
@ -1120,7 +1138,19 @@ _notification:
|
||||
youReceivedFollowRequest: "새로운 팔로우 요청이 있습니다"
|
||||
yourFollowRequestAccepted: "팔로우 요청이 수락되었습니다"
|
||||
youWereInvitedToGroup: "그룹에 초대되었습니다"
|
||||
_types:
|
||||
follow: "팔로잉"
|
||||
mention: "멘션"
|
||||
renote: "Renote"
|
||||
quote: "인용"
|
||||
reaction: "리액션"
|
||||
_deck:
|
||||
swapLeft: "왼쪽으로 이동"
|
||||
swapRight: "오른쪽으로 이동"
|
||||
swapUp: "위로 이동"
|
||||
swapDown: "아래로 이동"
|
||||
stackLeft: "왼쪽에 쌓기"
|
||||
popRight: "오른쪽으로 빼기"
|
||||
_columns:
|
||||
widgets: "위젯"
|
||||
notifications: "알림"
|
||||
|
@ -86,8 +86,8 @@ you: "您"
|
||||
clickToShow: "点击以显示"
|
||||
sensitive: "阅读注意"
|
||||
add: "添加"
|
||||
reaction: "反应"
|
||||
reactionSettingDescription: "选择您想要固定在反应选择器中的反应。"
|
||||
reaction: "回应"
|
||||
reactionSettingDescription: "选择您想要置顶的回应。"
|
||||
rememberNoteVisibility: "记录公开范围"
|
||||
attachCancel: "删除附件"
|
||||
markAsSensitive: "阅读注意"
|
||||
@ -104,6 +104,8 @@ unblockConfirm: "确定要解除屏蔽吗?"
|
||||
suspendConfirm: "要冻结吗?"
|
||||
unsuspendConfirm: "要解除冻结吗?"
|
||||
selectList: "选择列表"
|
||||
selectAntenna: "天线选择"
|
||||
selectWidget: "选择小工具"
|
||||
customEmojis: "自定义Emoji"
|
||||
emoji: "表情符号"
|
||||
emojiName: "Emoji 名称"
|
||||
@ -364,7 +366,7 @@ resetPassword: "重置密码"
|
||||
newPasswordIs: "新的密码是「{password}」"
|
||||
autoReloadWhenDisconnected: "断开连接时自动重新加载"
|
||||
autoNoteWatch: "自动关注帖子"
|
||||
autoNoteWatchDescription: "让您能够收到关于「反应」和回复其他用户的帖子的通知。"
|
||||
autoNoteWatchDescription: "让您能够收到关于「回应」和回复其他用户的帖子的通知。"
|
||||
reduceUiAnimation: "减少UI动画"
|
||||
share: "分享"
|
||||
notFound: "未找到"
|
||||
@ -535,10 +537,35 @@ 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: "邮件发送测试"
|
||||
wordMute: "文字屏蔽"
|
||||
userSaysSomething: "{name}说了什么"
|
||||
makeActive: "激活"
|
||||
display: "显示"
|
||||
_sidebar:
|
||||
icon: "头像"
|
||||
hide: "隐藏"
|
||||
_wordMute:
|
||||
muteWords: "禁用词"
|
||||
muteWordsDescription: "使用空格分隔表示AND逻辑,使用换行符分隔表示OR逻辑。"
|
||||
muteWordsDescription2: "将关键字用斜线括起来表示正则表达式。"
|
||||
softDescription: "隐藏时间轴中指定条件的帖文。"
|
||||
hardDescription: "防止将具有指定条件的帖文添加到时间线。 即使您更改条件,未添加的帖文也会被排除在外。"
|
||||
_theme:
|
||||
explore: "寻找主题"
|
||||
install: "安装主题"
|
||||
@ -651,8 +678,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 +1205,28 @@ _notification:
|
||||
youReceivedFollowRequest: "您有新的关注请求"
|
||||
yourFollowRequestAccepted: "您的关注请求已通过"
|
||||
youWereInvitedToGroup: "您有新的群组邀请"
|
||||
_types:
|
||||
all: "全部"
|
||||
follow: "关注中"
|
||||
mention: "提及"
|
||||
reply: "回复"
|
||||
renote: "转发"
|
||||
quote: "引用"
|
||||
reaction: "回应"
|
||||
pollVote: "投票"
|
||||
receiveFollowRequest: "关注请求"
|
||||
_deck:
|
||||
alwaysShowMainColumn: "总是显示主列"
|
||||
columnAlign: "列对齐"
|
||||
addColumn: "添加列"
|
||||
swapLeft: "向左移动"
|
||||
swapRight: "向右移动"
|
||||
swapUp: "向上移动"
|
||||
swapDown: "向下移动"
|
||||
stackLeft: "向左折叠"
|
||||
popRight: "向右弹出"
|
||||
_columns:
|
||||
widgets: "小部件"
|
||||
widgets: "小工具"
|
||||
notifications: "通知"
|
||||
tl: "时间线"
|
||||
antenna: "天线"
|
||||
|
@ -409,6 +409,8 @@ deletedNote: "已删除的貼文"
|
||||
smtpHost: "主機"
|
||||
smtpUser: "使用名稱"
|
||||
smtpPass: "密碼"
|
||||
_sidebar:
|
||||
icon: "頭像"
|
||||
_theme:
|
||||
func: "函数"
|
||||
keys:
|
||||
@ -673,6 +675,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"`);
|
||||
}
|
||||
|
||||
}
|
44
package.json
44
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <syuilotan@yahoo.co.jp>",
|
||||
"version": "12.43.0",
|
||||
"version": "12.46.0",
|
||||
"codename": "indigo",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -36,18 +36,18 @@
|
||||
"mocha/serialize-javascript": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-runtime": "7.10.3",
|
||||
"@babel/plugin-transform-runtime": "7.11.0",
|
||||
"@elastic/elasticsearch": "7.8.0",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.29",
|
||||
"@fortawesome/free-brands-svg-icons": "5.13.1",
|
||||
"@fortawesome/free-regular-svg-icons": "5.13.1",
|
||||
"@fortawesome/free-solid-svg-icons": "5.13.1",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.30",
|
||||
"@fortawesome/free-brands-svg-icons": "5.14.0",
|
||||
"@fortawesome/free-regular-svg-icons": "5.14.0",
|
||||
"@fortawesome/free-solid-svg-icons": "5.14.0",
|
||||
"@fortawesome/vue-fontawesome": "0.1.10",
|
||||
"@koa/cors": "3.1.0",
|
||||
"@koa/multer": "3.0.0",
|
||||
"@koa/router": "9.0.1",
|
||||
"@sinonjs/fake-timers": "6.0.1",
|
||||
"@syuilo/aiscript": "0.8.0",
|
||||
"@syuilo/aiscript": "0.11.0",
|
||||
"@types/bcryptjs": "2.4.2",
|
||||
"@types/bull": "3.14.0",
|
||||
"@types/cbor": "5.0.0",
|
||||
@ -106,14 +106,14 @@
|
||||
"@types/ws": "7.2.6",
|
||||
"@typescript-eslint/parser": "3.6.0",
|
||||
"abort-controller": "3.0.0",
|
||||
"apexcharts": "3.19.3",
|
||||
"apexcharts": "3.20.0",
|
||||
"autobind-decorator": "2.4.0",
|
||||
"autosize": "4.0.2",
|
||||
"autwh": "0.1.0",
|
||||
"aws-sdk": "2.713.0",
|
||||
"aws-sdk": "2.724.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"blurhash": "1.1.3",
|
||||
"bull": "3.15.0",
|
||||
"bull": "3.16.0",
|
||||
"cafy": "15.2.1",
|
||||
"cbor": "5.0.2",
|
||||
"chalk": "4.1.0",
|
||||
@ -123,7 +123,7 @@
|
||||
"content-disposition": "0.5.3",
|
||||
"core-js": "3.6.5",
|
||||
"crc-32": "1.2.0",
|
||||
"css-loader": "3.6.0",
|
||||
"css-loader": "4.1.1",
|
||||
"cssnano": "4.1.10",
|
||||
"dateformat": "3.0.3",
|
||||
"deep-entries": "3.1.0",
|
||||
@ -144,7 +144,7 @@
|
||||
"gulp-rename": "2.0.0",
|
||||
"gulp-replace": "1.0.0",
|
||||
"gulp-sourcemaps": "2.6.5",
|
||||
"gulp-terser": "1.2.0",
|
||||
"gulp-terser": "1.2.1",
|
||||
"gulp-tslint": "8.1.4",
|
||||
"gulp-typescript": "6.0.0-alpha.1",
|
||||
"hard-source-webpack-plugin": "0.13.1",
|
||||
@ -163,7 +163,7 @@
|
||||
"json5-loader": "4.0.0",
|
||||
"jsonld": "3.1.1",
|
||||
"jsrsasign": "8.0.20",
|
||||
"katex": "0.11.1",
|
||||
"katex": "0.12.0",
|
||||
"koa": "2.13.0",
|
||||
"koa-bodyparser": "4.3.0",
|
||||
"koa-favicon": "2.1.0",
|
||||
@ -187,8 +187,8 @@
|
||||
"nprogress": "0.2.0",
|
||||
"object-assign-deep": "0.4.0",
|
||||
"os-utils": "0.0.14",
|
||||
"parse5": "6.0.0",
|
||||
"parsimmon": "1.14.0",
|
||||
"parse5": "6.0.1",
|
||||
"parsimmon": "1.15.0",
|
||||
"pg": "8.3.0",
|
||||
"portal-vue": "2.1.7",
|
||||
"portscanner": "2.2.0",
|
||||
@ -202,14 +202,14 @@
|
||||
"pureimage": "0.2.1",
|
||||
"qrcode": "1.4.4",
|
||||
"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",
|
||||
"redis-lock": "0.1.4",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"regenerator-runtime": "0.13.5",
|
||||
"regenerator-runtime": "0.13.7",
|
||||
"rename": "1.0.4",
|
||||
"request-stats": "3.0.0",
|
||||
"require-all": "3.0.0",
|
||||
@ -225,28 +225,28 @@
|
||||
"style-loader": "1.2.1",
|
||||
"summaly": "2.4.0",
|
||||
"syslog-pro": "1.0.0",
|
||||
"systeminformation": "4.26.9",
|
||||
"systeminformation": "4.26.10",
|
||||
"syuilo-password-strength": "0.0.1",
|
||||
"textarea-caret": "3.1.0",
|
||||
"three": "0.117.1",
|
||||
"tinycolor2": "1.4.1",
|
||||
"tmp": "0.2.1",
|
||||
"ts-loader": "8.0.0",
|
||||
"ts-loader": "8.0.1",
|
||||
"ts-node": "8.10.2",
|
||||
"tslint": "6.1.2",
|
||||
"tslint-sonarts": "1.9.0",
|
||||
"typeorm": "0.2.25",
|
||||
"typescript": "3.9.6",
|
||||
"typescript": "3.9.7",
|
||||
"ulid": "2.3.0",
|
||||
"url-loader": "4.1.0",
|
||||
"uuid": "8.2.0",
|
||||
"uuid": "8.3.0",
|
||||
"v-animate-css": "0.0.3",
|
||||
"v-debounce": "0.1.2",
|
||||
"vue": "2.6.11",
|
||||
"vue-color": "2.7.1",
|
||||
"vue-content-loading": "1.6.0",
|
||||
"vue-cropperjs": "4.1.0",
|
||||
"vue-i18n": "8.18.2",
|
||||
"vue-i18n": "8.20.0",
|
||||
"vue-json-pretty": "1.6.5",
|
||||
"vue-loader": "15.9.3",
|
||||
"vue-marquee-text-component": "1.1.1",
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="mk-app" v-hotkey.global="keymap">
|
||||
<header class="header">
|
||||
<header class="header" ref="header">
|
||||
<div class="title" ref="title">
|
||||
<transition :name="$store.state.device.animation ? 'header' : ''" mode="out-in" appear>
|
||||
<button class="_button back" v-if="canBack" @click="back()"><fa :icon="faChevronLeft"/></button>
|
||||
@ -18,8 +18,10 @@
|
||||
</transition>
|
||||
</div>
|
||||
<div class="sub">
|
||||
<button v-if="widgetsEditMode" class="_button edit active" @click="widgetsEditMode = false"><fa :icon="faGripVertical"/></button>
|
||||
<button v-else class="_button edit" @click="widgetsEditMode = true"><fa :icon="faGripVertical"/></button>
|
||||
<template v-if="$store.getters.isSignedIn">
|
||||
<button v-if="widgetsEditMode" class="_button edit active" @click="widgetsEditMode = false"><fa :icon="faGripVertical"/></button>
|
||||
<button v-else class="_button edit" @click="widgetsEditMode = true"><fa :icon="faGripVertical"/></button>
|
||||
</template>
|
||||
<div class="search">
|
||||
<fa :icon="faSearch"/>
|
||||
<input type="search" :placeholder="$t('search')" v-model="searchQuery" v-autocomplete="{ model: 'searchQuery' }" :disabled="searchWait" @keypress="searchKeypress"/>
|
||||
@ -29,7 +31,7 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<x-sidebar ref="nav"/>
|
||||
<x-sidebar ref="nav" @change-view-mode="calcHeaderWidth"/>
|
||||
|
||||
<div class="contents" ref="contents" :class="{ wallpaper }">
|
||||
<main ref="main">
|
||||
@ -75,7 +77,7 @@
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<div class="buttons" :class="{ navHidden }">
|
||||
<button class="button nav _button" @click="showNav" ref="navButton"><fa :icon="faBars"/><i v-if="navIndicated"><fa :icon="faCircle"/></i></button>
|
||||
<button v-if="$route.name === 'index'" class="button home _button" @click="top()"><fa :icon="faHome"/></button>
|
||||
<button v-else class="button home _button" @click="$router.push('/')"><fa :icon="faHome"/></button>
|
||||
@ -83,7 +85,7 @@
|
||||
<button v-if="$store.getters.isSignedIn" class="button post _buttonPrimary" @click="post()"><fa :icon="faPencilAlt"/></button>
|
||||
</div>
|
||||
|
||||
<button v-if="$store.getters.isSignedIn" class="post _buttonPrimary" @click="post()"><fa :icon="faPencilAlt"/></button>
|
||||
<button v-if="$store.getters.isSignedIn" class="post _buttonPrimary" :class="{ navHidden }" @click="post()"><fa :icon="faPencilAlt"/></button>
|
||||
|
||||
<stream-indicator v-if="$store.getters.isSignedIn"/>
|
||||
</div>
|
||||
@ -122,6 +124,7 @@ export default Vue.extend({
|
||||
isDesktop: window.innerWidth >= DESKTOP_THRESHOLD,
|
||||
canBack: false,
|
||||
menuDef: this.$store.getters.nav({}),
|
||||
navHidden: false,
|
||||
wallpaper: localStorage.getItem('wallpaper') != null,
|
||||
faGripVertical, faChevronLeft, faComments, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faBell, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faEnvelope, faListUl, faPlus, faUserClock, faLaugh, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faServer, faProjectDiagram
|
||||
};
|
||||
@ -141,7 +144,7 @@ export default Vue.extend({
|
||||
};
|
||||
},
|
||||
|
||||
widgets(): any[] {
|
||||
widgets(): any {
|
||||
if (this.$store.getters.isSignedIn) {
|
||||
const widgets = this.$store.state.deviceUser.widgets;
|
||||
return {
|
||||
@ -150,18 +153,24 @@ export default Vue.extend({
|
||||
mobile: widgets.filter(x => x.place === 'mobile'),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
left: [],
|
||||
right: [{
|
||||
const right = [{
|
||||
name: 'calendar',
|
||||
id: 'b', place: 'right', data: {}
|
||||
}, {
|
||||
name: 'trends',
|
||||
id: 'c', place: 'right', data: {}
|
||||
}];
|
||||
|
||||
if (this.$route.name !== 'index') {
|
||||
right.unshift({
|
||||
name: 'welcome',
|
||||
id: 'a', place: 'right', data: {}
|
||||
}, {
|
||||
name: 'calendar',
|
||||
id: 'b', place: 'right', data: {}
|
||||
}, {
|
||||
name: 'trends',
|
||||
id: 'c', place: 'right', data: {}
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
left: [],
|
||||
right,
|
||||
mobile: [],
|
||||
};
|
||||
}
|
||||
@ -217,22 +226,15 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const adjustTitlePosition = () => {
|
||||
const left = this.$refs.main.getBoundingClientRect().left - this.$refs.nav.$el.offsetWidth;
|
||||
if (left >= 0) {
|
||||
this.$refs.title.style.left = left + 'px';
|
||||
}
|
||||
};
|
||||
|
||||
adjustTitlePosition();
|
||||
this.adjustTitlePosition();
|
||||
|
||||
const ro = new ResizeObserver((entries, observer) => {
|
||||
adjustTitlePosition();
|
||||
this.adjustTitlePosition();
|
||||
});
|
||||
|
||||
ro.observe(this.$refs.contents);
|
||||
|
||||
window.addEventListener('resize', adjustTitlePosition, { passive: true });
|
||||
window.addEventListener('resize', this.adjustTitlePosition, { passive: true });
|
||||
|
||||
if (!this.isDesktop) {
|
||||
window.addEventListener('resize', () => {
|
||||
@ -242,9 +244,27 @@ export default Vue.extend({
|
||||
|
||||
// widget follow
|
||||
this.attachSticky();
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.calcHeaderWidth();
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
adjustTitlePosition() {
|
||||
const left = this.$refs.main.getBoundingClientRect().left - this.$refs.nav.$el.offsetWidth;
|
||||
if (left >= 0) {
|
||||
this.$refs.title.style.left = left + 'px';
|
||||
}
|
||||
},
|
||||
|
||||
calcHeaderWidth() {
|
||||
const navWidth = this.$refs.nav.$el.offsetWidth;
|
||||
this.navHidden = navWidth === 0;
|
||||
this.$refs.header.style.width = `calc(100% - ${navWidth}px)`;
|
||||
this.adjustTitlePosition();
|
||||
},
|
||||
|
||||
showNav() {
|
||||
this.$refs.nav.show();
|
||||
},
|
||||
@ -365,12 +385,8 @@ export default Vue.extend({
|
||||
<style lang="scss" scoped>
|
||||
.mk-app {
|
||||
$header-height: 60px;
|
||||
$nav-width: 250px; // TODO: どこかに集約したい
|
||||
$nav-icon-only-width: 80px; // TODO: どこかに集約したい
|
||||
$main-width: 670px;
|
||||
$ui-font-size: 1em; // TODO: どこかに集約したい
|
||||
$nav-icon-only-threshold: 1279px; // TODO: どこかに集約したい
|
||||
$nav-hide-threshold: 650px; // TODO: どこかに集約したい
|
||||
$header-sub-hide-threshold: 1090px;
|
||||
$left-widgets-hide-threshold: 1600px;
|
||||
$right-widgets-hide-threshold: 1090px;
|
||||
@ -391,21 +407,13 @@ export default Vue.extend({
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: $header-height;
|
||||
width: calc(100% - #{$nav-width});
|
||||
width: 100%;
|
||||
//background-color: var(--panel);
|
||||
-webkit-backdrop-filter: blur(32px);
|
||||
backdrop-filter: blur(32px);
|
||||
background-color: var(--header);
|
||||
border-bottom: solid 1px var(--divider);
|
||||
|
||||
@media (max-width: $nav-icon-only-threshold) {
|
||||
width: calc(100% - #{$nav-icon-only-width});
|
||||
}
|
||||
|
||||
@media (max-width: $nav-hide-threshold) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
> .title {
|
||||
position: relative;
|
||||
line-height: $header-height;
|
||||
@ -675,7 +683,7 @@ export default Vue.extend({
|
||||
}
|
||||
|
||||
> .post {
|
||||
display: none;
|
||||
display: block;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
bottom: 32px;
|
||||
@ -686,8 +694,8 @@ export default Vue.extend({
|
||||
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12);
|
||||
font-size: 22px;
|
||||
|
||||
@media (min-width: ($nav-hide-threshold + 1px)) {
|
||||
display: block;
|
||||
&.navHidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: ($header-sub-hide-threshold + 1px)) {
|
||||
@ -709,7 +717,7 @@ export default Vue.extend({
|
||||
padding: 0 16px 16px 16px;
|
||||
}
|
||||
|
||||
@media (min-width: ($nav-hide-threshold + 1px)) {
|
||||
&:not(.navHidden) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
@ -70,21 +71,27 @@
|
||||
<button v-else class="button _button">
|
||||
<fa :icon="faBan"/>
|
||||
</button>
|
||||
<button v-if="!isMyNote && appearNote.myReaction == null" class="button _button" @click="react()" ref="reactButton">
|
||||
<button v-if="appearNote.myReaction == null" class="button _button" @click="react()" ref="reactButton">
|
||||
<fa :icon="faPlus"/>
|
||||
</button>
|
||||
<button v-if="!isMyNote && appearNote.myReaction != null" class="button _button reacted" @click="undoReact(appearNote)" ref="reactButton">
|
||||
<button v-if="appearNote.myReaction != null" class="button _button reacted" @click="undoReact(appearNote)" ref="reactButton">
|
||||
<fa :icon="faMinus"/>
|
||||
</button>
|
||||
<button class="button _button" @click="menu()" ref="menuButton">
|
||||
<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,
|
||||
};
|
||||
|
||||
const choices = [...this.appearNote.poll.choices];
|
||||
choices[choice] = {
|
||||
...choices[choice],
|
||||
votes: choices[choice].votes + 1,
|
||||
...(body.userId === this.$store.state.i.id ? {
|
||||
isVoted: true
|
||||
} : {})
|
||||
};
|
||||
|
||||
n.poll = {
|
||||
...this.appearNote.poll,
|
||||
choices: choices
|
||||
};
|
||||
|
||||
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');
|
||||
|
@ -51,11 +51,8 @@ export default Vue.extend({
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isMe(): boolean {
|
||||
return this.$store.getters.isSignedIn && this.$store.state.i.id === this.note.userId;
|
||||
},
|
||||
canToggle(): boolean {
|
||||
return !this.reaction.match(/@\w/) && !this.isMe && this.$store.getters.isSignedIn;
|
||||
return !this.reaction.match(/@\w/) && this.$store.getters.isSignedIn;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
@ -9,7 +9,7 @@
|
||||
</transition>
|
||||
|
||||
<transition name="nav">
|
||||
<nav class="nav" v-show="showing">
|
||||
<nav class="nav" :class="{ iconOnly, hidden }" v-show="showing">
|
||||
<div>
|
||||
<button class="item _button account" @click="openAccountMenu" v-if="$store.getters.isSignedIn">
|
||||
<mk-avatar :user="$store.state.i" class="avatar"/><mk-acct class="text" :user="$store.state.i"/>
|
||||
@ -62,6 +62,8 @@ export default Vue.extend({
|
||||
menuDef: this.$store.getters.nav({
|
||||
search: this.search
|
||||
}),
|
||||
iconOnly: false,
|
||||
hidden: false,
|
||||
faGripVertical, faChevronLeft, faComments, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faBell, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faEnvelope, faListUl, faPlus, faUserClock, faLaugh, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faServer, faProjectDiagram
|
||||
};
|
||||
},
|
||||
@ -85,9 +87,35 @@ export default Vue.extend({
|
||||
$route(to, from) {
|
||||
this.showing = false;
|
||||
},
|
||||
|
||||
'$store.state.device.sidebarDisplay'() {
|
||||
this.calcViewState();
|
||||
},
|
||||
|
||||
iconOnly() {
|
||||
this.$nextTick(() => {
|
||||
this.$emit('change-view-mode');
|
||||
});
|
||||
},
|
||||
|
||||
hidden() {
|
||||
this.$nextTick(() => {
|
||||
this.$emit('change-view-mode');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
window.addEventListener('resize', this.calcViewState);
|
||||
this.calcViewState();
|
||||
},
|
||||
|
||||
methods: {
|
||||
calcViewState() {
|
||||
this.iconOnly = (window.innerWidth <= 1279) || (this.$store.state.device.sidebarDisplay === 'icon');
|
||||
this.hidden = (window.innerWidth <= 650) || (this.$store.state.device.sidebarDisplay === 'hide');
|
||||
},
|
||||
|
||||
show() {
|
||||
this.showing = true;
|
||||
},
|
||||
@ -314,10 +342,8 @@ export default Vue.extend({
|
||||
|
||||
.mvcprjjd {
|
||||
$ui-font-size: 1em; // TODO: どこかに集約したい
|
||||
$nav-width: 250px; // TODO: どこかに集約したい
|
||||
$nav-icon-only-width: 80px; // TODO: どこかに集約したい
|
||||
$nav-icon-only-threshold: 1279px; // TODO: どこかに集約したい
|
||||
$nav-hide-threshold: 650px; // TODO: どこかに集約したい
|
||||
$nav-width: 250px;
|
||||
$nav-icon-only-width: 80px;
|
||||
|
||||
> .nav-back {
|
||||
z-index: 1001;
|
||||
@ -331,19 +357,66 @@ export default Vue.extend({
|
||||
width: $nav-width;
|
||||
box-sizing: border-box;
|
||||
|
||||
@media (max-width: $nav-icon-only-threshold) {
|
||||
&.iconOnly {
|
||||
flex: 0 0 $nav-icon-only-width;
|
||||
width: $nav-icon-only-width;
|
||||
|
||||
&:not(.hidden) {
|
||||
> div {
|
||||
width: $nav-icon-only-width;
|
||||
|
||||
> .divider {
|
||||
margin: 8px auto;
|
||||
width: calc(100% - 32px);
|
||||
}
|
||||
|
||||
> .item {
|
||||
padding-left: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: $ui-font-size * 1.1;
|
||||
line-height: 3.7rem;
|
||||
|
||||
> [data-icon],
|
||||
> .avatar {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
> i {
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
> .text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $nav-hide-threshold) {
|
||||
&.hidden {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1001;
|
||||
|
||||
> div {
|
||||
> .index,
|
||||
> .notifications {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: $nav-hide-threshold + 1px) {
|
||||
&:not(.hidden) {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
@ -365,25 +438,6 @@ export default Vue.extend({
|
||||
border-top: solid 1px var(--divider);
|
||||
}
|
||||
|
||||
@media (max-width: $nav-icon-only-threshold) and (min-width: $nav-hide-threshold + 1px) {
|
||||
width: $nav-icon-only-width;
|
||||
|
||||
> .divider {
|
||||
margin: 8px auto;
|
||||
width: calc(100% - 32px);
|
||||
}
|
||||
|
||||
> .item {
|
||||
&:first-child {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .item {
|
||||
position: relative;
|
||||
display: block;
|
||||
@ -452,34 +506,6 @@ export default Vue.extend({
|
||||
margin-top: 16px;
|
||||
border-top: solid 1px var(--divider);
|
||||
}
|
||||
|
||||
@media (max-width: $nav-icon-only-threshold) and (min-width: $nav-hide-threshold + 1px) {
|
||||
padding-left: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: $ui-font-size * 1.2;
|
||||
line-height: 3.7rem;
|
||||
|
||||
> [data-icon],
|
||||
> .avatar {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
> i {
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
> .text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $nav-hide-threshold) {
|
||||
> .index,
|
||||
> .notifications {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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');
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
<x-window ref="window" :width="400" :height="450" :no-padding="true" @closed="() => { $emit('closed'); destroyDom(); }" :with-ok-button="true" :ok-button-disabled="false" @ok="ok()" :can-close="false">
|
||||
<template #header>{{ title || $t('generateAccessToken') }}</template>
|
||||
<div class="ugkkpisj">
|
||||
<div>
|
||||
<mk-info warn v-if="information">{{ information }}</mk-info>
|
||||
<div v-if="information">
|
||||
<mk-info warn>{{ information }}</mk-info>
|
||||
</div>
|
||||
<div>
|
||||
<mk-input v-model="name">{{ $t('name') }}</mk-input>
|
||||
|
@ -19,14 +19,14 @@ import MkButton from './button.vue';
|
||||
import paging from '../../scripts/paging';
|
||||
|
||||
export default Vue.extend({
|
||||
mixins: [
|
||||
paging({}),
|
||||
],
|
||||
|
||||
components: {
|
||||
MkButton
|
||||
},
|
||||
|
||||
mixins: [
|
||||
paging({}),
|
||||
],
|
||||
|
||||
props: {
|
||||
pagination: {
|
||||
required: true
|
||||
|
@ -42,6 +42,7 @@ export default Vue.extend({
|
||||
},
|
||||
methods: {
|
||||
toggle() {
|
||||
if (this.disabled) return;
|
||||
this.$emit('change', this.value);
|
||||
}
|
||||
}
|
||||
@ -61,7 +62,10 @@ export default Vue.extend({
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
|
||||
&, * {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.checked {
|
||||
|
@ -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, {
|
||||
|
@ -11,7 +11,7 @@
|
||||
<img v-if="announcement.imageUrl" :src="announcement.imageUrl"/>
|
||||
</div>
|
||||
<div class="_footer" v-if="$store.getters.isSignedIn && !announcement.isRead">
|
||||
<mk-button @click="read(announcement)" primary><fa :icon="faCheck"/> {{ $t('gotIt') }}</mk-button>
|
||||
<mk-button @click="read(items, announcement, i)" primary><fa :icon="faCheck"/> {{ $t('gotIt') }}</mk-button>
|
||||
</div>
|
||||
</section>
|
||||
</mk-pagination>
|
||||
@ -47,8 +47,12 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
methods: {
|
||||
read(announcement) {
|
||||
announcement.isRead = true;
|
||||
// TODO: これは実質的に親コンポーネントから子コンポーネントのプロパティを変更してるのでなんとかしたい
|
||||
read(items, announcement, i) {
|
||||
Vue.set(items, i, {
|
||||
...announcement,
|
||||
isRead: true,
|
||||
});
|
||||
this.$root.api('i/read-announcement', { announcementId: announcement.id });
|
||||
},
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
@ -151,7 +151,7 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
onKeypress(e) {
|
||||
if ((e.which == 10 || e.which == 13) && e.ctrlKey && this.canSend) {
|
||||
if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && this.canSend) {
|
||||
this.send();
|
||||
}
|
||||
},
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -7,6 +7,12 @@
|
||||
<template #desc><button class="_textButton" @click="addItem">{{ $t('addItem') }}</button></template>
|
||||
</mk-textarea>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<div>{{ $t('display') }}</div>
|
||||
<mk-radio v-model="sidebarDisplay" value="full">{{ $t('_sidebar.full') }}</mk-radio>
|
||||
<mk-radio v-model="sidebarDisplay" value="icon">{{ $t('_sidebar.icon') }}</mk-radio>
|
||||
<!-- <mk-radio v-model="sidebarDisplay" value="hide" disabled>{{ $t('_sidebar.hide') }}</mk-radio>--> <!-- TODO: サイドバーを完全に隠せるようにすると、別途ハンバーガーボタンのようなものをUIに表示する必要があり面倒 -->
|
||||
</div>
|
||||
<div class="_footer">
|
||||
<mk-button inline @click="save()" primary><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
||||
<mk-button inline @click="reset()"><fa :icon="faRedo"/> {{ $t('default') }}</mk-button>
|
||||
@ -19,12 +25,14 @@ import Vue from 'vue';
|
||||
import { faListUl, faSave, faRedo } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkButton from '../../components/ui/button.vue';
|
||||
import MkTextarea from '../../components/ui/textarea.vue';
|
||||
import MkRadio from '../../components/ui/radio.vue';
|
||||
import { defaultDeviceUserSettings } from '../../store';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
MkButton,
|
||||
MkTextarea,
|
||||
MkRadio,
|
||||
},
|
||||
|
||||
data() {
|
||||
@ -38,7 +46,12 @@ export default Vue.extend({
|
||||
computed: {
|
||||
splited(): string[] {
|
||||
return this.items.trim().split('\n').filter(x => x.trim() !== '');
|
||||
}
|
||||
},
|
||||
|
||||
sidebarDisplay: {
|
||||
get() { return this.$store.state.device.sidebarDisplay; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'sidebarDisplay', value }); }
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
|
@ -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>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { utils, values } from '@syuilo/aiscript';
|
||||
import { jsToVal } from '@syuilo/aiscript/built/interpreter/util';
|
||||
|
||||
// TODO: vue3に移行した折にはvmを渡す必要は無くなるはず
|
||||
export function createAiScriptEnv(vm, opts) {
|
||||
let apiRequests = 0;
|
||||
return {
|
||||
@ -14,9 +15,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,
|
||||
@ -42,14 +43,16 @@ export function createAiScriptEnv(vm, opts) {
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: vue3に移行した折にはvmを渡す必要は無くなるはず
|
||||
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 +62,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 = {
|
||||
@ -69,6 +77,7 @@ export const defaultDeviceSettings = {
|
||||
enableInfiniteScroll: true,
|
||||
fixedWidgetsPosition: false,
|
||||
useBlurEffectForModal: true,
|
||||
sidebarDisplay: 'full', // full, icon, hide
|
||||
roomGraphicsQuality: 'medium',
|
||||
roomUseOrthographicCamera: true,
|
||||
deckColumnAlign: 'left',
|
||||
@ -103,6 +112,8 @@ export default () => new Vuex.Store({
|
||||
postFormActions: [],
|
||||
userActions: [],
|
||||
noteActions: [],
|
||||
noteViewInterruptors: [],
|
||||
notePostInterruptors: [],
|
||||
},
|
||||
|
||||
getters: {
|
||||
@ -266,6 +277,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 +614,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 +632,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, select: 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 ? {
|
||||
|
@ -9,8 +9,6 @@ export default async (actor: IRemoteUser, activity: ILike) => {
|
||||
const note = await fetchNote(targetUri);
|
||||
if (!note) return `skip: target note not found ${targetUri}`;
|
||||
|
||||
if (actor.id === note.userId) return `skip: cannot react to my note`;
|
||||
|
||||
await extractEmojis(activity.tag || [], actor.host).catch(() => null);
|
||||
|
||||
await create(actor, note, activity._misskey_reaction || activity.content || activity.name);
|
||||
|
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 });
|
||||
|
@ -40,12 +40,6 @@ export const meta = {
|
||||
id: '033d0620-5bfe-4027-965d-980b0c85a3ea'
|
||||
},
|
||||
|
||||
isMyNote: {
|
||||
message: 'You can not react to your own notes.',
|
||||
code: 'IS_MY_NOTE',
|
||||
id: '7eeb9714-b047-43b5-b559-7b1b72810f53'
|
||||
},
|
||||
|
||||
alreadyReacted: {
|
||||
message: 'You are already reacting to that note.',
|
||||
code: 'ALREADY_REACTED',
|
||||
@ -60,7 +54,6 @@ export default define(meta, async (ps, user) => {
|
||||
throw e;
|
||||
});
|
||||
await createReaction(user, note, ps.reaction).catch(e => {
|
||||
if (e.id === '2d8e7297-1873-4c00-8404-792c68d7bef0') throw new ApiError(meta.errors.isMyNote);
|
||||
if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError(meta.errors.alreadyReacted);
|
||||
throw e;
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user